自学内容网 自学内容网

【CSS in Depth 2 精译_058】第九章 CSS 的模块化与作用域 + 9.1 CSS 模块的定义(下)

当前内容所在位置(可进入专栏查看其他译好的章节内容)

  • 【第三部分 现代 CSS 代码组织】
  • 【第九章 CSS 的模块化与作用域】
    • 9.1 模块的定义
      • 9.1.1 模块和全局样式
      • 9.1.2 一个简单的 CSS 模块
      • 9.1.3 模块的变体 ✔️
      • 9.1.4 多元素模块 ✔️
    • 9.2 将模块组合为更大的结构

《CSS in Depth》新版封面

《CSS in Depth》新版封面

译者按
本篇为 9.1 小节 CSS 模块定义的下篇,通过两个具体的示例对模块化 CSS 的应用即推广进行了具体讲解。由于新版引入了最新的层叠图层即 Flexbox 布局等相关语法,使得 CSS 按模块化组织更加得心应手,结构层次也更加清晰。学习本篇重点在于理解作者的思路和决策依据,而非照本宣科,机械照搬。

9.1.3 模块的变体 Variations of a module

保持一致性确实不错,但有时候需要特意避免完全一致。方才的消息模块固然好用,但在某些情况下也需要作些调整。比如,如果需要显示一则报错信息,这时就应该使用红色而不是之前的蓝绿色(teal);再比如,您可能需要区分单纯的信息和示意操作成功的通知(如保存成功),这时就可以通过定义 修饰符(modifiers 来实现。

修饰符可以通过一个以模块名称开头的新类名来定义。例如,消息模块的 error 修饰符可以写作 message-error。通过包含模块名称,您就明确指定了该样式类属于消息模块。

关于 BEM 类名约定

带双连字符以及双下划线的类名是一种常见的类名约定,它们都属于 BEM 方法论的范畴。BEMBlock-Element-Modifier 的首字母缩写。

BEM 类名约定中,一个 block 块表示的是模块的主元素(main element),通常为一个具有描述性的、唯一的类名,如 messageBEM 中的 element 元素则表示模块中的子元素(有时也可以为子孙元素),并通常用双下划线来连接 block 块名称与 element 元素名称,例如用 media__image 来引用 media 媒体模块中的图片元素。而 BEM 中的 modifier 修饰符则是在创建模块的变体形式时添加到 block 块中的某个类名称,它们通常用 block 块名称后的一组双连字符来加以区分,例如 message--error

值得一提的是,BEM 只是众多类名约定中的一种,但由于其名气最大,本章也采纳了里面的部分做法;其他广受欢迎的类名约定还包括 OOCSSSMACSSITCSS 以及 SUITCSS。虽然在具体的约定内容和命名规则上略有不同,但目的都是一样的:以模块化的方式来组织页面样式。

下面为示例模块创建三个修饰符:成功(success)、警告(warning)和错误(error)。将代码清单 9.4 添加到样式表中,通过 BEM 中的双连字符约定来指明该修饰符属于 message 消息模块。

代码清单 9.4 带修饰符类名的消息模块

@layer modules {
  .message { /* 基础 message 模块样式 */
    padding: 0.8em 1.2em;
    border-radius: 0.2em;
    border: 1px solid #265559;
    color: #265559;
    background-color: #e0f0f2;
  }

  .message--success { /* success 修饰器将 message 模块改为绿色 */
    color: #2f5926;
    border-color: #2f5926;
    background-color:  #cfe8c9;
  }

  .message--warning { /* warning 修饰符将 message 模块改为黄色 */
    color: #594826;
    border-color: #594826;
    background-color:  #e8dec9;
  }

  .message--error { /* error 修饰符将 message 模块改为红色 */
    color: #59262f;
    border-color: #59262f;
    background-color:  #e8c9cf;
  }
}

修饰符的样式不用重新定义整个模块,只需覆盖需要变更的部分即可。在本例中,则表示修改文字、边框及背景的颜色。

如代码清单 9.5 所示,要让修饰符样式生效,只需将主模块类名和对应的修饰符类名同时添加到目标元素上即可。这样既应用了模块的默认样式,又可以根据需求利用修饰符重写个别样式。

代码清单 9.5 使用了错误修饰符 error 的消息模块示例

<div class="message message--error"><!-- 将两个样式类同时放入元素中 -->
  Invalid password
</div>

同理,需要展示成功或警告消息时,添加对应的修饰符即可。它们会改变模块的颜色,而其他修饰符还能改变模块的大小甚至布局。

9.1.3.1 按钮模块的变体形式 Button module variants

再来看看另一个模块的变体形式。我们将实现一个按钮模块,其中包含尺寸大小和颜色样式的变体形式(如图 9.2 所示)。这样就能用不同的颜色为按钮添加视觉意义。绿色代表积极的行为,比如保存和提交表单;红色则表示警告,以防用户不小心点到或意外取消按钮。

图 9.2 使用了不同尺寸和颜色修饰符的按钮效果

【图 9.2 使用了不同尺寸和颜色修饰符的按钮效果】

代码清单 9.6 给出了上述按钮的具体样式,其中包括基础按钮模块和四个修饰符样式类:两个尺寸修饰符和两个颜色修饰符。将这些代码添加到样式表中。

代码清单 9.6 按钮模块及其修饰符样式代码

@layer modules {
  .button { /* 按钮基础样式 */
    padding: 0.5em 0.8em;
    border: 1px solid #265559;
    border-radius: 0.2em;
    background-color: transparent;
    font-size: 1rem;
  }

  .button--success { /* 绿色的 success 颜色变体形式 */
    border-color: #cfe8c9;
    color: #fff;
    background-color: #2f5926;
  }

  .button--danger { /* 红色的 danger 颜色变体形式 */
    border-color: #e8c9c9;
    color: #fff;
    background-color: #a92323;
  }

  .button--small { /* 小字号的变体形式 */
    font-size: 0.8rem;
  }

  .button--large { /* 大字号的变体形式 */
    font-size: 1.2rem;
  }
}

字号修饰符旨在设置字体的大小。在第二章中我们用过类似的技巧:通过变更字号来调整元素相对单位 em 的具体取值,进而改变内边距和圆角半径的大小,无需分别重写对应的声明样式。

提示

一个模块中的所有代码务必要集中放到同一个地方,以便让模块一个接一个的组成最终想要的样式表。

有了这些修饰符,写 HTML 的时候就有了多种选择:既可以根据按钮的重要程度来添加修饰符类并控制其大小,也可以搭配不同的颜色来给用户提供相应的语境。

代码清单 9.7 中的 HTML 通过不同的修饰符组合来创建各种按钮效果。将其添加到页面的任意位置,看看效果如何:

代码清单 9.7 使用修饰符创建多种类型的按钮样式

<button class="button button--large">Read more</button><!-- 带大号修饰符的按钮模块 -->
<button class="button button--success">Save</button><!-- 带成功修饰符的按钮模块 -->
<button class="button button--danger button--small">Cancel</button><!-- 带危险和小号修饰符的按钮模块 -->

这里的第一个按钮是大号字体;第二个是表示操作成功的绿色按钮;第三个则带有两个修饰符:一个变更颜色(danger 危险修饰符),另一个变更大小(small 小号修饰符),最终效果如图 9.2 所示。

双连字符的写法看上去稍显多余,但在创建名称很长的模块时,比如 nav-menu 导航菜单或 pull-quote 文章摘要时,该写法的好处就凸显出来了。给这些模块添加修饰符后,类名就可以分别写作 nav-menu--horizontalpull-quote--dark 的形式。

双连字符的写法很容易区分哪部分是模块名称,哪部分是修饰符;nav-menu--horizontalnav--menu-horizontal 分别代表了不同的含义。如此一来,即便项目中存在很多名称相仿的模块,也可以很容易进行分辨。

9.1.3.2 不要使用有语境依赖的选择器 Avoiding context-dependent selectors

假设我们正在维护一个网站,里面有浅色调的下拉菜单。现在需要将页面标题的下拉菜单样式反转,使其变为深底白字效果。

如果没有模块化 CSS,这时可能会用类似于 .page-header .dropdown 的选择器,先选中要修改的下拉菜单,再通过重写 dropdown 类的默认样式实现目标;但在模块化 CSS 中,上述选择器的写法是严格禁用的。虽然后代选择器可以满足当下的需求,但后续也很可能带来诸多问题,下面来具体梳理一下这些问题。

首先,必须考虑将这段代码放到哪里:是和页面标题样式放到一起合适呢,还是和下拉菜单放到一起合适?如果设置太多类似的单一用途的样式规则,彼此间毫无关联,到最后样式表肯定会变得杂乱无章;要是后续需要修改样式,您还能快速回忆起它们在哪儿吗?如果换作其他同事来修改,他们能快速定位吗?

其次,这种处理方式提升了选择器优先级。当下次需要修改样式时,则不得不达到或者继续提升该优先级(specificity)。

再者,后续可能需要在其他场景下应用深色的下拉列表,而之前创建的版本需要满足位于页面标题区的大前提。如若侧边栏也需要同样的下拉列表效果,就只能为该规则集添加新的选择器来同时匹配两个业务场景,或者完全复刻一遍样式。

最后,反复采用这样的写法会导致选择器越写越长,最终令 CSS 与特定的 HTML 结构深度绑定。例如,对于选择器 #products-page .sidebar .social-media div::first-child h3 而言,该样式集就与指定页面的指定位置紧密耦合到了一起。

这些问题是开发人员处理 CSS 时屡屡受挫的根源。要使用和维护的样式表越长,情况就会越糟糕。当新样式需要覆盖旧样式时,选择器的优先级也会持续攀升,以致于到最后才猛然发现某个选择器为了选择一个复选框,竟然已经包含了两个 ID 和五个样式类。

在样式表中,元素可能会被各种彼此不相关的选择器选中,以致于很难找出其真正生效的样式。此时理解整个样式表的组织方式变得愈发困难,根本理不清它们是如何将页面渲染成现在这样的。代码搞不明白则意味着 Bug 屡见不鲜,可能很小的样式变更都会波及一大片区域。删除旧代码风险就更高了,因为没人知道这段代码是做什么用的、是否还在生效。样式表越长,类似的问题就愈发严重。模块化 CSS 就是要尝试解决这些问题。

当模块需要不同的外观或者呈现效果时,可以创建一个直接作用于指定元素的修饰符类。例如将选择器 .page-header .dropdown 直接写作 .dropdown--dark。这样一来,最终效果由且仅由该模块本身决定,其他模块将无法对其进行修改。鉴于深色系下拉菜单效果不与 HTML 中的任何深度嵌套结构绑定,它也就可以在页面任意位置随意设置。

切忌通过基于页面位置的后代选择器来修改模块样式。只要严格遵守这一原则,就可以有效防止样式表变成一堆难以维护的代码。

9.1.4 包含多个元素的模块 Modules with multiple elements

我们已经创建了 message 消息和 button 按钮两个模块,它们既简单又实用,并且都由单个元素组成;但是有很多模块需要多个元素,我们不可能单凭一个元素来实现下拉菜单或者模态框效果。

下面来创建一个更复杂的样式模块。它是一个由前端开发与 CSS 领域专家 妮可·沙利文(Nicole Sullivan 1 率先为其命名的媒体模块(media module),如图 9.3 所示:

图 9.3 由四个元素组成的 media 媒体模块

【图 9.3 由四个元素组成的 media 媒体模块】

这个模块由四个元素组成:一个 div 容器、以及容器内的一张图片、和正文区,最后是正文中的标题。与其他模块一样,我们将给主容器添加 media 样式类作为模块名称。

对于图片和正文,则可以使用样式类 media__imagemedia__body。它们以模块名开头,并通过双下划线连接模块与子元素名称。这也是 BEM 命名约定的另一种形式。与双连字符表示的修饰符一样,这样的类名可以清楚的告知该元素所扮演的角色及所属的模块。

媒体模块的样式代码如代码清单 9.8 所示。将它们添加到您的本地样式表中:

代码清单 9.8 包含子元素的媒体模块样式代码

@layer modules {
  .media { /* 主容器 */
    display: flex;
    gap: 1.5em;
    padding: 1.5rem;
    background-color: #eee;
    border-radius: 5px;
  }

  .media__image { /* 图片元素样式 */
    align-self: start;
  }

  .media__body { /* 正文元素样式 */
    overflow: auto;
    margin-block-start: 0;
  }

  .media__body > h4 { /* 正文中的标题样式 */
    margin-block-start: 0;
    color: #333;
  }
}

注意,此时无需使用太多后代选择器。虽然图片是媒体模块的一个子元素,也可以写作 .media > .media__image,但大可不必;因为样式类 media__image 已经包含了模块名称,足以确保该名称是唯一的。

而正文区中的标题直接用到了后代选择器。其实也可以使用 media__title 这样的样式类(或者写作 media__body__title,以完整表示出其在整个模块层级中的位置),但是大部分时候没必要这么写。本例中的 <h4> 标签已经足够语义化来表明它是媒体模块的标题了。只是这样一来,标题就不能用其他级别的 HTML 标签(如 <h3><h5>)了。如果不希望限制得这么死板,可以改为样式类来选中元素。相关 HTML 标记如代码清单 9.9 所示:

代码清单 9.9 媒体模块的 HTML 标记

<div class="media">
  <img class="media__image" src="runner.png"
    alt="Silhouette of a person running"><!-- 图片子元素 -->
  <div class="media__body"><!-- 正文子元素 -->
    <h4>Strength</h4><!-- 标题子元素 -->
    <p>
      Strength training is an important part of
      injury prevention. Focus on your core&mdash;
      especially your abs and glutes.
    </p>
  </div>
</div>

这是一个多功能模块,可以在各种尺寸的容器内生效,并随着容器宽度的变化进行自适应调整。正文也可以包含多个段落,或者使用不同尺寸大小的图片(可以考虑给图片设置一个 max-width 属性,以避免挤占正文区域)。

9.1.4.1 同时使用变体和子元素 Using variants and subelements together

我们也可以创建模块的变体形式。例如,可以很轻松地将图片从左浮动改为右浮动(如图 9.4 所示)。

图 9.4 把媒体模块的图片改到右侧渲染

【图 9.4 把媒体模块的图片改到右侧渲染】

变体样式类 media--right 就可以实现上述效果,将该类名添加到媒体模块的 div 主容器上(即 <div class="media media--right">)即可;然后通过类名选中图片并令其浮动方式为向右浮动。

将该修饰符样式类添加到 HTML 对应元素上后,再根据代码清单 9.10 更新 modules 模块图层中的样式,即可看到最终效果。

代码清单 9.10 为媒体模块定义一个向右浮动的变体形式

@layer modules {
  .media--right {
    flex-direction: row-reverse;
  }
}

上述代码将颠倒各弹性元素的排列顺序,将图片置于容器右侧。

在某些情况下,变体形式的模块样式可能涉及子元素样式的调整,此时可以通过后代选择器(descendant selectors)实现,例如 .media--right .media__image

9.1.4.2 避免在模块选择器中使用通用标签名 Avoiding generic tag names in module selectors

我们在媒体模块中使用了选择器 .media__body > h4 来选中标题元素,这样写是说得通的,因为 <h4> 标签本就是用来标识一个次级标题的。对于包含列表项的模块这种写法同样适用,相比为列表中的各列表项分别添加一个 menu__item 的样式类,通过 .menu > li 来进行匹配则要简单很多,尽管该写法尚存争议。

我们应该避免使用过于通用的 HTML 标签来选中目标元素,尤其是 div 元素和 span 元素。类似于 .page-header > span 的选择器太宽泛了。一开始创建模块的时候,可能只是用 span 标签实现了某一效果,但谁能保证后续不会出于其他考虑再加一个 span 呢?到最后才给 span 元素追加类名就比较棘手了,因为需要在 HTML 中找出所有用到过该模块的位置,并全部修改一遍。

P.S.:相信通过本节的学习,您也可以跟我一样,对目前市面上常见的 CSS 框架中的类名的写法有更为清晰的认识,比如 Bootstrap 中的很多前端组件样式,就是用的 BEM 命名约定:定义一个 success 按钮,其样式类就是一个 btn 模块,写作 btn btn-success;再比如,从阿里图标库定制某些图标效果时也会使用类似的声明方式。将这些具体场景和本篇知识点结合,就能更好地理解 CSS 模块化的用法与注意事项。



关于《CSS in Depth》(中译本书名《深入解析 CSS》)

第 1 版第 2 版
读者评分原版:4.7(亚马逊);中文版:9.3(豆瓣)原版:5.0(亚马逊);中文版:暂无,待出版
出版时间原版:2018 年 3 月;中文版:2020 年 4 月原版:2024 年 7 月;中文版:暂无,待出版
原价原版:$44.99;中文版:¥139.00原版:$59.99;中文版:暂无,待出版
现价原版:$36.49;中文版:¥52.54 起步原版:$52.09;中文版:暂无,待出版
原版国内预订起步价 ¥461.00起步价 ¥750.00

本专栏为该书第 2 版高分译文专栏,全网首发,精译精校,持续更新,计划今年内完成全书翻译,敬请期待!!!

目前已完结的章节(可进入本专栏查看详情,连载期间完全免费):

  • 第一章 层叠、优先级与继承(已完结)
    • 1.1 层叠
    • 1.2 继承
    • 1.3 特殊值
    • 1.4 简写属性
    • 1.5 CSS 渐进式增强技术
    • 1.6 本章小结
  • 第二章 相对单位(已完结)
    • 2.1 相对单位的威力
    • 2.2 em 与 rem
    • 2.3 告别像素思维
    • 2.4 视口的相对单位
    • 2.5 无单位的数值与行高
    • 2.6 自定义属性
    • 2.7 本章小结
  • 第三章 文档流与盒模型(已完结)
    • 3.1 常规文档流
    • 3.2 盒模型
    • 3.3 元素的高度
    • 3.4 负的外边距
    • 3.5 外边距折叠
    • 3.6 容器内的元素间距问题
    • 3.7 本章小结
  • 第四章 Flexbox 布局(已完结)
    • 4.1 Flexbox 布局原理
    • 4.2 弹性子元素的大小
    • 4.3 弹性布局的方向
    • 4.4 对齐、间距等细节处
    • 4.5 本章小结
  • 第五章 网格布局(已完结)
    • 5.1 构建基础网格
    • 5.2 网格结构剖析 (上)
      • 5.2.1 网格线的编号(下)
      • 5.2.2 网格与 Flexbox 配合(下)
    • 5.3 两种替代语法
      • 5.3.1 命名网格线
      • 5.3.2 命名网格区域
    • 5.4 显式网格与隐式网格(上)
      • 5.4.1 添加变化 (中)
      • 5.4.2 让网格元素填满网格轨道(下)
    • 5.5 子网格(全新增补内容)
    • 5.6 对齐相关的属性
    • 5.7 本章小结
  • 第六章 定位与堆叠上下文(已完结)
    • 6.1 固定定位
      • 6.1.1 创建一个固定定位的模态对话框
      • 6.1.2 在模态对话框打开时防止屏幕滚动
      • 6.1.3 控制定位元素的大小
    • 6.2 绝对定位
      • 6.2.1 关闭按钮的绝对定位
      • 6.2.2 伪元素的定位问题
    • 6.3 相对定位
      • 6.3.1 创建下拉菜单(上)
      • 6.3.2 创建 CSS 三角形(下)
    • 6.4 堆叠上下文与 z-index
      • 6.4.1 理解渲染过程与堆叠顺序(上)
      • 6.4.2 用 z-index 控制堆叠顺序(上)
      • 6.4.3 深入理解堆叠上下文(下)
    • 6.5 粘性定位
    • 6.6 本章小结
  • 第七章 响应式设计(已完结)
    • 7.1 移动端优先设计原则(上篇)
      • 7.1.1 创建移动端菜单(下篇)
      • 7.1.2 给视口添加 meta 标签(下篇)
    • 7.2 媒体查询(上篇)
      • 7.2.1 深入理解媒体查询的类型(上篇)
      • 7.2.2 页面断点的添加(中篇)
      • 7.2.3 响应式列的添加(下篇)
    • 7.3 流式布局
    • 7.4 响应式图片
    • 7.5 本章小结
  • 第八章 层叠图层及其嵌套
    • 8.1 用 layer 图层来操控层叠规则(上篇)
      • 8.1.1 图层的定义(上篇)
      • 8.1.2 图层的顺序与优先级(下篇)
      • 8.1.3 revert-layer 关键字(下篇)
    • 8.2 层叠图层的推荐组织方案
    • 8.3 伪类 :is() 和 :where() 的用法
    • 8.4 CSS 嵌套的使用
      • 8.4.1 嵌套选择器的使用
      • 8.4.2 深入理解嵌套选择器
      • 8.4.3 媒体查询及其他 @规则 的嵌套
    • 8.5 本章小结

  1. Nicole Sullivan 既是 媒体模块 概念的提出者之一,同时也是 OOCSS(Object-Oriented CSS)和 SMACSS(Scalable and Modular Architecture for CSS)两种 CSS 方法论的重要人物之一,媒体模块这个概念最早是 OOCSS 设计模式的一部分,旨在通过面向对象的方式组织 CSS 代码,强调结构和表现分而治之,以便于样式的复用与后期维护。 ↩︎


原文地址:https://blog.csdn.net/frgod/article/details/143857555

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!