CSS-架构教程-全-

CSS 架构教程(全)

原文:Architecting CSS

协议:CC BY-NC-SA 4.0

一、级联样式表

这本关于级联样式表(CSS)的书采用了与大多数书截然不同的方法。它不是试图教你如何设计网页,除了粗略的概述之外,也不是专注于教你如何使用 CSS。本章介绍了本书的重点,即如何(以及为什么)将 CSS 作为一种编程语言来对待。

分类

级联样式表(CSS)是一种允许将布局、主题和样式应用于文档的 web 技术。在大多数情况下,所讨论的文档是超文本标记语言(HTML)文件,并且由 web 浏览器执行呈现。

CSS 通常被视为一种设计工具,因为它允许网页的作者或设计者决定网页的视觉外观。因为它控制着网页的最终外观,CSS 对可用性和可访问性都有直接的影响。由于这些因素,创建样式表和编写 CSS 有时被认为是设计任务,而维护样式表的任务可能是软件团队中的设计师。

有趣的是,在 CSS 成为 Web 的主流样式语言之前,有许多其他的竞争提案。然而,

  • “CSS 有一个与众不同的特点:它考虑到在网络上,文档的风格不能由作者或读者自己设计,而是他们的愿望必须以某种方式结合或级联;事实上,不仅仅是读者和作者的愿望,还有显示设备和浏览器的功能。” 1

那么,CSS 的核心就是把控制权交到作者和读者手中。这使得它具有一定的交互性,并服从于网页读者的意愿,因为他们能够根据自己的喜好影响页面的最终外观。最常见的情况是,当作者的意图与最终用户的影响相遇,以创建一个独特的混合输出,这就是所谓的编程。所以这就引出了一个问题:到底什么是 CSS?写样式表应该算编程吗,写 CSS 的应该算程序员吗?

对于初学者来说,就像 JavaScript 和 Python 等流行的编程语言一样,CSS 是一种语言。如“结构”一节所示,CSS 有一个必须遵循的特定语法,您编写的规则导致操作被执行。此外,万维网联盟(W3C)将 CSS 称为一种语言。 2

衡量一门编程语言的一个标准是看它是否是图灵完备的。跳过形式定义,图灵完全语言的简单解释是可以解决任何任意计算。请注意,这不是一个严格的要求,有一些非常有用的编程语言并不是图灵完整的,最著名的是结构化查询语言(SQL)和正则表达式(RegEx)。然而,如果一种语言可以被证明是图灵完备的,那么它将消除所有关于其分类的疑问。CSS + HTML 的组合已经得到了被归类为图灵完备的必要的形式证明。 3

这意味着 CSS + HTML 符合任何通用编程语言的要求,写 CSS 和 HTML 算编程。这意味着你绝对是一名程序员(或者 web 开发人员,如果你喜欢的话)。

语言特征

尽管 CSS 被归类为一种编程语言,我们可能会同意使用 CSS + HTML 来完成一般的编程任务并不特别方便。这是因为这真的不是 CSS(或 HTML)的重点。

不管怎样,这种语言有许多有趣的特性与更传统的编程语言相似,包括

  • 变量

  • 功能

  • 计算

  • 进口

  • 范围

  • 评论

  • 多态性

当利用 CSS 预编译器时,您可以获得更多的编程语言特性,例如

  • 混入类

  • 延长

  • 命名空间

  • 列表和映射数据结构

  • 数学表达式

参见第章第二部分更深入地探究 CSS 语言特性,参见第章第七部分更多地了解 CSS 预编译器。

结构

值得注意的是,CSS 是一种声明性语言,而不是命令性语言。这意味着,我们不是编写代码来告诉 web 浏览器如何将样式应用于页面,而是告诉浏览器应用什么样式以及在哪里应用它们。这些声明在规范中被称为规则集,但也可以简称为规则

CSS 中的每个规则都由一个或多个选择器和一个或多个声明组成,如图 1-1 所示。

img/487079_1_En_1_Fig1_HTML.png

图 1-1

CSS 规则集

每个声明都由一个属性值对组成。在撰写本文时,CSS 工作组列出了 564 个可能的属性。每个属性都必须得到用户代理(通常是 web 浏览器)的支持才能生效。不支持的属性会被忽略。

规则集可以通过 at-rules 如@media@supports进一步分组和修改,并被收集到样式表中。样式表只是一个带有. css 扩展名的文本文件,它包含许多描述文档或网页表示的规则。

软件体系结构

一旦我们承认 CSS 拥有编程语言的所有复杂性,我们就需要接受这样的暗示:我们必须像对待代码一样对待样式表。这意味着我们可以利用软件架构的许多原则、最佳实践和设计模式,并在编写 CSS 时应用它们。

Note

您可能会发现术语软件架构与术语软件设计可以互换使用。这在行业内很常见,这两个术语指的是相同的高级设计思想和过程方法。由于 CSS 经常用于视觉设计,我们在本书中选择了术语架构来避免这些概念之间的混淆。

软件架构着眼于系统的结构和组件,并权衡各种可能的组合和方法的利弊。各种系统和方法的优点、缺点和局限性都应该考虑。架构师的方法更像是高层次的鸟瞰图,而不是开发人员的(尽管通常是同一个人做这两项工作)。

例如,如果您想让一个图像在单击按钮时在页面上移动,您将如何实现该功能?你会使用 CSS 还是 JavaScript?你会使用<img>元素、可缩放矢量图形(SVG)还是画布?哪个会产生最流畅的视觉动画?当需求改变时,哪种方法最容易维护?这些是软件架构试图评估的问题类型。

做这些决定时,你不必从头开始。有一些公认的软件架构原则和最佳实践,可以指导您在 CSS 方面做出更具战略性的决策。

关注点分离

术语关注点分离被认为是埃德格·迪克斯特拉 4 的功劳,指的是一次专注于问题的一个方面非常有帮助的想法。正如后面的“web 架构”一节所显示的,Web 应用程序将内容、风格和动作分开,甚至对每一个关注点使用不同的技术。

看看与 CSS 相关的关注点分离,我们可能会在规则集中发现哪些关注点?如图 1-2 所示,我们看到布局、主题、版式和交互都是可以用 CSS 控制的网页的各个方面。见图 1-2 。

img/487079_1_En_1_Fig2_HTML.png

图 1-2

CSS 关注的领域

现在,假设您有一个包含 20,000 个规则集的样式表。这显然是难以管理的,这些规则集需要被分割到多个文件中。如何确定需要多少个文件以及每个文件包含哪些规则集?一种方法是基于关注点(例如,布局与主题)分割文件,而另一种方法是基于规则集所应用的特定组件对规则集进行分组。这个问题对于第十章中不同 CSS 架构模型的讨论来说非常重要。

软件架构的两个最广泛接受的原则,内聚和耦合,用来更好地定义关注点分离的概念。这些度量标准首次发表在结构化设计 5 中,并从此成为软件工程中的标准。

内聚力

凝聚力可以被描述为一种责任的度量。它是对给定代码单元所负责的不同任务或效果的广度以及这些任务或效果之间的关系性质的定性度量。传统上,衔接有七个等级,从巧合(最差)到功能(最好)。

另一个与凝聚力相关的流行原则是单一责任原则 (SRP)。这个想法是每个功能和模块应该只有一个职责。由此衍生出两个重要目标:

  1. 缺少副作用 如果一个函数只做一件事,那么它的使用几乎没有副作用或意想不到的后果的风险。

  2. 改变的理由只有一个 每次代码改变,都会增加引入错误和 bug 的风险。如果我们减少变更的数量,我们就能减少风险。此外,这有助于避免系统范围变化的副作用。

内聚和单一责任的目标都是促进简单性和减少风险,这是我们所有架构决策的重要目标。

耦合

耦合描述了两个或多个代码单元之间的相互依赖。松散耦合与良好的内聚性相关联,通常描述具有良好可重用性的模块,该模块可以独立于其他模块进行更新,对整个系统的影响最小。这是健壮和灵活系统的一个重要属性。

紧密耦合与较差的内聚性相关联,并且描述了难以独立测试或修改的模块。这种模块通常不能自由重用,并且在改变时可能需要更大的测试工作量。尽可能支持松散耦合。

当构建 web 应用程序时,我们会发现减少内容和设计之间的耦合有很多价值。理想情况下,我们应该能够创建适用于各种内容的样式表,而无需调整。当我们做到这一点时,我们可以说我们有正交性。

正交性

虽然在讨论系统设计时,正交性是一个重要且常见的术语,但近年来,这个词已经积累了一些不利因素。这可能是由于误用和不良定义的结合,导致它有时被描述为“技术术语” 6 然而,正交性是一个重要的概念,它与 7 的内聚和耦合直接相关,并且它将成为我们在本书后面讨论的许多决策的因素。

正交性描述的是一种合作而非相互依赖的关系,其中两件事情朝着共同的目标一起工作,同时保持一定程度的独立性。

在数学中,两个向量的正交性的最简单形式是当它们彼此垂直时,这意味着它们形成直角并且只相交一次。正交性也可以被描述为统计独立性,意味着两个(或更多)因素的变化不会相互影响

在计算机软件中,我们使用正交性来描述两个模块或组件之间的关系,这两个模块或组件能够彼此独立地改变。例如,如果我们能够编辑一个 HTML 文件来更改页面的内容和/或结构,而不对 CSS 进行相应的更改,则可以认为 HTML 页面与其 CSS 正交,但页面的视觉设计在更改后仍不受影响。

事实上,文档布局和结构之间的这种关注点分离是 CSS 背后最初的设计考虑之一。

非技术因素

为了实现关注点的分离,我们必须首先练习将复杂且具有挑战性的问题分解成简单部分的艺术。我们经常发现,看似不可能的任务其实是许多简单任务的大量积累。在学习看到单个部分的过程中,我们现在有了创建解决方案所需的构件。

除了软件架构的技术方面,我们的决策中还必须考虑一些实际的因素。

维护成本

人们很容易接受这样的观点,即今天最便宜的东西就是最好的财务决策;然而,软件产品的真实拥有成本必须包括计算中的持续维护成本。通常,建造成本最低的东西可能维护成本最高。也许我们可以购买现有的第三方库或模板,并从它们那里获取更新,而不是自己构建和维护?

程序调试时间

我们经常在紧迫的期限内工作,不断努力为客户创造价值,为公司创造收入。架构决策的总时间和工作量是一个重要的决策点,因为它可能会影响成本和时间表。有时候值得一开始就尝试一种新方法,随着时间的推移,这种方法会变得更快。其他时候,我们需要承认使用我们已经熟悉的东西是最好的选择。但是一定要考虑到开发时间是非常昂贵的,所以有时一个看起来微不足道的决定(在开发期间减少 10 秒的页面重新加载时间)可能会在以后产生回报(10 秒 x 每天 100 次 x 260 个工作日 x 5 个开发人员=每年节省 15 天)。

开发者满意度

虽然我们决策的技术和财务影响相对直接,但决策对士气的影响同样重要,而且容易被忽略。因此,当在 CSS、Sass 和 Less 之间做出决定或者选择下一个 CSS 框架时,团队的态度和认同是一个重要的考虑因素。有时,摩擦可能是通常对变革的阻力或变革的速度;有时这是一个合理的担忧,即决策不是最适合产品或团队的。然而其他时候,这是因为开发人员没有感觉到决策在帮助他们建立有用的技能。认真对待这些问题,因为士气会影响绩效、生活质量和人员流动。

最佳实践

重要的是要认识到,对架构的研究围绕着为常见问题定义解决方案模式,但也没有绝对的答案。没有一种方法永远是正确的,也没有一个决定在所有情况下都行得通。实践架构就是了解你的可用选项,权衡每个选项的积极和消极结果,然后做出决定。记录这些决定,以及进入这些决定的推理,是作为一个架构师的另一个重要部分。至关重要的是,我们和其他人都可以从成功和失败中吸取教训。

有一系列实践通常是决策制定中的良好默认。这并不是说它们总是正确的答案,而是在没有任何令人信服的相反理由的情况下使用它们通常会产生好的结果。

不要重复你自己

通常被称为 DRY,不要重复自己表明重复可能是一种反模式。当一段代码在一个项目中重复十次时,这意味着我们必须在这段代码发生变化时更新十个地方。如果我们在未来的更新中只更新其中的八个地方,我们可能会发现难以诊断的错误在我们认为它们已经被修复后仍然存在。

CSS 也是如此——随着时间的推移,重复相同的规则集和声明会导致额外的维护工作和外观上的不一致。

有许多机制可以减少样式表中的重复,包括级联、继承、变量和混合。

奥卡姆剃刀

奥卡姆的逻辑剃刀是:“不要在没有必要的情况下增加实体!”。虽然奥卡姆从未写下这些确切的词语,但这一原则来自他在解决问题时的工作,使其与编程环境相关。奥卡姆剃刀原理更广为人知的说法可能是“最简单的工作方案可能是最好的方案。”

Note

逻辑剃刀是一种理性的原则,用来刮掉对给定现象可能但不现实或不太可能的解释。 9

简单性为我们的代码提供了巨大的价值。它可以让代码更容易调试,更容易阅读,也让新队友更容易上手。此外,这提供了一个排除任何外部因素的极好的缺省值——我们能想到的最简单的解决方案应该足以应对许多情况。

你不需要它

有时被称为 YAGNI,你不会需要它的原则是,我们通常应该避免在代码中添加任何我们没有特定需求的东西。一般来说,我们应该尽可能保持代码简单,以避免过早优化,除非有令人信服的理由。通常,这甚至意味着忽略 DRY 原则,直到我们知道我们将需要三到四次或更多的代码,因为最小化重复的成本对于仅仅两到三个案例来说可能太昂贵了。

向他人学习

使用现有的架构模式和方法,比如在第 10 章中介绍的那些。使用谷歌找到其他有类似挑战的人,并向他们学习。利用社交媒体获得同事的帮助。

网络架构

如前所述,web 页面通常由文档(HTML)、样式表(CSS)和可能的脚本(JavaScript)组成,所有这些都通过用户代理(web 浏览器)提供给最终用户。web 浏览器执行许多活动来从这些组件构建网页。Mozilla Firefox 模型如图 1-3 所示。

img/487079_1_En_1_Fig3_HTML.jpg

图 1-3

浏览器引擎 10

所有的源文件都必须从 web 服务器上获取,然后文本必须根据其类型进行解析。HTML 和 JavaScript 结合起来构建和操作文档对象模型(DOM ),这将在下文中更详细地描述。样式引擎将 DOM 和 CSS 结合起来生成布局,包括任何媒体文件,如图像或视频。但是,即使这个布局也只是一个非可视的模型,必须使用绘制和合成步骤将其呈现在屏幕上。

虽然没有必要完全理解浏览器进行的所有活动,但 HTML 和 CSS 之间的关系是本书特别感兴趣的。因为我们已经解释了 CSS,所以在接下来的章节中提供了 HTML 和 DOM 的概述。

超文本标记语言

为了让 CSS 在 web 环境中工作,必须从 HTML(超文本标记语言)文档中引用所需的样式或样式表。有三种可能的选择,但是大多数情况下最好的方法是链接到一个外部样式表文件,如清单 1-1 所示。

<!DOCTYPE>
<html>
<head>
  <title>Linked Style Sheet</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <p>Sample HTML</p>
</body>
</html>

Listing 1-1Link to External Style Sheet

在一些罕见的情况下,HTML 是独立的,并且在一个文件中包含所有的样式信息,这可能是必要的或可取的。这可以通过使用清单 1-2 中所示的样式标签来实现。

<!DOCTYPE>
<html>
<head>
  <title>Embedded Style Sheet</title>
  <style>
    p { font-weight: bold; }
  </style>
</head>
<body>
  <p>Sample HTML (in bold)</p>
</body>
</html>

Listing 1-2Self-Contained Styles

最后一种方法是在 HTML 标签中直接内联包含样式,如清单 1-3 所示。使用该方法在功能上等同于直接使用 JavaScript 设置元素样式。

<!DOCTYPE>
<html>
<head>
  <title>Inline Styles</title>
</head>
<body>
  <p style="color: red">Sample HTML (in red)</p>
</body>
</html>

Listing 1-3Inline Styles

有大量的 CSS 特性是使用内联样式无法实现的,包括大多数 at-rules。此外,这“打破”了级联和继承,这在第三章中有更深入的描述。

HTML 文档必须指定自己的样式表,这一事实意味着文档与样式表之间存在权威关系。样式表不能指定它属于什么样的文档,但是它可以指定选择器和条件来决定规则应用于文档的情况,这个概念将在后面的章节中详细阐述。

由于 CSS 和 HTML 之间的关系,理解图 1-4 中概述的 HTML 文档的结构和词汇很重要。

img/487079_1_En_1_Fig4_HTML.png

图 1-4

HTML 结构

如前所述,HTML 由尖括号分隔的标签组成。HTML 元素是指标签的全部内容,从开始标签的第一个尖括号到结束标签的最后一个尖括号。有些元素,比如<img>,没有主体,因此没有结束标签。一些元素,如<div><button>,可能在它们的开始和结束标签之间包含文本甚至其他标签。所有标签都可能有属性,如IDclasstitle。一些标签具有强制属性,这些属性被认为是有效的。

标签、属性和值都有 CSS 选择器,这将在第二章中详细介绍。这里值得注意的一点是,一些 HTML 标签的存在主要是为了提供语义上下文。

Note

语义学是语言学和逻辑学中有关意义的分支。当应用于代码(包括 HTML)时,我们使用“语义”一词来表示一个单词或标签,它传达的意义或目的不仅仅是一个简单的标签。

例如,<div>可以用来对任意一组标签进行分组,但这只是一个普通的划分或分组。<nav>标签表示导航,<article>表示独立自足的内容,<aside>表示相关但次要的内容,很像前面的注释。这个额外的含义对用户代理和屏幕阅读器很有帮助,但也可以在我们的 CSS 中使用,以编写更健壮和更有意义的选择器。

文档对象模型

文档对象模型(DOM)是由用户代理构建的关系树,它从一个或多个来源(包括 HTML、JavaScript 和 CSS)描述整个文档。DOM 规范包括一个用于 JavaScript 访问和操作的 API,每个 HTML 元素和属性都映射到 DOM 上,如图 1-5 所示。

img/487079_1_En_1_Fig5_HTML.png

图 1-5

文档对象模型

DOM 中的每一项都称为一个节点。节点可以是元素、属性或文本,反映了底层的 HTML。和 HTML 一样,元素也有属性。所有这些都可以使用动态网页的 JavaScript 直接读取和修改。

注意 CSS 并不直接影响 DOM。然而,通过直接修改classstyle属性,可以使用 DOM 来改变可视化输出。回头看图 1-5 ,我们看到 DOM 提供了样式引擎在生成布局时应用 CSS 的结构。

CSS 的历史

| 十月浏览器:万维网,蒂姆 berners-Lee–第一款网络浏览器 | One thousand nine hundred and ninety |   | |   | One thousand nine hundred and ninety-three | 1 月 23 日浏览器:Mosaic——第一个显示内嵌图像和文本的浏览器 | | 10 月 1 日万维网联盟(W3C)成立十二月十五日浏览器:网景浏览器 1 | One thousand nine hundred and ninety-four | 十月十日由 kon Wium Lie 首先提出的 CSS | | 8 月 16 日浏览器:Internet Explorer 1.0 | One thousand nine hundred and ninety-five | 4 月 10 日浏览器:Opera 1 | |   | One thousand nine hundred and ninety-six | 十二月十七日级联样式表,级别 1 (CSS1)推荐,W3C | | 5 月 12 日一份名单,杰弗里·泽尔德曼 | One thousand nine hundred and ninety-seven |   | | 八月网络标准项目(WaSP) | One thousand nine hundred and ninety-eight | 5 月 12 日级联样式表,级别 2 (CSS2)W3C 推荐标准 | |   | One thousand nine hundred and ninety-nine | 6 月 22 日前 3 个 CSS3 草稿:颜色配置文件、多栏布局和分页媒体 | | 4 月 14 日 CSS3 简介,W3C 工作草案 | Two thousand |   | |   | Two thousand and two | 十月十日连线新闻 CSS 重新设计 11 | | 1 月 7 日浏览器:Safari 15 月 7 日 CSS 禅宗花园,戴夫谢伊 | Two thousand and three | 二月 ESPN CSS 改版 12 | |   | Two thousand and four | 11 月 9 日浏览器:火狐 1.0 | | 十月 Sass CSS 预处理程序 | Two thousand and five |   | |   | Two thousand and seven | 7 月 4 日 CSS-Tricks,Chris Coyier | | 十二月十一日浏览器:谷歌浏览器 1.0 | Two thousand and eight |   | |   | Two thousand and nine | 八月较少的 CSS 预处理程序 | | 四月 caniuse.com | Two thousand and ten |   | |   | Two thousand and eleven | 6 月 7 日级联样式表,级别 2 修订版 1 (CSS 2.1),W3C 建议 | | 6 月 19 日媒体查询,W3C 推荐13 | Two thousand and twelve |   | |   | Two thousand and thirteen | 11 月 7 日样式属性 W3C 推荐14 | | 3 月 20 日 CSS 形状模块级别 1W3C 候选人推荐 15 | Two thousand and fourteen |   | |   | Two thousand and fifteen | 12 月 3 日级联变量的 CSS 自定义属性 W3C 候选人推荐 16 | | 3 月 1 日 CSS 灵活框布局级别 1,W3C 候选人推荐 17 | Two thousand and sixteen | 9 月 29 日 CSS 网格布局模块级别 1,W3C 候选人推荐 18 | | 9 月 25 日滚动条模块级别 1,W3C 首次公开草案 19 | Two thousand and eighteen | 11 月 6 日选择器级别 3,W3C 推荐20 | | 十一月二十四日书写模式级别 3W3C 提出建议 21 | Two thousand and nineteen |   |

CSS 的创建

影响网页的视觉风格是一个众所周知的问题。最初,一些基本的可视化控件被内置到 HTML 中,但是这种方法的问题很快就被发现了。正如本章开始时提到的,许多人提出,甚至实现了设计网页样式的机制,在此期间,kon Wium Lie 提出了 CSS 的想法。他和伯特·波斯一起制定了一个提案,并提交给了新成立的 W3C。

Lie 和 Bos 与克里斯·威尔逊和 Vidur Apparao 一起成立了第一个 W3C CSS 工作组,Chris Lilley 是第一任工作组主席。CSS 级推荐标准在两年后的 1996 年发布。

håkon wium lie 博士

1994 年,kon Wium Lie 加入了欧洲粒子物理研究所的万维网项目,在那里他加入了网络先驱蒂姆·伯纳斯·李和罗伯特·卡里奥。在第一年,他利用他在麻省理工学院媒体实验室的电子出版背景,提出了 CSS 的建议。第二年,他去了 W3C 的 CSS 工作组工作。

Lie 在 1999 年成为 Opera Software 的 CTO,Opera Software 是当时对 CSS 最友好的浏览器。他继续担任这一职务,直到 2016 年公司被出售。

-你好。伯特·博斯

当 kon Wium Lie 在做他的 CSS 提案时,伯特·波斯正在制作他自己的基于流的样式表提案。他审阅了 CSS 的初始方案,他和 Lie 确定这两个方案可以合并。在 1995 年万维网项目从 CERN 转移出来期间,Bos 受雇于新成立的 W3C,在那里他继续与 Lie 一起研究 CSS1 规范。

Bos 仍然是 CSS 工作组的活跃成员,之前曾担任该工作组的主席。他和 Lie 一起写了层叠样式表:为网络设计,这是关于 CSS 的最早的书籍之一。

克里斯·莉莉

Chris Lilley 作为从事 HTML 2.0 和 PNG 图形格式工作的互联网工程任务组(IETF)的成员,开始建立 web 标准。他于 1996 年加入 W3C,最初从事图形和字体方面的工作,主持一个 Web 字体工作组。一年后,当 CSS 工作组成立时,他成为了这个工作组的主席。第二年,他开始担任 W3C 可缩放矢量图形(SVG)工作组主席,为期 10 年。多年来,Lilley 撰写和编辑了大量的 web 和图形规范,以及相关书籍。

克里斯·威尔逊

克里斯·威尔逊是第一个 CSS 工作组的创始人之一,他被 kon Wium Lie 誉为在规范完成之前就将 CSS 添加到 Internet Explorer 第 3 版的程序员。从那以后,他一直活跃在 W3C 中,他担任的职务包括 Web 平台孵化小组主席、HTML 工作组主席和顾问委员会成员。他在 2009 年之前一直为微软开发 Internet Explorer,2010 年他加入了谷歌,在那里他负责 Chrome,特别是增强和虚拟现实功能。

真空出现了

Vidur Apparao 在 Netscape 担任首席架构师时加入了最初的 CSS 工作组,当时他正在开发 Gecko 布局引擎。除了在 CSS 组的工作之外,他还参与了文档对象模型的推荐工作。在做了十多年的 web 架构师之后,Apparao 继续他的云软件高管生涯。

早期采用

在 CSS 成为众所周知的成熟技术之前,一些早期的网站需要冒险用内嵌样式更新他们的旧 HTML3 网站,使其布局和主题更加“纯粹”。

这些非常公开的网站迁移的第一个是有线新闻。2002 年 10 月 11 日,Wired News 宣布了他们的重新设计,包括符合 web 标准和技术,包括 XHTML 和全 CSS 布局。埃里克·迈耶说过:

  • 这种新设计在 Web 服务器本身上更容易访问、下载更快、更灵活、更容易。任何对网络未来感兴趣的人只需要看看这个就够了。 24

当《连线》杂志发布他们的重大公告时,ESPN 的另一个团队正在努力开发他们的新网站。仅仅 4 个月后宣布,他们的巨大胜利是(几乎)无表布局。 25 通过证明使用基于 CSS(而不是基于表格)的布局来构建网站可以为月浏览量达到数百万的网站服务, 26 这两个网站帮助巩固了 CSS 作为强大的网络标准的地位。

早期倡导者

如果没有早期的倡导者来通知和教育 web 开发人员,我们今天可能会有一个非常不同的 Web。大量的开发人员,包括本书的作者,都受到了这些倡导者的教育和启发,本书并不是孤立的,而是建立在他们多年工作的基础上。

名单之外

web 和 CSS 教育和宣传的第一次重大努力是从电子邮件列表开始的。杰弗里·泽尔德曼于 1997 年创立了一个名为“与众不同”的组织,埃里克·迈耶很快加入其中。这个早期的邮件列表已经发展成为一个完整的生态系统,包括书籍和会议,直到今天仍然具有影响力。

泽尔德曼

杰弗里·泽尔德曼在 1995 年开始了他的网页设计生涯,此前他从事了十年的广告文案工作。在创立 List Apart 的第二年,他与乔治·奥尔森和格伦·戴维斯共同创立了 web 标准项目(WaSP ),开始了对开放 Web 标准的长期推动。泽尔德曼于 2012 年入选 SXSW 交互名人堂,是第一个获此殊荣的人。 27

埃里克·迈尔

Meyer 与 kon Wium Lie 和 Tim Boland 一起开发了 CSS1 的首个测试套件,旨在帮助评估是否符合该标准。同年,他加入 WaSP,并共同创建了 CSS 行动委员会。Meyer 已经写了六本关于 CSS 的书,并且为一些最有影响力的网页设计出版物写了无数的文章,包括一个列表。他还创建了 CSS-discuse 邮件列表,并与杰弗里·泽尔德曼共同创建了一个活动。2006 年,Meyer 因其在 HTML 和 CSS 方面的国际工作而入选国际数字艺术与科学学院。

CSS 禅宗花园

2003 年,一个神奇的新网站启动了,展示了 CSS 的力量。CSS Zen Garden 有一个独特的方法它提供了一个固定的 HTML 文档,设计者被鼓励使用 CSS(和图片)来设计他们想要的风格和主题。通过阻止对 HTML 的编辑,网页设计者被迫分离他们的设计实现,结果是不可思议的。最初的几个主题是由网站作者 Dave Shea 提供的,但是很快全世界的设计师都提交了他们的主题供考虑。这提供了一个强大的动手实验室,一劳永逸地证明了 CSS 作为网络生态系统的一等公民的地位。

2005 年,Dave 与 Molly Holzschlag 合作出版了一本书,书名为CSS 设计之禅,这本书卖出了 70,000 多册,成为网页设计的国际标准。 28

戴夫·谢伊

在推出 CSS Zen Garden 之前不久,Dave Shea 开始了一个关于网页设计的博客,名为 mezzoblue 。在接下来的几年里,他成为了一名多产的博客作者,就广泛的话题提供了有价值的见解。Shea 在 Web 标准项目中很活跃,也为一个独立的列表写文章。 29

茉莉木版

当欧洲粒子物理研究所构思网络时,莫莉·霍尔茨施拉格开始了她在互联网技术方面的职业生涯。1996 年,她出版了第一本关于网页设计的书,之后又写了超过 35 本关于网页技术和设计的书。她被广泛认为是网络上最有影响力的女性之一。

Holzschlag 直接与 CERN、AOL、微软、BBC、易贝、Opera 和 Netscape 合作,确保浏览器支持现代标准。她是 WaSP 的项目负责人,W3C CSS Accessibility Community Group 的主席,也是国际化指南、教育和推广工作组以及 HTML 工作组的 W3C 特邀专家。 三十

CSS 技巧

阅读这本书的人不太可能不通过 Chris Coyier 创建的 CSS-Tricks 网站来寻找 CSS 的答案。十多年来,这个网站一直在分享关于 CSS 和其他 web 开发主题的实用技巧和诀窍。

克里斯科伊耶

2007 年,克里斯·科伊尔创建了 CSS-Tricks,作为一个关于 CSS 的个人博客。今天,网站上有大量 web 开发人员和设计人员的文章,包括本章列出的许多人。与蒂姆·萨巴特和亚历克斯·瓦兹奎一起,科伊尔创立了 CodePen,这是一个非常受欢迎的在线代码编辑器和共享平台。 31

今日 CSS

W3C 的 CSS 工作组在出色的领导下仍然很强大。当前 CSS level 3 的模块化方法,以及常青浏览器的新趋势,导致了稳步的进步。以下是一些活跃的有影响力的人,除了已经提到的那些人之外,他们正在继续改善 CSS 的状况。

雷切尔·安德鲁

Rachel Andrew 是 20 多本关于 web 开发书籍的作者。她是 WaSP 的成员,也是 W3C CSS 工作组的特邀专家。她是一名谷歌开发专家,一份独立列表的撰稿人,以及 Smashing 杂志的主编。 32

珍·西蒙斯

Jen Simmons 是 Mozilla 的一名设计师和倡导者,她负责 Firefox,特别是 Grid Inspector。她曾在许多会议上发言,包括一个单独的事件和 SXSW。Simmons 是 W3C CSS 工作组的活跃成员,她在 CSS 网格布局的设计和部署方面影响极大。自 1998 年以来,她一直是一名活跃的 web 开发人员,她的客户包括 CERN、W3C 和 Google。 33

妮可·沙利文

妮可·沙利文是一个受欢迎的演讲者,她的会议露面包括一个事件除了和 SXSW。她合著了两本关于 web 性能的书,并且是 CSS 和 web 标准的倡导者。Sullivan 开始了面向对象的 CSS (OOCSS)项目,它为 CSS 提供了一个架构框架。她还和 Nicholas Zakas 一起创建了 CSS Lint,一个帮助捕捉常见 CSS 错误的工具。 34

米丽娅姆·苏珊娜

Miriam Suzanne 是一名项目经理、用户体验设计师和前端架构师。作为一名多才多艺的作家和小说家,她撰写了《Jump Start Sass》一书,并且是 CSS Tricks 的专职撰稿人。Suzanne 是 Sass 核心团队的成员,是流行的开源工具(包括 Susy、True 和 Herman)的创建者。她是 W3C CSS 工作组的特邀专家,也是 Mozilla Developer channel 的教师,为 web 专业人员制作资源,包括工具、视频、文章和演示。Suzanne 是一名国际会议演讲者,2017 年她在 CSS Dev 大会上获得了“最佳”演讲者奖。 35

摘要

在这一章中,你已经了解了 CSS 的历史,它是如何发展成为一种编程语言的,以及 CSS 如何适应网页的构建。具体来说,您已经了解了:

  • CSS 规则集各部分的名称

  • CSS 是一种编程语言,为什么这很重要

  • 用户代理(如 web 浏览器)如何将 CSS 应用于网页

在下一章中,你将回顾 CSS 语言的基本特性,特别关注高级的和不常用的语言特性。

二、规则和选择器

虽然你可能已经熟悉了 CSS 的基础知识,但是这一章提供了一个在你做架构决策时可以使用的语言特性的快速概述。软件架构的一个重要部分是对工具和方法的深刻理解,这些工具和方法可用来完成各种任务,以实现我们的系统目标。

选择器

正如我们在第一章中看到的,选择器是 CSS 规则集的一部分,它决定了哪些元素应用了样式声明。熟练地使用选择器有助于分离 HTML 和 CSS,从而创建健壮且风格一致的网站和 web 应用程序。

基础

基本选择器允许根据元素在 HTML 中呈现的明显特征来选择元素:标记名、属性和类名。CSS 选择器语法非常有表现力,以至于有一个 DOM 函数querySelector接受 CSS 选择器字符串来定位 DOM 树中的元素。参见第八章了解更多关于 JavaScript 的内容。

通用选择器

CSS 中的*是匹配页面上每个元素的通用选择器。有时这可能是有帮助的,例如清单 2-1 和图 2-1 ,它为用键盘选择的任何元素添加了一个可视指示器。

img/487079_1_En_2_Fig1_HTML.jpg

图 2-1

通用选择器

*:focus {
  outline: 1px dotted grey;
}

Listing 2-1Outline Selected Elements

然而,这种便利是有代价的,通用选择器有效地缩短了关联声明的继承。

除非您有特定的用例,否则通常最好避免通用选择器,而支持继承。将通用选择器与其他选择器结合使用也是一个好主意。

通用选择器的一个用例是将声明应用于另一个元素的所有子元素,即使该属性不会被继承,例如 border。对于这个用例,可以考虑将定制属性或混合作为这种方法的替代。

类型选择器

在 CSS 中选择一个元素就像使用标记名一样简单。这叫做类型选择器,所有的 HTML 标签都是有效的选择器。

清单 2-2 和图 2-2 中的示例为所有段落元素添加了填充。

img/487079_1_En_2_Fig2_HTML.jpg

图 2-2

类型选择器

p {
  padding: 0.5rem;
}

Listing 2-2Add Padding to Selected Elements

类别选择器

要按类选择元素,只需使用一个点,后跟类名,如.example。因为一个 HTML 元素可以有多个类,所以可以组合多个类选择器,它们也可以与元素选择器组合。参见列表 2-3 和 2-4 。输出如图 2-3 所示。

img/487079_1_En_2_Fig3_HTML.jpg

图 2-3

类别选择器

img {
  width: 200px;
}
button {
  background-color: lightblue;
}
button.outline {
  border: 1px solid green;
}
button.outline.bold {
  border: 5px solid darkgreen;
}

Listing 2-4Class Selector CSS

<body>
  <div>
    <p>Lorem Ipsum...</p>
    <img class="outline" src="image.png" alt="art">
    <button class="outline">Cancel</button>
    <button id="ok" class="outline bold">OK</button>
  </div>
</body>

Listing 2-3Selector HTML

在这个例子中,由于类型选择器的原因,<img>元素没有接收到边框。“确定”按钮的边框比“取消”按钮更粗、更深。

ID 选择器

ID 选择器#根据元素的ID属性选择元素。请注意,在单个页面上使用重复的 id 对于 HTML 是无效的,因此该选择器应该匹配 0 或 1 个元素。清单 2-5 中的例子使用了清单 2-3 中上一个例子的 HTML。图 2-4 显示了输出。

img/487079_1_En_2_Fig4_HTML.jpg

图 2-4

ID 选择器

img {
  width: 200px;
}

#ok {
  font-size: 1.5rem;
  font-weight: bold;
}
p#ok {
  color: pink;
}

Listing 2-5ID Selector CSS

这将使“确定”按钮上的按钮文本加粗。这个例子显示了 ID 选择器可以像类选择器一样与类型选择器组合。在这个例子中,通过以不匹配 HTML 的方式组合这些选择器,段落标记的内容不会变成粉红色。

属性选择器

属性选择器根据元素的一个属性匹配元素。这个选择器使用方括号来包含属性匹配,并且可以选择与类型选择器结合使用。例如,a [rel]可以用来匹配所有具有给定关系的锚标签。为了允许<area>标签也匹配,单独使用[rel]

除了测试属性的存在,这个选择器还可以测试特定的值,如清单 2-6 和 2-7 以及图 2-5 所示。

img/487079_1_En_2_Fig5_HTML.jpg

图 2-5

属性选择器

label, input, a, button {
  display: block;
  margin-bottom: 1rem;
}
button {
  display: flex;
  align-items: center;
}

/* Matches password input fields */
input[type="password"] {
  color: red;
}

/* Strikes out any quotes cited from Wikipedia */
blockquote[cite*="wikipedia.org"] {
  text-decoration: line-through;
}

/* Underlines any element with a title attribute containing
   the word "continue" with any Capitalization.
*/
[title*="continue"] i {
  text-decoration: underline;
}

/* Display a gray border around any input which has an
   accept starting with image, such as image/png
*/
input[accept^="image"] {
  border: solid 4px gray;
}

/* Display a PDF icon beside any .pdf download links */
a[href$=".pdf"]::before {
  content: url(icon-pdf.png);
}

/* Matches a material design icon such as <i class="material-icons">arrow_back_ios</i> */
i[class|="material-icons"] {
  color: blue;
  width: 32px;
}

Listing 2-7Attribute Selector CSS

<body>
  <h1>Attribute Selector</h1>
  <form>
    <button href="" title="go back">
      <i class="material-icons">arrow_back_ios</i>
      Previous
    </button>
    <label>
      Username
      <input type="text" >
    </label>
    <label>
      Password
      <input type="password" >
    </label>
    <label>
      Avatar
      <input type="file" accept="image/png">
    </label>
    <button href="" title="Continue">
    Next
    <i class="material-icons">arrow_forward_ios</i>
    </button>
  </form>
  <blockquote cite="w3.org">
    The World Wide Web Consortium (W3C) is an...
  </blockquote>
  <blockquote cite="https://en.wikipedia.org/wiki/Wikipedia">
    Wikipedia is a multilingual online encyclopedia...
  </blockquote>
  <a href="myfile.pdf" download>PDF File</a>
  <a href="myfile.docx" download>Word Doc</a>
</body>

Listing 2-6Attribute Selector HTML

因为classID都是 HTML 属性,所以类和 ID 选择器具有等同的属性选择器,如表 2-1 所示。

表 2-1

属性选择器等效项

|   |

基本选择器

|

属性选择器

|
| --- | --- | --- |
| 按 ID 选择 | #contactForm | [id=contactForm] |
| 按类别选择 | .outline | [class~="outline"] |

Grouping

为了尽量减少声明块的重复,可以将选择器组合到一个逗号分隔的列表中。例如,a, button { ... }将声明块应用于 HTML 中的锚和按钮元素。

组合子

我们已经看到了如何将类型选择器与类和 ID 选择器结合起来,但是如果我们想要结合多个类型选择器甚至属性选择器呢?还有一些其他的组合子可以实现这一点,它们甚至提供了基于 DOM 中元素关系的层次上下文。表 2-2 中的组合子示例可以在清单 2-8 和 2-9 以及图 2-6 中找到。

表 2-2

组合子

|

名字

|

配合

|

例子

|

描述

|
| --- | --- | --- | --- |
| 后裔 | " " (space) | nav a | nav 元素内的所有锚标记 |
| 儿童 | ">" | nav > ul > li | 导航列表中的第一个列表项,忽略第一级之后的任何项 |
| 兄弟 | "~" | p ~ p | 共享同一父元素的所有段落(在第一个段落之后) |
| 相邻兄弟姐妹 | "+" | h2 + p | 同一层次上紧跟在<h2>标签之后的所有段落 |

img/487079_1_En_2_Fig6_HTML.jpg

图 2-6

组合子

nav a {
  display: block;
  margin: 0 1rem;
}

nav > ul > li {
  border: solid 1px gray;
  display: inline-block;
  list-style-type: none;
  vertical-align: top;
}

p ~ p {
  color: purple;
  font-weight: bold;
}

h2 + p {
  font-family: sans-serif;
}

Listing 2-9Combinators CSS

<body>
  <h1>Combinators</h1>
  <nav>
    <ul>
      <li><a href="">Home</a></li>
      <li>
        <a href="">Combinators</a>
        <ul>
          <li>" " (space)</li>
          <li>&gt;</li>
          <li>~</li>
          <li>+</li>
        </ul>
      </li>
    </ul>
  </nav>
  <main>
    <h2>List of Combinators</h2>
    <p>There are a few other combinators to make this...</p>
    <ul>
      <li>
        " " (space)
        <ul>
          <li>nav li</li>
          <li>nav a</li>
        </ul>
      </li>
      <li>></li>
      <li>~</li>
      <li>+</li>
    </ul>
    <p>By combining selectors together we can select...</p>
  </main>
</body>

Listing 2-8Combinators HTML

通过将选择器组合在一起,我们可以根据元素在 HTML 文档中的自然位置和顺序来选择样式。这有助于我们将布局、主题和内容分离开来,以实现更易管理的规则集。

伪元素

伪元素允许您选择 HTML 文档中不存在的元素,但可以在屏幕上直观地显示出来。::first-letter::first-line都选择元素中的一部分文本。

虽然::first-letter的效果可以通过在想要的字母周围添加一个<span>标签来重现,但是对于流畅的布局,除了::first-line之外,实际上没有其他方法可以选择文本块的整个第一行。这是因为这个规则是在计算完布局之后应用的,这样浏览器就知道哪些单词应该受到规则的影响。参见清单 2-10 和 2-11 以及图 2-7 。

img/487079_1_En_2_Fig7_HTML.jpg

图 2-7

伪元素:::第一行和::第一级

p::first-letter {
  color: red;
  font-size: 3rem;
  line-height: 0;
  display: block;
  float: left;
  margin-top: .125rem;
  margin-right: .5rem;
}

p::first-line {
  color: red;
}

Listing 2-11Pseudo Elements CSS – ::first-line and ::first-level

<body>
  <h1>Pseudo Elements</h1>
  <p>Lorem ipsum dolor sit amet, consectetur...</p>
  <p>Cras id blandit risus. Nunc dictum, elit...</p>
  <p>Quisque euismod tempus erat, sit amet pharetra...</p>
</body>

Listing 2-10Pseudo Elements HTML – ::first-line and ::first-level

::after::before伪元素使用content属性根据特定标准插入内容(文本或图像)。我们在清单 2-12 和 2-13 以及图 2-8 中看到了这一点。

img/487079_1_En_2_Fig8_HTML.jpg

图 2-8

伪元素 ::before 和::after

a {
  display: block;
}
a::before {
  content: url(link.png);
  display: inline-block;
  margin-right: .5rem;
  vertical-align: middle;
}
a::after {
  content: ' (link)'
}

Listing 2-13Pseudo Elements CSS – ::before and ::after

<body>
  <a href>First Link</a>
  <a href>Second Link</a>
  <a href>Third Link</a>
</body>

Listing 2-12Pseudo Elements HTML – ::before and ::after

您曾经想要自定义输入元素上的占位符文本吗?你可以用input[type=text]::placeholder来做这件事(见清单 2-14 和 2-15 以及图 2-9 和 2-10 )。

img/487079_1_En_2_Fig10_HTML.jpg

图 2-10

伪元素::背景

img/487079_1_En_2_Fig9_HTML.jpg

图 2-9

伪元素::占位符和::选择

input {
  box-sizing: border-box;
  border-radius: 4px;
  border: solid 1px gray;
  padding: .5rem 1rem;
  font-size: 1rem;
  width: 100%;
}

input[type=text]::placeholder {
  font-family: cursive;
}
::selection {
  background-color: cornflowerblue;
  color: white;
}

::backdrop {
  background: cornflowerblue;
}

Listing 2-15Pseudo Elements CSS – ::placeholder, ::selection, and ::backdrop

<form>
  <label>
    Username:
    <input type="text" placeholder="Example: user@email.com">
  </label>
</form>

<video width="100%" height="250" controls>
  <source src="" type="video/mp4">
</video>

Listing 2-14Pseudo Elements HTML – ::placeholder, ::selection, and ::backdrop

你有没有在一个网站上选择文本,并注意到选择的突出显示是在网站的品牌颜色中?这可以通过*::selection {background-color: cornflowerblue}来完成。

全屏浏览模式下的背景可以使用::backdrop自定义。

在前面的示例中也可以看到这两种情况。

Note

CSS 规范要求在伪元素前有一个双冒号前缀,比如::after。然而,大多数浏览器支持只有一个冒号(:after)的伪元素,而不会抛出错误。您可能会在遇到的样式表中看到这种用法,理解它的工作原理很重要。一般来说,我们推荐标准的双冒号前缀有两个原因:(1)它符合 CSS 规范,(2)它清楚地区分了伪元素和伪类。

伪类

伪类根据 HTML 文档中没有的信息选择元素。这可能包括状态或上下文元数据。

一些伪类使得基于用户交互调整样式成为可能。

  • 当一个元素被悬停时匹配(比如使用鼠标)

  • :focus –匹配用键盘(通过制表符)或鼠标(通过点击元素)选择的元素

  • :active –匹配一个正在被激活的元素(比如点击,同时按下鼠标键)

  • :target –选择一个元素,该元素的 ID 与 URL 的片段(在#)之后的部分)相匹配

使用位置伪类可以很容易地以漂亮的格式显示表格数据。分别用tr:first-of-typetr:last-of-type选择表格的第一行和最后一行。使用同样的技术,使用<td>选择第一列和最后一列。使用tbody > tr:nth-child(even)高亮显示每隔一行。

管理表单和显示有用的指示器可以使用以下一些伪类:

  • :in-range:out-of-range –与定义范围相比的数值

  • :placeholder-shown –如果占位符文本当前可见

  • :invalid:valid –检查表单字段的验证状态,查看是否有错误和成功指示器

  • :checked:indeterminate用于选择当前选中的复选框或单选按钮,或者无法确定选中的选项

  • :default –仅当该元素是一组元素中的默认元素时才匹配(如表单上的默认提交按钮或默认单选选项)

  • :disabled:read-only:read-write –根据用户交互的可用性匹配表单字段的当前状态

  • :optional:required –根据字段所需的状态匹配字段

另一个重要的伪类是:not()选择器,它选择匹配选择器列表的元素。虽然许多伪类都定义了它们的反转状态(例如,:optional:required),但是在许多其他场景中,否定也是有用的。例如,您可以通过使用article > *:not(img) { ... }来选择<article>的每个直接子标签,即而非<img>

这些伪类中有许多为 CSS 提供了功能,否则在设计用户体验时需要 JavaScript 的参与。通过利用 CSS 实现上下文相关的 UI,我们将应用程序和视图逻辑分开,提高了网站和 web 应用程序的可维护性和性能。前面提到的一些伪类的例子可以在图 2-11 所示的清单 2-16 和 2-17 中找到。

img/487079_1_En_2_Fig11_HTML.jpg

图 2-11

伪类

table {
  border-collapse: collapse;
  margin-bottom: 1rem;;
  width: 100%;
}
tr {
  border-top: solid 1px lightgrey;
  border-bottom: solid 1px lightgrey;
}
tbody tr:nth-last-of-type(odd) {
  background: lightblue;
}
th, td {
  padding: .5rem 1rem;
  text-align: left;
}

form {
  margin-top: 2rem;
}
form > *:not(button) {
  border-radius: 4px;
  box-sizing: border-box;
  display: block;
}

label {
  margin-bottom: .5rem;
}
input {
  border: solid 1px lightblue;
  padding: .5rem 1rem;
  width: 100%;
}
input:hover, input:active {
  border-color: slategray;
}
input:invalid {
  border-left: solid 5px red;
}
input:valid {
  border-left: solid 5px green;
}

button {
  padding: .5rem 1.5rem;
  border: solid 1px lightblue;
  border-radius: 3px;
  background: white;
  margin-top: .5rem;
}
button:hover, button:active {
  outline: dotted 1px blue;
  outline-offset: 2px;
}

Listing 2-17Pseudo Classes CSS

<body>
  <h1>Pseudo Classes</h1>
  <table>
    <thead>
      <th>Name</th>
      <th>Email</th>
      <th>Zip Code</th>
    </thead>
    <tbody>
      <tr>
        <td>Jane</td>
        <td>jane@email.com</td>
        <td>15978</td>
      </tr>
      <tr>
        <td>John</td>
        <td>john@email.com</td>
        <td>11458</td>
      </tr>
      <tr>
        <td>Alex</td>
        <td>alex@email.com</td>
        <td>68978</td>
      </tr>
    </tbody>
  </table>
  <form>
    <label>
      Name:
      <input type="text" maxlength="20" required>
    </label>
    <label>
      Email
      <input type="email" maxlength="100" required>
    </label>
    <label>
      Zip Code:
      <input type="number" max="99999">
    </label>
    <button type="submit">Submit</button>
  </form>
</body>

Listing 2-16Pseudo Classes HTML

声明

如果我们不对元素应用样式,那么选择元素没有任何好处。每个规则集的声明部分是为匹配元素指定单个样式属性及其值的地方。

性能

CSS 中的属性指的是可能受到影响的布局和样式的各个方面。许多属性只适用于某些元素,而不适用于其他元素。有时,属性的可见性将取决于元素的显示设置。例如,height属性在带有display: inline的元素上被忽略,但是在display: inline-block上呈现。

有些 CSS 属性是许多单个属性的简写符号。考虑清单 2-18 中的例子,其中两个声明块产生相同的结果。

p {
  border-width: 2px;
  border-style: solid;
  border-color: #666666;
}

p {
  border: 2px solid #666666;
}

Listing 2-18Border Property

每个单独的边框属性都可以作为边框速记属性的可选值参数。其他一些速记属性包括backgroundbox-shadowfontpaddingmarginoutline。其中每一个都有一个不同的属性列表,并且它们有一个特定的属性提供顺序。在使用这些方法时,一定要查阅参考资料,直到您对每种方法的语法都很熟悉为止。

一些属性和值的组合可能会产生看似相似但实际上非常不同的结果。表 2-3 列出了其中一些,并解释了它们之间的区别。

表 2-3

属性歧义消除

|

第一个属性

|

第二财产

|

描述

|
| --- | --- | --- |
| 边距:2px | 填充:px; | 边距在方框模型之外,相邻时可以折叠。填充物在盒子里面。 |
| 边框:2px 纯黑; | 轮廓:2px 纯黑; | 边框添加到框模型尺寸,并存在于边距和填充之间。轮廓位于边框之外,不占用盒子上的空间。 |
| 可见性:隐藏; | 显示:无; | 隐藏元素仍然存在于页面上,并且可以占用空间和接收事件。渲染树中不存在未有效显示的元素。 |

属性对于突出显示屏幕上不希望项目回流的元素非常有用。这通常用于结合:focus伪类来突出显示元素。关于盒子模型的详细信息,以及与布局相关的属性,包括displaygridflex,请参见第四章。

对可用 CSS 属性和值的全面回顾超出了本书的范围。对于一个优秀的参考,我们推荐 Mozilla 的 MDN CSS 参考,可以在 https://developer.mozilla.org/docs/Web/CSS/Reference 找到。

单位

有许多 CSS 属性需要一个<length>数据类型。该长度是一个标量(数字)值,带有相关的测量单位。选择正确的单位可以使一个漂亮的、流畅的、有反应的布局和一个在用户调整窗口大小或缩放时会中断的布局产生区别。正确的单位也会对实现特定布局所需的工作量产生巨大影响。

我们将讨论三种基本的单位类别。第一类包括在设计时建立的绝对度量。第二类是字体相关的,这意味着如果用户缩放页面或者改变默认的字体大小,这些值的含义会相对于彼此而改变。第三类包含相对于视口的长度,这意味着它们将相对于浏览器大小或用户设备上的特定显示而变化。

绝对的

px计算机图形学的传统计量单位;这仅适用于基于屏幕的显示。

英寸到英寸。1in. = 6pc = 72pt = 2.54cm。这将是打印机上的真实英寸,但相对于屏幕的参考像素定义,而屏幕的参考像素96px,与屏幕分辨率无关。

PCPica。印刷字体中的传统度量单位。

点。印刷字体中的传统度量单位。

厘米厘米厘米。1cm = 10mm。请参阅前面关于打印机和屏幕的英寸说明。

毫米毫米毫米。

Note

绝对测量单位不会相对于font-size等用户设置进行缩放。因此,使用这些单元(尤其是在屏幕上)可能会导致严重的可访问性问题,因此不推荐使用。

字体-相对

ch 表示元素字体中“0”字符的宽度(包括字样和大小)。

ex 表示元素字体中“x”字符的高度(包括字样和大小)。

em 元素的计算font-size。如果这个单元用在font-size属性上,它将相对于继承的font-size

rem em完全相同,但总是相对于根元素的font-size(对于 HTML 文档来说就是<html>)。这是许多网页设计者的首选默认单位,因为它允许可管理的流畅布局,同时解决了可访问性问题。

视口-相对

vh 等于视口高度的 1%

vw 等于视口宽度的 1%

vmin等于vhvw较小的

VMAX等于vhvw较大的

百分率

许多 CSS 属性将接受一个<percentage>或一个<length-percentage>(意味着长度或百分比)。虽然rem对于许多目的来说是最好的选择,尤其是那些与内容和可访问性相关的目的,但是百分比对于任何继承的大小都有效,包括字体相对大小、视图相对大小,甚至是绝对单位。

功能

虽然 CSS 不允许用户定义函数,但是有大量的函数可用于执行各种任务,其中一些描述如下:

形状 功能circle()ellipse()inset()polygon()支持多种非矩形形状。与shape-outside属性结合以将文本换行为特定形状,或者与clip-path结合以裁剪图像或容器。

变换 变换函数数量众多,包括rotateX()scale()skewY()。还有perspective()matrix3d()scaleZ()等 3D 变换。这些变换可以调整屏幕上元素的形状、方向和位置,以创建各种视觉效果和布局。

渐变 有大量的函数支持渐变的创建,包括linear-gradient()radial-gradient()repeating-linear-gradient()repeating-radial-gradient()。由渐变实现的颜色混合支持大量的视觉效果。

除了渐变,还有其他视觉效果。blur()功能将在所选元素上产生高斯模糊,甚至是图像。这对于模式对话框的背景很有用。drop-shadow()给主题增加了一些维度。并且opacity()允许元素处于完全不透明和完全透明之间,以允许维度覆盖。(请注意,如果您想要不透明的文本和半透明的背景,您可以考虑使用rgba()hsla()颜色功能,如下文所述。)

在 CSS 中指定颜色最常见的方式是在 3 或 6 位数的十六进制代码前加一个散列符号,比如红色的#FF0000。也可以使用hsl()hsla()功能通过色调、饱和度和亮度指定颜色,或者使用rgb()rgba()指定为 RGB(红、绿、蓝)。每个函数集中的“a”指的是指定不透明度或透明度级别的 alpha 通道。

还可以使用filter属性以一致的方式操纵颜色,修改如contrast()saturate()hue-rotate(),应用效果如grayscale()sepia()。这些函数特别有用,因为它们可以应用于页面上的图像和文本。

资源–``url()功能用于通过 CSS 将图像资源添加到设计中。这使得 HTML 中的< img >标签可以保留给与内容相关的图片,而不是与布局和设计相关的图片。

计数 计数函数counter()counters()symbols()用于管理计数器变量。有关计数器的更多信息,请参见下面的“变量”一节。

数学 有时候内置的单位不够,你需要根据其他元素来计算大小或位置。calc()函数使得用混合单位做一些基本的数学运算成为可能。支持加、减、乘、除和括号。举个例子,你可以使用height: calc(10vh - 1rem)来计算一个标题的高度,它是视窗高度的 10%,但是考虑到了1rem边框。

清单 2-19 和 2-20 显示了图 2-12 的源代码。

img/487079_1_En_2_Fig12_HTML.jpg

图 2-12

功能

.shape {
  clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
  display: inline-block;
  position: relative;
  height: calc(100vw / 3);
  width: calc(100vw / 3);
}
.shape:nth-of-type(1) {

  background: rgba(255, 0, 255, 0.31);
  transform: rotate(-25deg);
  filter: saturate(15%);
}
.shape:nth-of-type(2) {
  background: rgb(255,116,0);
  background: linear-gradient(90deg, rgba(255,116,0,1) 0%, rgba(255,237,0,1) 47%, rgba(255,167,0,1) 100%);
  filter: opacity(.75);
  transform: translate(0, -50px);
  left: calc((100vw / 3) - 200px);
}
.shape:nth-of-type(3) {
  background: hsl(189, 100%, 50%);
  transform: rotate(25deg);
  opacity: .33;
  left: calc((100vw / 3) - 100px);
  top: -200px;
}

Listing 2-20Functions CSS

<body>
  <h1>Functions</h1>
  <div class="shape"></div>
  <div class="shape"></div>
  <div class="shape"></div>
</body>

Listing 2-19Functions HTML

图 2-12 中所示的例子突出了一些功能。示例中星星的位置取决于浏览器窗口的大小,因为计算基于vwvh单位。

变量

有几种方法可以在 CSS 中使用动态数据(示例见清单 2-21 和 2-22 以及图 2-13 ):

自定义属性 这些变量的定义很像任何其他 CSS 属性,可以包含任何在 CSS 中合法的值。然后可以使用var()函数在样式表中引用它们。

属性 使用attr()函数,您可以从 HTML 属性中获取值。将它与content属性结合起来,以独特的方式显示属性数据。

每个 HTML 元素可以有 0 到多个命名的计数器,这些计数器在文档树中相关联,并使用 CSS 进行操作。HTML 列表自动生成一个“列表项”计数器,除非显式重置,否则每个列表元素递增 1。这也包括无序列表。使用counter-setcounter-incrementcounter-decrement属性调整计数器,并使用counter()counters()以您选择的方式显示指定计数器的值。这是为了支持嵌套列表而存在的,但可能有许多其他用途。

img/487079_1_En_2_Fig13_HTML.jpg

图 2-13

变量

ul {
  counter-reset: li;
}
li:before {
  content: counter(li)"-" attr(category)": ";
  counter-increment: li;

  text-transform: capitalize;
  background: lightblue;
  display: inline-block;
  padding: .5rem 1rem;
  border-radius: 25px;
  margin: 0 1rem 1rem 0;
}

Listing 2-22Variables CSS

<body>
  <h1>Variables</h1>
  <ul>
    <li category="fruit">Apple</li>
    <li category="vegetable">Lettuce</li>
    <li category="starch">Corn</li>
  </ul>
</body>

Listing 2-21Variables HTML

规则

CSS at-rules(之所以这样命名是因为每个名称中都有@或“at”符号)是一些语言特性,它们提供了对样式结构的一些控制。这些规则提供了一种收集或分组其他规则集的机制。

@导入

在第一章中,我们看了在 HTML 文档中包含 CSS 的三种方法,包括<link>元素。@import at-rule 为 CSS 提供了类似的功能。这两者都在 CSS 文件中包含了拉机制,有效地将其内容插入到 import 语句的位置。

这非常有用,因为它允许我们将样式表分解成更具逻辑性和更易管理的文件,而不会对 HTML 文档产生任何影响。关于@imports和其他引入外部样式表的机制的更深入的讨论,请参见第七章。

@支持

@supports at-rule 允许基于用户代理对 CSS 特性的特定支持来应用规则。这是一种基于 web 浏览器声明支持的内容来提供样式和格式的方式,而不是使用老派的技巧来尝试检测给定的规则是否会按预期工作。

这种 at-rule 允许您开始利用当今最新的 CSS,它使得为尖端浏览器提供替代规则(或者为旧浏览器提供替代规则)成为可能,如清单 2-23 和 2-24 中所示。

p {
  text-decoration: underline;
  text-underline-offset: 1rem;
}

@supports not (text-underline-offset: 1rem) {
  p {
    text-decoration: none;
    padding-bottom: 1rem;
    border-bottom: solid 3px orange;
    display: inline-block;
  }
}

Listing 2-24At-Rules CSS

<body>
  <h1>At-Rules</h1>
  <p>Hello World</p>
</body>

Listing 2-23At-Rules HTML

因为text-underline-offset是 Firefox 支持的(图 2-14 ,Firefox 忽略了@support not代码。然而,在撰写本书时,Opera 不支持text-underline-offset,因此使用@supports not中提供的回退代码(图 2-15 )。

img/487079_1_En_2_Fig15_HTML.jpg

图 2-15

Opera 中不支持 At-Rules,text-underline-offset

img/487079_1_En_2_Fig14_HTML.jpg

图 2-14

火狐支持 At-Rules,text-underline-offset

@媒体

CSS media at-rule 用于对系统、环境或用户代理执行查询。这种使用被称为媒体查询

Media Query

媒体查询允许作者测试和查询用户代理或显示设备的值或特征,而与正在呈现的文档无关。它们用在 CSS @media 规则中,以有条件地将样式应用于文档,以及各种其他上下文和语言,如 HTML 和 JavaScript。

—媒体查询级别 4 1

如第四章所述,这些媒体查询可用于构建响应性布局,但它们还有许多其他用途。例如,可以创建一个打印机友好的主题,隐藏导航和横幅,同时保留内容,如清单 2-25 所示。

@media print and monochrome {
  nav, .banner { display: none; }
}

Listing 2-25Printer-Friendly Design

通过使用@page at-rule 提供特定于页面的指令,可以获得对打印的额外控制。 2

还可以为没有支持悬停的定点设备(如鼠标)的设备调整布局,这在平板电脑和移动设备中很常见。在清单 2-26 中,我们在每个链接旁边显示目标 URL,但是只在不支持悬停的设备上显示。

@media (not(hover)) {
  a::after {
    content: attr(href);
    font-size: x-small;
    position: absolute;
  }

}

Listing 2-26Tablet-Friendly Icon

摘要

本章讲述了 CSS 规则集的基本构件。你已经学会了如何

  • 指定打印机特定的布局和设计

  • 仅使用 CSS 根据文件扩展名插入文档图标

  • 将多个选择器组合成更高级的表达式

  • 突出显示表格中的交替行

  • 基于浏览器和设备功能提供替代样式

下一章将介绍级联样式表的级联部分,并解释用户代理决定页面上每个元素的每个属性值的过程。

三、重要性顺序

正如在第一章中提到的,CSS 的一个重要特性是用户、浏览器和 web 开发者都能够影响页面的最终输出。用户代理、作者和用户都可以影响页面的输出。为了规定什么属性值“胜出”,需要执行多步计算。

遗产

继承是一种机制,通过这种机制,CSS 允许父元素上设置的值(如<body>)传播到其子元素。这有助于确定在元素属性上没有声明属性时使用什么值。继承的值由父代或祖先的计算值决定。如果不存在,则使用初始值或浏览器设置的默认值。

默认情况下,并非所有属性值都会被继承。 1 确实如此的属性通常与主题化相关,例如与排版相关的属性(字体大小、行高、字母间距等。).与布局相关的属性(如显示、边框、宽度和高度)通常不是。如果不可继承的属性没有声明的值,则使用初始值。见清单 3-1 和 3-2 和图 3-1 。

<body>
  <h1>Inheritance</h1>

  <p>Lorem ipsum dolor sit amet, consectetur... </p>
  <img src="image.png" alt="art">
  <table>
    <tr>
      <th>Lorem Ipsum</th>
      <td>Lorem ipsum dolor sit amet, consectetur... </td>
    </tr>
    <tr>
      <th>Pellentesque</th>
      <td>Pellentesque sit amet massa auctor est... </td>
    </tr>
  </table>
  <p>Pellentesque sit amet massa... </p>
</body>

Listing 3-1Cascading and Inheritance HTML

body {
  color: gray;
  padding: 2rem;
  text-align: justify;
  line-height: 1.5rem;
  font-family: Helvetica, Arial, sans-serif;
  font-weight: lighter;
}

h1 {
  color: slategray;
  font-family: 'Comic Sans MS';
  font-size: 2.5rem;
  letter-spacing: .0625rem;
}
h1 {
  font-family: fantasy;
}

p:first-of-type::first-letter {
  color: gold;
  display: block;
  float: left;
  font-size: 3rem;
  line-height: 0;
  margin: .5rem .5rem 0 0;
}

table {
  border-collapse: collapse;
}
tr {

  color: slategray;
  border-top: solid 1px lightsteelblue;
  border-bottom: solid 1px lightsteelblue;
}
td {
  padding: .5rem 1rem;
}

img {
  margin: 0 0 0 1rem;
  float: right;
  width: 200px;
}

Listing 3-2Cascading and Inheritance CSS

img/487079_1_En_3_Fig1_HTML.jpg

图 3-1

级联和继承

body 属性的text-align属性值为justify。段落属性上未设置样式;然而,这些段落事实上是合理的。段落的text-align值继承自主体的text-align属性。然而,填充不是继承的,这就是为什么即使正文选择器的填充值为两个 rem,段落、图像、链接等也没有两个 rem 的填充值。

继承的主要好处之一是,它避免了在不同的选择器中一遍又一遍地为相同的属性编写值的需要,有助于代码的风格一致性和可维护性。

在本例中,颜色也是继承的,但是第一段的第一个字母并没有像在body选择器中设置的那样显示为灰色,而是像在p:first-of-type::first-letter选择器中设置的那样显示为金色。第一段的第一个字母是金色而不是灰色的原因是一个特殊性的问题;p:first-of-type::first-letterbody更具体。

全球价值观

Inherit、unset 和 initial 与 CSS 中的其他属性值略有不同。这些值在所有属性上都可用,它们的明显区别是要么将值重置为默认值,要么重置为祖先值而不是新值。这些值使您可以明确控制属性的继承方式。

inheritunset,initial的示例基于清单 3-3 和 3-4 中的代码。

body {
  font-family: sans-serif;
  padding: 10px;
}

p {
  padding: 20px;
  border: dashed 1px gray;
}
p::first-letter {
  display: block;
  float: left;
  font-size: 3rem;
  color: red;
}
p:nth-of-type(2) { padding: unset }
p:nth-of-type(3) { padding: default }
p:nth-of-type(3) { padding: initial }
p:nth-of-type(4) { padding: inherit }

li { font-family: cursive; }
li:nth-of-type(2) { font-family: unset; }
li:nth-of-type(3) { font-family: initial; }
li:nth-of-type(4) { font-family: inherit; }

Listing 3-4Exceptions CSS

<body>
  <h1>Exceptions</h1>

  <ol>
    <li>Cursive</li>
    <li>Unset</li>
    <li>Initial</li>
    <li>Inherit</li>
  </ol>

  <p>1 Lorem ipsum dolor sit amet, consectetur... </p>
  <p>2 Pellentesque sit amet massa auctor est... </p>
  <p>3 Duis vitae iaculis risus. Vivamus id egestas... </p>
  <p>4 Mauris vel mi quis lorem laoreet aliquet... </p>
</body>

Listing 3-3Exceptions HTML

未设置

根据所分配的属性,Unset 的工作方式会有所不同。如果值可以从父代继承,它将继承;否则,它会将属性值设置为 initial。

在列表项的情况下,由于font-family可以被继承,第二个列表项将具有sans-seriffont-family。该值继承自其父容器body(见图 3-2 )。

img/487079_1_En_3_Fig2_HTML.jpg

图 3-2

继承未设置

因为填充是不可继承的,所以填充在第二个段落标签上被设置为0,因为段落标签上的初始填充值是0(见图 3-3 )。

img/487079_1_En_3_Fig3_HTML.jpg

图 3-3

对不可继承的属性取消设置

最初的

属性的初始值可以由浏览器设置,并且可以根据用户代理而变化。如果 CSS 规范中声明了初始值,那么 initial 应该返回该值。大多数现代浏览器是一致的,但里程可能会有所不同。例如,在 Firefox 中,font-family的默认值是serif。因此,第三个列表元素font-family的值为serif(见图 3-4 )。

img/487079_1_En_3_Fig4_HTML.jpg

图 3-4

最初的

继承

属性值将等于父属性的值,无论该属性是否是默认继承的。填充不是继承的。即便如此,当在第四个段落标记的 padding 属性上设置 inherit 时,该段落标记采用设置为其父级<body>的值。Body 的填充值为 10px 因此,该段也是如此。见图 3-5 。

img/487079_1_En_3_Fig5_HTML.jpg

图 3-5

继承

如这个例子所示,我们可以通过使用inherit属性来强制继承,从而直接控制级联。

特征

根据计算特异性的方式,不同类型的选择器有不同的重要性顺序。表 3-1 中总结了四个重要类别,每个类别都比其下一个类别重要一个数量级。

表 3-1

选择器排名

|

种类

|

选择器

|
| --- | --- |
| A | ID 选择器 |
| B | 类选择器、属性选择器、伪类 |
| C | 类型选择器,伪元素 |
| Zero | 通用选择器 |

任何给定选择器的特异性计算为三位数,数字 A、B 和 C,其中 A、B 和 C 代表其类别中选择器的总数。 2 几个例子见表 3-2 。

表 3-2

计算特异性

|

示例选择器

|

A

|

B

|

C

|

特征

|
| --- | --- | --- | --- | --- |
| * | Zero | Zero | Zero | Zero |
| button | Zero | Zero | one | one |
| ul li | Zero | Zero | Two | Two |
| button:not([type=submit]) | Zero | one | one | 1 1 |
| a[href$=".pdf"]::before | Zero | one | Two | 1 2 |
| button.outline.bold | Zero | Two | one | 2 1 |
| button#submit | one | Zero | one | 1 0 1 |

特殊性在决定级联过程中应用哪些样式时起着重要的作用。

Inline Styles

直接应用于 HTML 中元素的样式,如

<p style="margin-left: 10px">Lorem ipsum am met...</p>

是内嵌样式。它们相当于使用 JavaScript 直接在 DOM 中添加属性值。内联样式被赋予了[1 0 0 0], 3 的特异性,这比使用普通选择器可能得到的任何东西都高,如表 3-2 所示。内联样式通常被认为是不好的做法,因为它们忽略了继承和级联。但是也有一些不可避免的例外,包括 HTML 电子邮件。

优先

规则的应用顺序很重要。直接作为目标的规则总是优先于从父项或祖先继承的规则。如果应用了两个具有相同特征级别的规则,将应用最后一个规则。这个概念是 CSS 的核心,从一开始就有了,正如它的名字“级联样式表”所表明的。

!important

CSS 从业者都知道,!important注释既是一个强大的工具,也是一个巨大的负担。当其他方式都不起作用时,web 开发人员有时会使用它来强制一种样式生效。但是你知道吗,!important目的实际上是为了提高可访问性。因为重要的用户声明总是具有最高的优先级,所以在呈现页面时,它让用户最终决定设置哪些属性和值。

除了特殊性,规则的来源也是确定元素使用的值的一个因素。表 3-3 按照从最不重要到最重要的顺序显示了层叠期间应用规则的顺序。

表 3-3

级联顺序 4

|

命令

|

起源

|

重要

|

优先

|
| --- | --- | --- | --- |
| one | 用户代理人 | 标准 | eight |
| Two | 用户 | 标准 | seven |
| three | 作者 | 标准 | six |
| four | 动画片 |   | five |
| five | 作者 | !important | 4 |
| six | 用户 | !important | 3 |
| seven | 用户代理人 | !important | 2 |
| eight | 过渡 |   | one |

在层叠中,最后应用的项目获胜;所以,转场会争取用户代理!重要包含用户规则!重要的规则等等。

级联

级联表示来自各种源的属性和值以不同的优先级和特性级别组合在一起,以确定将呈现的最终样式集。

需要注意的是,级联到元素的是属性,而不是规则集。元素的最终状态可能包括在许多不同的规则集中声明的属性。

为了计算级联,应用以下公式: 5

  1. 选择具有最高优先级的声明。

  2. 选择剩余的具有最高特异性的声明。

  3. 当所有其他因素相同时,最后出现在的声明将被应用。

价值处理

所有不同来源的属性值被一起使用,以使用以下计算来确定最终值: 6

  1. 首先,对于每个元素上的每个属性,收集应用于元素的所有声明值。可能有 0 个或多个声明值应用于该元素。

  2. 级联产生级联值。每个元素的每个属性最多有一个级联值。

  3. 默认产生指定值。每个元素的每个属性只有一个指定值。

  4. 解析值依赖关系产生计算值。每个元素的每个属性只有一个计算值。

  5. 格式化文档会产生已用值。对于给定的属性,如果该属性适用于元素,则该元素仅具有已用值。

  6. 最后,基于显示环境的约束,使用值被转换成实际值。与使用的值一样,元素上的给定属性可能有也可能没有实际值。

W3C 规范中的这一计算引用了各种值分类,它们被定义为

  • 声明的 这些都是与被检查的元素和属性相匹配的值(0 多)。

  • 级联 这是处理级联后选择的值(0 1)。

  • 指定 这是级联的值,如果可用,或者是属性和元素的默认值。对于每个属性和元素,始终有一(1)个指定值。

  • 计算的 指定的值的绝对值,该值可以被子元素继承。

  • 已用 这是用户代理用于文档布局的最终值。

  • 实际 这是设备上实际显示的值,由于设备或环境的限制,可能会从使用的值进行调整。

每个元素的每个属性的最终实际值由项目代码以外的各种因素决定,包括设备、用户代理或浏览器、用户代理样式表和用户提供的样式表。

摘要

在这一章中,你已经学习了 CSS 如何从许多不同的来源获取规则集,并为网页建立一个应用样式的内聚集的细节。特别是,你学到了

  • HTML 内联样式和!important注释如何影响层叠

  • 如何计算任何给定选择器的特异性

  • 在 DOM 树中继承属性的方式

在下一章中,你将了解 CSS 提供的不同选项,这些选项用于构建能够适应设备和内容变化的流畅的响应性布局。

四、布局

单个元素放在一起就形成了一个布局。使用 CSS,我们依靠盒子模型来控制每个元素的宽度和行为,而不需要布局。为了控制元素之间的相互关系,我们可以使用 display 和 float 这样的属性。在这一章中,我们定义了盒子模型,并着眼于具体布局的浮动、伸缩、内嵌块和网格。

盒子模型

布局内容的基础根植于框模型,它描述了为文档树中的元素生成的矩形框。如图 4-1 所示,内容由三个框包围:填充、边框和边距。

img/487079_1_En_4_Fig1_HTML.png

图 4-1

盒子模型

这些属性中的每一个,包括内容,都将由维度、类型、定位、与其他元素的关系以及外部信息来控制。

盒子尺寸

Box-sizing ,或者定义元素高度和宽度的属性,默认情况下有一个 content-box 的值,这意味着当为元素定义宽度和高度时,它只应用于内容。因此,向元素添加填充或边距会增加元素利用的总可用视区的百分比宽度。

内容盒

如果需要两列布局,每个 div 等于视口宽度的 50%,则需要从给定元素的宽度中减去应用于每列的填充量,否则两个元素的总宽度将超过 100%。

考虑包含两个 div 的 800 像素宽的视口。如果没有向 div 添加任何填充、边距或边框,并且每个 div 的宽度为 50%,则它们的组合宽度将等于视口的 100%。如果它们是浮动的,它们将完美地并排放置并占据 100%的屏幕,如图 4-2 所示。

img/487079_1_En_4_Fig2_HTML.png

图 4-2

内容框无填充

如果向列添加了填充,列的宽度将增加填充量,从而导致它们超过视区的宽度。图 4-3 显示了添加了填充的列。

img/487079_1_En_4_Fig3_HTML.png

图 4-3

带填充的内容框

如果 div 是浮动的,那么第二个 div 将因此被推到第一个 div 的下面,因为它们的组合宽度现在大于容器的 100%,如图 4-4 所示。

img/487079_1_En_4_Fig4_HTML.jpg

图 4-4

使用box-sizing: content-box时填充和边框的效果

用代码

让我们把前面描述的场景放到代码中(清单 4-1 和 4-2;输出如图 4-4 所示。

.container { overflow:auto; }

.container > div {
  width: 50%;
  float: left;
}

.container p {
  background: rgba(0, 0, 0, .16); /* light grey */
  text-align: center;
}

.container > div:last-of-type p { /* second rectangle */
  background: rgba(0, 0, 0, .32); /* dark grey */
}

.has-padding > div {
  outline: dashed 1px rgba(0, 0, 0, .5);
  padding: 10px;
}

Listing 4-2CSS

<body>
  <h1>No Padding</h1>
  <div class="container">
    <div>
      <p>Content</p>
    </div>
    <div>
      <p>Content</p>
    </div>
  </div>

  <h1>With Padding</h1>
  <div class="container has-padding">
    <div>
      <p>Content</p>
    </div>
    <div>
      <p>Content</p>
    </div>
  </div>
</body>

Listing 4-1HTML

边框的行为方式与填充相同;因此,应用的任何边框宽度都需要包含在内容和填充的总和中,以计算布局中包含的元素的全宽或全高。

边缘塌陷

边距的行为与填充略有不同。当同级元素都有填充时,应用两者的填充,两个元素之间的间距是两组填充的总和。然而,根据具体情况,利润率可能会下降。页边距折叠是指顶部底部页边距合并或折叠成一个页边距,等于应用的最大页边距,如图 4-5 所示,或者,如果所有页边距都为负值,则等于最大负值页边距的大小。左右边距不会折叠。

img/487079_1_En_4_Fig5_HTML.png

图 4-5

边距折叠

利润率将会崩溃

  • 没有任何东西分隔父对象的边距和其子对象的边距,包括填充、边框、内联部分、块格式上下文或清除属性 clear(例如,clear: right,与 floats 一起使用)。

  • 元素是相邻的兄弟元素,除非后者需要通过浮动来清除(本章后面会有更多关于浮动的内容)。

  • 即使其中一个边距等于 0。 1

用代码

当两个各有 10 个像素填充的 div 并排设置时,它们之间只有 10 个像素的垂直边距(如清单 4-3 、 4-4 和图 4-6 所示)。

div {
 margin: 10px;
 background: rgba(0, 0, 0, .16); /* light grey */
}

div:last-of-type {
 background: rgba(0, 0, 0, .32); /* dark grey */
}

Listing 4-4CSS

<body>
  <div>Content</div>
  <div>Content</div>
</body>

Listing 4-3HTML

img/487079_1_En_4_Fig6_HTML.jpg

图 4-6

边缘塌陷

但是,如果我们以前面的例子为例,其中的列是浮动的,并替换了边距的填充,请注意边距没有折叠。当这些列的组合总宽度大于 100%[50%+(2x10px]x2 = 105%时,它们仍然会堆叠在一起,但是因为 div 是浮动的,所以边距不会折叠。见图 4-7 。

img/487079_1_En_4_Fig7_HTML.jpg

图 4-7

浮动 div无边距塌陷

与 border 不同,border 可以应用于任何类型的元素,而 margin 和 padding 对于它们可以应用于哪些元素都有限制。不能在 display 属性值为的元素上设置填充

  • 表格-行-组(<tbody>)

  • 表格标题组(<thead>)

  • 表格-页脚-组(<tfoot>)

  • 表格行(<tr>)

  • 表格-列-组

  • 表格列

不能在具有表格显示类型(如<tr><td>等)的元素上设置边距。)除了表格、内嵌表格和表格标题。 2

利弊

虽然混合像素和基于百分比的值可以产生一些有趣的数学结果,但是将box-sizing值保持为content-box的好处是,当宽度或高度值被分配给内容时,它不会受到添加的填充的副作用的影响。内容将完全是由开发人员指定的高度或宽度。此外,因为它是默认值,所以元素的大小将表现出“正常”的预期行为,而不必了解元素上已经设置的其他属性。

Special Case: Outlines and Box Shadows

轮廓和框阴影在边界周围创建框,可以认为属于框模型。事实并非如此,因为轮廓和方框阴影不占用空间。它们自身重叠,类似于图 4-8 中描述的绝对位置。

img/487079_1_En_4_Fig8_HTML.png

图 4-8

轮廓和方框阴影

轮廓和方框阴影不仅会覆盖下面的内容,而且会溢出视窗,无法滚动到视窗。因为它们不占用空间,所以不构成溢出,因此滚动条不会被触发。见图 4-9 和清单 4-5 和 4-6 。

.container div {
  background: rgba(0, 0, 0, .16); /* light grey */
  height: 50px;
}

.container div:last-of-type {
  background: rgba(0, 0, 0, .32); /* dark grey */
}

.container.outline div:last-of-type {
  outline: dotted 15px rgba(0, 0, 0, .5);
}

.container.box-shadow div:last-of-type {
  box-shadow: 0px 0px 10px 10px rgba(0, 0, 0, .5)
}

Listing 4-6CSS

<body>
  <h1>Outline</h1>
  <div class="container outline">
    <div>Content</div>
    <div>Content</div>
  </div>
  <h1>Box-Shadow</h1>
  <div class="container box-shadow">
    <div>Content</div>
    <div>Content</div>
  </div>
  <h1>Both</h1>
  <div class="container outline box-shadow">
    <div>Content</div>
    <div>Content</div>
  </div>
</body>

Listing 4-5HTML

img/487079_1_En_4_Fig9_HTML.jpg

图 4-9

方框阴影和轮廓不占用布局中的空间

当轮廓和框阴影都设置时,它们将相互重叠。

边框

如前所述,box-sizing: content-box在混合绝对单位和基于百分比的单位时有一些缺点。当内容有填充时,将内容设置为总宽度或高度的百分比时,内容框也会增加额外的复杂性。

这就是 border-box 出现的原因。分配box-sizing: border-box改变元素的宽度和高度的计算方式。它不仅包含内容,还包含内容、填充和边框。当添加填充或边框时,内容本身的宽度和高度会因此而减小。见图 4-10 。

img/487079_1_En_4_Fig10_HTML.png

图 4-10

带衬垫的边框

因此,如果我们以两个浮动列的第一个示例为例,我们将看到这两个列保留了 50%的宽度。页边空白仍然以与content-box相同的方式运行。清单 4-7 和 4-8 以及图 4-11 显示了与图 4-4 相同的示例,使用box-sizing: border-box代替默认的box-sizing: content-box

img/487079_1_En_4_Fig11_HTML.jpg

图 4-11

使用边框的元素

.container { overflow:auto; }
.container > div {
  width: 50%;
  box-sizing: border-box;
  float: left;
}
.container p {
  background: rgba(0, 0, 0, .16); /* light grey */
  text-align: center;
  margin: 0;
}
.container > div:last-of-type p { /* second rectangle */
  background: rgba(0, 0, 0, .32); /* dark grey */
}
.has-padding > div {
  border: dashed 1px rgba(0, 0, 0, .5);
  padding: 10px;
}
.has-margin > div {
  border: dashed 1px rgba(0, 0, 0, .5);
  margin: 10px;
}

Listing 4-8CSS

<body>
  <h1>No Padding or Margin</h1>
  <div class="container">
    <div>
        <p>Content</p>
    </div>
    <div>
      <p>Content</p>
    </div>
  </div>

  <h1>With Padding</h1>
  <div class="container has-padding">
    <div>
      <p>Content</p>
    </div>
    <div>
      <p>Content</p>
    </div>
  </div>

  <h1>With Margin</h1>
  <div class="container has-margin">
    <div>
      <p>Content</p>
    </div>
    <div>
      <p>Content</p>
    </div>
  </div>
</body>

Listing 4-7HTML

盒子大小不是继承的。它将需要被应用到所有需要被改变的元素。

显示

边距和填充允许操作元素的显示;display 属性通过指定框用于元素的呈现类型来控制元素如何相互显示。

自 CSS 出现以来,display 属性就一直是 Web 布局的基石。表 4-1 显示了每个显示值添加到规格的时间。

表 4-1

按 CSS 版本显示属性值 3

|

一级

|

第二级

(修订版 1)

|

三级

|
| --- | --- | --- |
| One thousand nine hundred and ninety-six | Two thousand and eleven | Two thousand and eighteen |
| 街区在一条直线上的列表项目没有人 | 内嵌块桌子内嵌表格表格-行-组表格标题组表格-页脚-组表格-行表格-列-组表格列表格单元格表格标题继承 | 内容流根试车内嵌列表项目 flex 嵌入式-flex 网格嵌入式网格红宝石** |

*人选推荐

** 工作草案

在一条直线上的

考虑到流动内容,在流动布局中,内联元素与文本内联放置。默认情况下,以下元素是内联的:

<a>, <abbr>, <acronym>, <audio> (if it has visible controls), <b>, <bdi>, <bdo>, <big>, <br>, <button>, <canvas>, <cite>, <code>, <command>∗∗, <data>, <datalist>, <del>, <dfn>, <em>, <embed>, <i>, <iframe>, <img>, <input>, <ins>, <kbd>, <keygen>*, <label>, <map>, <mark>, <meter>, <noscript>, <object>, <output>, <picture>, <progress>, <q>, <ruby>, <s>, <samp>, <script>, <select>, <slot>, <small>, <span>, <strong>, <sub>, <sup>, <svg>, <template>, <textarea>, <time>, <u>, <tt>, <var>, <video>, <wbr>4

  • 已弃用

** 废弃

当内容以内联方式显示时,默认情况下,元素从左到右排列,并在宽度允许的情况下并排放置。默认情况下,无论填充和边距如何,元素都将自己与文本基线对齐。如果宽度不允许,内容将在下面换行,如图 4-12 和 4-13 以及清单 4-9 和 4-10 所示。

img/487079_1_En_4_Fig12_HTML.png

图 4-12

内嵌元素

img/487079_1_En_4_Fig13_HTML.jpg

图 4-13

使用边框的元素

body {
  font-size: 24px;
  padding: 36px;
  margin: 0;
}

a {
  padding: 10px;
  outline: dotted 2px grey;
}

code {
  margin: 10px;
  outline: dotted 2px grey;
  outline-offset: 8px;
}

Listing 4-10CSS

<body>
    I am some text.
    <span>I am a span.</span>
    More text goes here.
    <code>I am code.</code>
    And some more text.
    <a href="">I am an anchor tag.</a>
</body>

Listing 4-9HTML

块状元素

也被认为是流动内容,块元素堆叠在另一个之上,除非它们受到另一个属性(如 float)的影响。

以下元素默认为块级元素: 5

<address>, <article>, <aside>, <blockquote>, <details>, <dialog>, <dd>, <div>, <dl>, <dt>, <fieldset>, <figcaption>, <figure>, <footer>, <form>, <h1>, <h2>, <h3>, <h4>, <h5>, <h6>, <header>, <hgroup>, <hr>, <li>, <main>, <nav>, <ol>, <p>, <pre>, <section>, <table>, <ul>

图 4-14 和 4-15 显示了块级元素的默认行为。

img/487079_1_En_4_Fig14_HTML.png

图 4-14

默认块级行为图

默认情况下,块级元素将占据视口的整个宽度。如果应用了宽度,即使元素内联仍有足够的空间,block 元素仍会将其自身放置在前一个元素的下方。参见清单 4-11 和 4-12 。

img/487079_1_En_4_Fig15_HTML.jpg

图 4-15

默认块级行为

html, body {
  font-size: 24px;
  padding: 36px;
  margin: 0;
}

div {
  background: rgba(0, 0, 0, .16);
  height: 50px;
  margin: 20px;
  outline: dotted 1px gray;
  outline-offset: 19px;
  text-align: center;
}

div:first-of-type {
  padding: 20px;
}

div:nth-of-type(2),
div:last-of-type {
  width: 200px;
}

Listing 4-12CSS

<body>
  <div>Content</div>
  <div>Content</div>
  <div>Content</div>
</body>

Listing 4-11HTML

如果一个行内元素被放置在一个块元素之后,行内元素仍然会被放置在块元素之后,如清单 4-13 、 4-14 和图 4-16 所示。

img/487079_1_En_4_Fig16_HTML.jpg

图 4-16

块和内联

html, body {
  font-size: 24px;
  padding: 36px;
  margin: 0;
}

div {
  background: rgba(0, 0, 0, .16);
  height: 50px;
  width: 200px;
}

Listing 4-14CSS

<body>
  <div>Block Content</div>
  <span>Inline Content</span>
</body>

Listing 4-13HTML

内嵌块

内联式数据块利用了数据块和内联式的概念。它的行为类似于一个块元素,但会像内联一样与周围的内容一起流动。内嵌块的一个常见用例是水平导航;参见清单 4-15 和 4-16 以及图 4-17 。

img/487079_1_En_4_Fig17_HTML.jpg

图 4-17

内嵌块

html, body {
  font-size: 24px;
  padding: 36px;
  margin: 0;
}

ul {
  margin: 0;
  padding-left: 0;
  background: lightgray;
}
li {
  list-style-type: none;
  display: inline-block;
  margin: 2rem;
}

a {
  padding: 1rem 2rem;
  background: white;
  border-radius: 2rem;
}

Listing 4-16Inline-Block CSS

<body>
  <nav>
    <ul>
      <li><a href="">Home</a></li>
      <li><a href="">About</a></li>
      <li><a href="">Contact</a></li>
    </ul>
  </nav>
</body>

Listing 4-15Inline-Block HTML

内联、块和内联块元素的默认行为不足以创建经常需要的布局。在 grid 和 flex 等选项于 2018 年推出或 float(直到 1996 年推出)之前, 6 即使数据不是表格,我们也依赖于表格。长期以来,这是创建一些更复杂布局的唯一选择,因为即使 CSS 规范描述了更好的方法,浏览器也不一定支持它们。今天,这种情况不再存在,并且现在完全不赞成将表格用于显示目的,因为它阻止了辅助技术将正在显示的内容正确地传达给用户。一些例外,如电子邮件模板,仍然存在。类似于为什么历史上它们被用于网站的显示目的,大多数电子邮件客户端很少或根本不支持 CSS 布局属性,但是可访问性问题仍然存在,因此应该尽可能避免用于布局的表格。然而,对于一般的 web 使用,使用表格进行布局被认为是不好的形式并且不可访问。我们将讨论三种常用的内容布局模式:浮动、flexbox 和网格。

浮动

与 flex 和 grid 不同,float 不是 display 属性的一部分,而是一个独立的属性。

浮动的一个很好的用例是当一个图形包含在文本中时,允许文本围绕图形流动,如图 4-18 所示。

img/487079_1_En_4_Fig18_HTML.png

图 4-18

浮动图像

然而,使用 float 创建布局要困难得多。让我们看一个使用以下 HTML 的例子(清单 4-17 )。

<body>
  <h1>Flexbox</h1>
  <nav>
    <ul>
      <li>Nav Element</li>
      <li>Nav Element</li>
      <li>Nav Element</li>
    </ul>
  </nav>
  <div class="container">
    <section>
      <div><span>1</span></div>
      <div><span>2</span></div>
      <div><span>3</span></div>
    </section>
    <main>
      <h2>Main Content</h2>
      <p>Lorem ipsum dolor sit amet, consectetur... </p>
      <img src="./image.png" alt="">
      <p>Varius morbi enim nunc faucibus a. Ut placerat... </p>
      <p>Eget duis at tellus at urna condimentum mattis... </p>
    </main>
    <aside>
      <h2>Aside</h2>
      <p>Placerat duis ultricies lacus sed turpis tincidunt id aliquet... </p>
    </aside>
  </div>
</body>

Listing 4-17Example HTML

我们将尝试实现图 4-19 中的布局。

img/487079_1_En_4_Fig19_HTML.jpg

图 4-19

布局示例

从导航开始,我们已经看到了图 4-20 (列表 4-18 和 4-19 )中的问题。

html, body {
  padding: 36px;
  margin: 0;
}

h1 {
  text-align: center;
}
nav {
  border-top: solid 1px gray;
  border-bottom: solid 1px gray;
}
nav ul { padding: 0; }
nav li {
  list-style-type: none;
  width: 33%;
  float: left;
}

Listing 4-18Float: CSS

一旦列表项被浮动,边框就会在它们上面升起,左边栏中的数字会自动出现在它的左边(图 4-20 )。

img/487079_1_En_4_Fig20_HTML.jpg

图 4-20

浮动导航

为了让数字正常工作,必须清除浮点。我们可以使用列表中的display: flow-root来清除浮动。这是一处相当新的房产。历史上,“clearfix”或“group”类(见清单 4-19 )会被添加到列表中。

.clearfix::after {
  content: "";
  clear: both;
  display: table;
}

Listing 4-19Clearfix

继续向下页面,我们可以接近带有浮动的目标布局(图 4-19 )(见清单 4-20 和图 4-21);但是,该列并没有在屏幕底部对齐。当调整窗口大小和内容重排时,将左栏中的数字居中以看起来均匀分布也不起作用。此外,由于填充用于使数字居中,如果内容是为完整的句子而不是数字编辑的,则必须重新计算填充,否则文本将不再居中。

img/487079_1_En_4_Fig21_HTML.jpg

图 4-21

尝试使用浮动的布局

html, body {
  padding: 36px;
  margin: 0;
}

h1 {
  text-align: center;
}
nav {
  border-top: solid 1px gray;
  border-bottom: solid 1px gray;
  display: flow-root;
}
nav ul {
  padding: 0;
  margin: 0;
}
nav li {
  box-sizing: border-box;
  list-style-type: none;
  float: left;
  padding: 1rem;
  text-align: center;
  width: 33.33%;
}

section {
  float: left;
  background: rgba(0, 0, 0, .16);
}
section div {
  box-sizing: border-box;
  color: white;
  background: rgba(0, 0, 0, .50);
  height: 100px;
  width: 100px;
  text-align: center;
  padding: 37px;
  margin-top: 5rem;
  margin-bottom: 5rem;
}

main {
  background: rgba(0, 0, 0, .05);
  box-sizing: border-box;
  padding: 30px;
  float: left;
  width: calc(100vw - 244px - 30%)
}

aside {
  box-sizing: border-box;
  background: rgba(0, 0, 0, .16);
  width: 30%;
  padding: 30px;
  float: right;
}

img {
  width: 100%;
  max-width: 250px;
  float: left;
  padding-right: 20px;
}

Listing 4-20CSS for Figure 4-21

让图片周围的文本工作得非常好,因为这就是 float 的设计目的。然而,布局的其余部分有一些问题。填充以及设置的高度和宽度的组合很可能会使较长的内容在最左边的栏中扩展到其容器之外。同样令人担忧的是,100%不容易被 3 整除,因此根据浏览器决定如何计算每个导航元素的宽度,内容可能会以不应该的方式被推来推去。最后,柱子上的背景并不一致。

出于所有这些原因,这将是一个容易出错且难以维护的布局。为了实现这种布局,并使 UI 流畅,在引入 flexbox 和 grid 之前,就需要使用表格、CSS display: table属性和/或 JavaScript。

flex box(flex box)的缩写形式

从历史上看,在前面的例子中,有两种类型的布局非常困难,包括:

  • 不管其中的内容如何,背景颜色都要对齐的多列

  • 垂直居中内容

今天我们有了 flexbox。解决这两个问题是 flex 真正的亮点。Flexbox 还允许根据内容量动态确定列宽。当创建一个需要控制容器中元素间距的布局时,使用display: flex特别有用。

让我们再次尝试创建我们的布局,这次使用 flexbox(清单 4-21 )。

h1 { text-align: center; }

nav {
  border-top: solid 1px gray;
  border-bottom: solid 1px gray;
}

ul {
  display: flex;
  padding-left: 0;
  justify-content: space-around;
}

.container { display: flex; }

aside {
  background: rgba(0, 0, 0, .16);
  flex-basis: 30%;
  flex-shrink: 0;
  padding: 30px;
}

section {
  background: rgba(0, 0, 0, .5);
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: space-evenly;
}

section div {
  color: white;
  background: rgba(0, 0, 0, .50);
  align-items: center;
  display: flex;
  height: 100px;
  justify-content: center;
  width: 100px;
}

main {
  background: rgba(0, 0, 0, .05);
  padding: 30px;
}

li { list-style-type: none; }
img {
  width: 100%;
  max-width: 250px;
  float: left;
  padding-right: 20px;
}

Listing 4-21Flexbox CSS

剖析前面的布局,我们将 flex 应用于布局的三个区域,三个内容列、导航和最左边的列本身。对于最左边的列和导航,使用 flex 来分别垂直和水平地分布容器中的内容。

弯曲方向

Flexbox 应用于两个轴:主轴和横轴。主轴定义了布局的方向。属性flex-directio n 用在容器上,包括四个选项:rowrow-reversecolumncolumn-reverse,默认为row。这将决定元素显示的顺序和布局的方向(见图 4-22 )。

img/487079_1_En_4_Fig22_HTML.png

图 4-22

柔性盒主轴

默认情况下,Flexbox 会尝试将所有内容放在一行中,但是可以使用flex-wrap属性让内容换行。flex-wrap属性可以取以下任意值(图 4-23 ):

img/487079_1_En_4_Fig23_HTML.png

图 4-23

包装

  • nowrap 这是默认的。所有项目将被放置在主轴线后的一行上,必要时会导致溢出。

  • wrap 项会从上到下换行。

  • wrap-reverse 项将自下而上换行。

通过添加到 row、column 或 wrap,我们可以改变元素的显示顺序。如果我们想单独移动序列中的特定元素,我们可以使用order。属性采用整数,默认情况下为 0。如果一个元素被赋值为 1,并且所有其他元素被设置为缺省值 0,那么它将出现在最后。如果赋值为-1,该元素将出现在开头。因此,顺序基于提供的序列,然后基于使用 order 属性分配给每个元素的值进行加权。

调整内容

为了确定每个元素在主轴上的位置,我们在容器上使用justify-content。可能的值如下:

flex-start 元素放在容器的开头。弹性启动是默认值。

img/487079_1_En_4_Figa_HTML.png

flex-end 元素被放置在容器的末端。

img/487079_1_En_4_Figb_HTML.png

居中 元素被放置在容器的中心。

img/487079_1_En_4_Figc_HTML.png

space-between 元素在容器中均匀分布,容器的边缘与第一个和最后一个项目之间没有空间。

img/487079_1_En_4_Figd_HTML.png

空间环绕 元素在容器中均匀分布,容器边缘与第一个和最后一个项目之间的空间是其他项目之间空间的一半。

img/487079_1_En_4_Fige_HTML.png

空间均匀 元素在容器中均匀分布,边缘与第一个和最后一个元素之间以及元素之间的间距相同。

img/487079_1_En_4_Figf_HTML.png

Inline-Block

Flex-start、middle 和 flex-end 比 inline-block 有一些优势。对于很多用例来说,display: inline-block配合text-align使用可以达到同样的效果;然而,内联块在间距方面有一些复杂性。即使边距设置为 0,元素之间也会出现小间隙。因此,元素的总和等于容器宽度的 100%的布局变得很难创建。让我们看看代码及其输出(清单 4-22 和 4-23 以及图 4-24 )。

例 1

img/487079_1_En_4_Fig24_HTML.jpg

图 4-24

内嵌块间隙

html, body {
  padding: 12px 36px;
  margin: 0;
  font-size: 32px;
}

ul {
  padding-left: 0;
}

li {
  display: inline-block;
  padding: 10px 20px;
  background: grey;
  color: white;
}

Listing 4-23Inline-Block CSS

<body>
  <h1>Example 1</h1>
  <nav>
    <ul>
      <li>inline-block element</li>
      <li>inline-block element</li>
      <li>inline-block element</li>
    </ul>
  </nav>
</body>

Listing 4-22Inline-Block HTML

请注意内联块元素之间的间隙。列表元素上没有边距。弯曲的项目不会受到这种意外行为的影响。

对齐项目和对齐自身

横轴垂直于主轴。回顾我们最初的 flexbox 示例(图 4-24 ,我们有三列内容,左边的数字部分、中间的内容部分和右边的部分。对于它们中的每一个都有相同的长度,由它们各自的背景颜色在底部对齐来表示,我们可以使用align-content属性。这些值如下所示:

拉伸 元素将扩展到容器的可用高度(如果伸缩方向为列,则为宽度)。

img/487079_1_En_4_Figg_HTML.png

对于列来说,这种行为允许弯曲的元素增加大小,类似于表格行,以便包含的所有元素都与数组中最新的元素具有相同的高度。

flex-start 或 start 元素将与容器顶部对齐,类似于vertical-align: top

img/487079_1_En_4_Figh_HTML.png

flex-end 或 end 元素将与容器底部对齐,类似于vertical-align: bottom

img/487079_1_En_4_Figi_HTML.png

中心 元素将对齐容器的中间,类似于vertical-align: middle

img/487079_1_En_4_Figj_HTML.png

基线 元素将与文本基线对齐,类似于vertical-align: baseline

img/487079_1_En_4_Figk_HTML.png

上述属性是在容器上设置的,将应用于中的所有元素。要操作单个元素并使其行为不同于其他元素,我们可以使用align-self。其值与前面列出的align-items的值相同。

弹性基础、弹性增长和弹性收缩

Flex-basis 允许设置元素开始的基本宽度。它们的内容将决定它们是否需要增长或收缩以适应容器中的可用空间。

为了确保内容填满容器中 100%的可用空间,可以应用一个 flex-grow 属性。默认设置为 0,它指定弯曲项目的增长因子。该值基于比率。如果容器的所有同级都具有相同的值,那么它们都将增长相同的量,因此元素的总和等于可用宽度的 100%(如果 flex-direction 设置为 column,则为高度)。否则,它将根据每个弯曲元素上定义的比率进行分配。

Flex-shrink 的工作方式与 flex-grow 类似,只是收缩内容以防止溢出。默认情况下设置为 1,它可以设置为 0,并与 flex-basis 结合使用,以确保弯曲元素具有固定的宽度(或高度,如果将 flex-direction 设置为 column)。

由于能够动态处理所提供的空间,display-flex 使得生成流畅的设计和对齐内容比以往任何时候都更容易,而无需为了显示而使用表格,但它是非常单向的。然而,网格带来了第二维度。

格子

最近引入的还有网格。grid 的亮点在于它让开发人员能够命名各个部分,使代码易于阅读和维护。与 flexbox 一次只处理一个方向不同,grid 允许定义行和列。这些部分可以是名称,如清单 4-24 中所示,或者基于行号和列号,如清单 4-26 中所示。

.container {
 display: grid;
 grid-template-columns: 1fr 1fr 1fr 300px;
 grid-template-rows: 46px auto 36px;
 grid-template-areas:
   "header header header header"
   "main main . sidebar"
   "footer footer footer sidebar";
}

Listing 4-24Grid CSS

Grid-template-columns定义了四列。前三个等宽,最后一个 300 像素。这里使用的“fr”单位以比率的形式表示剩余空间的分数。 7 最后一列将被赋予 300 像素的宽度;其他三个将获得与其宽度相等的剩余空间。

Grid-template-rows定义三行,第一行的高度和最后一行的高度分别为 46 和 36 像素。中间一行设置为 auto,将调整其高度以适应其内容。

Grid-template-areas在刚刚创建的 4 乘 3 的网格上,按行定义命名区域,如图 4-25 所示。

img/487079_1_En_4_Fig25_HTML.png

图 4-25

网格模板区域

所以看看完整的实现,代码看起来和输出看起来如下(清单 4-25 和 4-26 )。图 4-26 显示输出。

html, body {
  padding: 36px;
  margin: 0;
}

header {
  grid-area: header;
  background: rgba(0, 0, 0, .1);
  text-align: center;
  padding: 5px;
}
main {
  grid-area: main;
  background: rgba(0, 0, 0, .2);
  padding: 10px;
}
.sidebar {
  grid-area: sidebar;
  padding: 10px;
  background: rgba(0, 0, 0, .3);
}
footer {
  grid-area: footer;
  background: rgba(0, 0, 0, .5);
  text-align: center;
  color: white;
}
.container {
 display: grid;
 grid-template-columns: 1fr 1fr 1fr 1fr;
 grid-template-rows: 46px auto 36px;
 grid-template-areas:
   "header header header header"
   "main main . sidebar"
   "footer footer footer sidebar";
}

header, footer {
  display: flex;
  align-items: center;
  justify-content: center;
}
header h2, footer h2 {
  margin: 0;
}

Listing 4-26Grid CSS

<body>
  <div class="container">
    <header>
      <h2>Header</h2>
    </header>
    <main>
      <h2>Main</h2>
      <p>Lorem ipsum dolor sit amet, consectetur… </p>
      <p>Quisque faucibus, augue sed varius ornare… </p>
    </main>
    <aside class="sidebar">
      <h2>Sidebar</h2>
      <ol>
        <li>Lorem</li>
        <li>Ipsum</li>
        <li>Dolor</li>
        <li>Sit</li>
        <li>Amet</li>
      </ol>
    </aside>
    <footer>
      <h2>Footer</h2>
    </footer>
  </div>
</body>

Listing 4-25Grid HTML

请注意“.”第二行的grid-template-areas为容器类;这允许三列/第二行网格部分保持空白。

img/487079_1_En_4_Fig26_HTML.jpg

图 4-26

电网输出

每个 UI 元素都被设置为其命名的grid-area。这样做的好处是命名可以遵循被定位容器的目的,使得代码易于阅读和维护。此外,当为了响应性而重新定位元素时,唯一需要更新的属性是grid-template-areas

Grid 还允许使用列号和行号来定义区域。如图 4-27 所示,列的编号从最左边的 1 开始,行的编号从最上面的 1 开始。

img/487079_1_En_4_Fig27_HTML.png

图 4-27

网格行和列

使用与前面相同的 HTML,我们可以通过给每个部分分配grid-rowgrid-column值来获得相同的输出(清单 4-27 )。

html, body {
  padding: 36px;
  margin: 0;
}

header {
  grid-area: header;
  background: rgba(0, 0, 0, .1);
  text-align: center;
  padding: 5px;
  grid-row: 1;
  grid-column: 1 / 5;
}
main {
  grid-area: main;
  background: rgba(0, 0, 0, .2);
  padding: 10px;
  grid-row: 2;
  grid-column: 1;
}
.sidebar {
  grid-area: sidebar;
  padding: 10px;
  background: rgba(0, 0, 0, .3);
  grid-row: 2 / 4;
  grid-column: 4;
}
footer {
  grid-area: footer;
  background: rgba(0, 0, 0, .5);
  text-align: center;
  color: white;
  grid-row: 3;
  grid-column: 1 / 4;
}
.container {
 display: grid;
 grid-template-columns: 1fr 1fr 1fr 1fr;
 grid-template-rows: 46px auto 36px;
}

header, footer {
  display: flex;
  align-items: center;
  justify-content: center;
}
header h2, footer h2 {
  margin: 0;
}

Listing 4-27Grid CSS

Grid-rowgrid-column可以用一个整数(grid-row: 1)或者用一个/ ( grid-row: 1/3)分开的两个整数来定义。当只使用一个整数时,该部分将从指定的行开始,跨越一列或一行,例如前面示例中的main元素。当使用由/分隔的两个整数时,该部分将从第一个整数指定的行开始,到第二个整数指定的行结束,如页脚的grid-column值所示。

与 flexbox 或 table 类似,可以调整内容在节中的位置。可以在 grid 容器上使用 justify-items 属性来确定节或单元格内的内容如何从左到右对齐。其值为开始、结束、中心和拉伸,如图 4-28 所示。拉伸是默认值。

img/487079_1_En_4_Fig28_HTML.png

图 4-28

对齐-项目值

通过使用特定部分的justify-self属性,可以使用相同的值来更改特定单元格的对齐方式。

要指定内容如何在单元格中垂直对齐,我们可以将 align-items 属性分配给 grid 容器。其值与之前的相同,也默认为拉伸(图 4-29 )。

img/487079_1_En_4_Fig29_HTML.png

图 4-29

使用 Align-Items 进行垂直对齐

justify-self类似,相同的值可以与单个部分上的align-self属性一起使用,以允许单元格的行为不同于容器上的默认设置。

就像 flexbox 一样,grid 也有一个 justify-content 和 align-items 属性。它们与 flexbox 具有相同的价值观和工作方式。它们在容器内水平和垂直定位网格单元。当网格本身小于网格容器时,这非常有用。

为了增加单元格之间的空间,可以使用网格间隙。这将决定每行和/或每列之间的间距。分别使用网格-行-间隙和网格-列-间隙来设置它们。例如,grid-gap: 5px 2rem将在每行之间设置 5 个像素的间隙,在每列之间设置 2 个 rems 的间隙。

最后,grid 有一个自动布局算法。当要放置的项目多于 CSS 定义的项目时,或者当一个元素的 grid-column 或 grid-row 值超出容器模板中定义的界限时,就会出现这种情况。使用网格自动流动属性控制自动放置的行为。自动流动可以优化灌装

  • 用行值逐行填充行,并根据需要添加新行

  • 按列,使用列值 填充列并根据需要添加新列

为了让网格填充可能存在的任何间隙,可以将 dense 添加到行和列值中,grid-auto-flow: column dense

网格现在在 evergreen 浏览器中得到更广泛的支持,但在 Internet Explorer 11 等较老的浏览器中存在一些兼容性问题,Internet Explorer 11 目前还没有完全实现该规范。

img/487079_1_En_4_Figl_HTML.gif 使用 Flexbox 或 Grid 时的可访问性

Grid 和 flexbox 提供了随意重新定位和重新排序内容的能力,只需改变一两个属性,而不考虑 HTML 中的顺序。这对于可访问性来说是个问题。

网站内容可访问性指南指出

当内容呈现的顺序影响其含义时,可以通过编程来确定正确的阅读顺序。(A 级)准则 1.3.2 有意义的序列(A 级) 8

当使用 CSS 改变元素的顺序时,确保编程顺序仍然有意义是很重要的。

响应性设计

响应性设计实现的核心是媒体查询。

Media Query

媒体查询允许作者测试和查询用户代理或显示设备的值或特征,而与正在呈现的文档无关。它们用在 CSS @media 规则中,以有条件地将样式应用于文档,以及各种其他上下文和语言,如 HTML 和 JavaScript。

—媒体查询级别 4 9

更具体地说,使用媒体查询允许您根据视口的特性有条件地更改样式。响应式设计中经常使用的是与视口宽度相关的媒体查询,最终目标是为小型移动屏幕和大型桌面显示器以及介于两者之间的任何设备定制布局。为了实现这一技术,在样式将改变的不同视口宽度处选择断点。CSS 可能如清单 4-28 所示。

@media (min-width: 500px) { ... }

Listing 4-28Media Query

其中除非视口的宽度大于 500 像素,否则括号之间的任何内容都不会被应用。宽度范围也可以这样声明(清单 4-29 )。

@media (500px <= width <= 700px) { ... }

Listing 4-29Ranged Media Query

当视口宽度在 500 和 700 像素之间时应用样式的位置。

人们很容易陷入这样的思维陷阱:需要为每个断点重写样式。此外,当从窄到宽布局时,需要覆盖的样式比从宽到窄少得多。这是因为在较窄的布局中,项目比宽布局更容易简单地堆叠。对于大多数用例,设置响应以减少代码量的最简单方法是从窄布局开始,然后随着屏幕变宽而增加。我们来看看这个样本设计的实现(图 4-30 )。

img/487079_1_En_4_Fig30_HTML.png

图 4-30

响应式设计

为了实现这种布局,将使用清单 4-30 中的 HTML。

<body>
  <header>
    <h1>Responsive Design</h1>
  </header>
  <nav>
    <ul>
      <li><a href="">Link 1</a></li>
      <li><a href="">Link 2</a></li>
      <li><a href="">Link 3</a></li>
    </ul>
  </nav>
  <h2>My Items</h2>
  <main>
    <article>
      <h3>Article Title</h3>
      <p>Lorem ipsum dolor sit amet, consectetur elit...</p>
      <a href="">Read More</a>
    </article>
    ...
  </main>
</body>

Listing 4-30Responsive Layout HTML

我们做的第一件事是建立一些不管屏幕大小都适用的基本样式(清单 4-31 )。

html, body {
  margin: 0;
  padding: 0;
}

body {
  box-sizing: border-box;
  font-family: 'Gill Sans', 'Gill Sans MT', ... sans-serif;
  height: 100vh;
  left: 0;
  margin: 0;
  position: absolute;
  top: 0;
  width: 100vw;
}

h1, h2, h3 {
  font-family: Impact, Haettenschweiler, ... sans-serif;
  margin: 0;
}

header {
  background: rgba(0, 0, 0, .1);
  box-sizing: border-box;
  grid-area: header;
  text-align: center;
  padding: .75rem;
}

nav {
  background: #c6c6c6;
  grid-area: nav;
}
nav ul {
  margin: 0;
  padding: 0;
  display: flex;
  justify-content: space-evenly;
}
nav li { list-style-type: none; }
nav a {
  display: block;
  padding: 1rem;
}

h2 {
  background: #c6c6c6;
  grid-area: title;
  padding: .5rem 1rem;
}

main {
  padding: 1rem;
  grid-area: main;
  overflow: auto;
}
article {
  background: #e9e9e9;
  border-left: solid 2.5rem gray;
  padding: 1rem;
  margin-bottom: 1rem;
}
article a {
  display: block;
  text-align: right;
}

Listing 4-31Base Styles

然后我们可以为每个断点添加布局特定的信息(清单 4-32 和 4-33 )。

/* Desktop layout */
@media (min-width: 500px) {
  body {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto;
    grid-template-areas:
      "header"
      "nav"
      "title"
      "main";
    height: auto;
    overflow: auto;
  }

  main { column-width: 250px ; }
  article { break-inside: avoid; }
  h2 { background: none; }
}

Listing 4-33Desktop CSS

/* Mobile layout */
body {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 4rem auto auto 3rem;
  grid-template-areas:
    "header"
    "title"
    "main"
    "nav";
  height: 100vh;
  overflow: hidden;
}

Listing 4-32Mobile CSS

注意,在清单 4-33 中,为桌面用户重新调整布局只需要很少的 CSS。这是因为基本样式已经应用,不需要复制。在这里,我们还可以看到命名区域对于网格布局的优势,以及组织它们以实现正确布局的便利性。

摘要

元素受制于盒子模型,盒子模型规定了元素的宽度、填充、边距和边框的行为方式。当这些元素放在一起时,就形成了一个布局。有多少种布局就有多少种方法,但是每种技术都有自己的优点和缺点。我们已经研究了 float、flexbox 和 grid,以及针对响应性布局的媒体查询。

在下一章,我们将会看到 CSS 没有像预期的那样工作的场景,特别关注浏览器之间的差异。

五、兼容性和默认值

在编写 CSS 时,大多数开发人员很快就会意识到,当相同的代码在不同的浏览器中运行,甚至在不同的设备上运行时,它们的行为会有所不同。本章介绍了浏览器的差异和处理跨浏览器兼容性的技术。

浏览器支持

测试布局时,在多种浏览器中测试应用程序很重要,因为它们并不都使用相同的布局和 JavaScript 引擎,这导致它们解释代码的方式有所不同。表 5-1 列出了一些常用的浏览器及其引擎。

表 5-1

浏览器技术

|

浏览器

|

布局引擎

|

JavaScript 引擎

|
| --- | --- | --- |
| 铬 | 眨眼,WebKit | V8 |
| 火狐浏览器 | 壁虎,量子 | 蜘蛛猴 |
| 微软公司出品的 web 浏览器 | 三叉戟 | 脉轮,JScript |
| 微软边缘 | EdgeHTML,WebKit(在 IOS 上),Blink(在 Android 上)——切换到 Chromium 平台 1 | 人体精神力量的中心 |
| 歌剧 | 闪烁(铬) | 铬 V8 |
| 旅行队 | 网络工具包 | 硝基 |

布局引擎负责页面的外观。它根据 CSS 决定视图应该如何布局、绘制和动画。渲染细节见第一章。此外,许多都是开源的,由不同的团体和机构维护,允许任何给定规范的实现和状态存在差异。

例如,scroll-snap-type CSS 属性是 Scroll Type 模块的一部分,它的第一个公开草案于 2015 年 3 月发布,现在是推荐的候选对象,它在不同的浏览器中具有非常不同的支持和实现。这就导致了行为差异。具体浏览器支持详情见表 5-2 。

表 5-2

浏览器版本 3 支持滚动捕捉

| ![img/487079_1_En_5_Figa_HTML.png](https://gitee.com/OpenDocCN/vkdoc-html-css-pt2-zh/raw/master/docs/arch-css/img/487079_1_En_5_Figa_HTML.png) |

随着时间的推移,很明显使用这个属性会在不同的浏览器上产生不同的结果。此外,浏览器包括 CSS 默认设置,这些也略有不同。

浏览器默认值

当编写没有应用 CSS 的 HTML 时,某些标签具有默认样式,例如 header 标签(参见图 5-1 )。

img/487079_1_En_5_Fig1_HTML.jpg

图 5-1

默认样式

然而,浏览器不使用相同的样式表,因此没有相同的默认设置。虽然大多相似,但也有一些细微的差别。例如,Textarea 在 Safari 和 Firefox 中的表现会有所不同(见图 5-2 和 5-3 )。

img/487079_1_En_5_Fig3_HTML.jpg

图 5-3

旅行队

img/487079_1_En_5_Fig2_HTML.jpg

图 5-2

火狐浏览器

注意文本区域中的默认字体;在 Firefox 中是等宽字体,而在 Safari 中是无衬线字体。对齐方式也略有不同。在 Firefox 上,textarea 与文本的基线对齐,而在 Safari 上,textarea 则在基线之上。当试图让一个设计在不同的浏览器和版本中具有相同的外观和行为时,这些细微的差异可能会令人恼火。

解决这个问题的一个可靠的方法是手动设置默认值,这样所有的浏览器都可以运行相同的基本样式。虽然这没有解决兼容性差异,但是它将解决细微的非故意行为差异,例如前面概述的差异。

CSS 重置

CSS reset 是一个文件,它接受浏览器对元素设置的所有默认值,并“重置”它们。目标是获取元素样式,并使它们都达到相同的一致基线,以减少或消除浏览器之间存在的不一致。有很多选择,但最常用的是 Eric Meyer(见表 5-3 ),他是 CSS reset 的先驱之一。无论你使用哪一个,都没有一个尺寸适合所有人,它可能需要根据你的特定项目进行定制。

表 5-3

CSS 重置

|

参考

|

|
| --- | --- |
| 工程地点 | https://meyerweb.com/eric/tools/css/reset/index.html |
| 样式表 | https://meyerweb.com/eric/tools/css/reset/reset.css |

使标准化

Normalize 是尼古拉斯·加拉格尔和乔纳森·尼尔在 2016 年 8 月发表的一个项目。它专注于修复浏览器之间的已知差异。这种方法与 CSS 重置完全不同,CSS 重置旨在通过扁平化默认样式来防止差异。Normalize 保留默认值。通过将 normalize 添加为项目中要加载的第一个 CSS,使其成为要导入的第一个样式表,或者将其作为要应用的第一个 CSS 包含在项目的 CSS 中,这些变化已经得到处理,并且焦点可以转移到实现布局上,而不是与浏览器之间的细微差异作斗争(关于在哪里找到 normalize,请参见表 5-4 )。值得指出的是,许多 CSS 框架和库,比如 Bootstrap,已经包含了某种形式的规范化。值得仔细检查正在使用的任何 UI 库或框架是否已经考虑到差异,以防止不必要的膨胀。

表 5-4

使标准化

|

参考

|

|
| --- | --- |
| 工程地点 | http://necolas.github.io/normalize.css/ |
| GitHub 存储库 | https://github.com/necolas/normalize.css |
| 新公共管理理论 | www.npmjs.com/package/normalize.css |
| 加拿大 | https://yarnpkg.com/en/package/normalize.css |
| 样式表 | https://necolas.github.io/normalize.css/latest/normalize.css |

尽管规范化基本样式解决了 CSS 默认值的差异,但它并没有解决实现或支持方面的差异。

Note

Normalize 和 reset 没有得到任何形式的认可,任何包的质量和相关性都可能会发生快速变化。请研究您打算使用的任何依赖项。

浏览器兼容性

跨浏览器兼容性,确保 UI 在多个浏览器上看起来一样,是 CSS 中最难做到的事情之一。有多种方法可以解决这个问题,它们经常相互结合使用。

供应商前缀

当浏览器的功能仍处于试验阶段或非标准时,浏览器通常使用特定于供应商的前缀来提供这些功能。虽然这可能有助于在用户代理之间达到相似性,但是在生产中使用依赖于供应商前缀的 CSS 不是一个好主意,因为实现是实验性的,可能不符合规范。因为历史上开发人员一直在产品中使用这些前缀,所以浏览器越来越倾向于将非标准的和实验性的特性放在特性标志之后,以结束这种做法;然而,许多仍在使用中(见表 5-5 )。 4

表 5-5

供应商前缀

|

前缀

|

浏览器

|
| --- | --- |
| -网络工具包- | 基于 WebKit 的浏览器(Chrome、Safari 等。) |
| 蚊子 | 火狐浏览器 |
| 用作复合形式的末尾元音 | Opera 的 WebKit 前版本 |
| -女士- | Internet Explorer 和 Microsoft Edge |

例如,Internet Explorer 11 (IE)就有一个非标准的网格实现。其实现基于 2011 年 4 月 7 日的工作草案,而非候选人推荐。因此,为了让网格在 IE 中工作,必须使用供应商前缀。然而,即使使用了前缀,行为仍然不同。在 IE 中,明确定位网格中的每个元素是必要的,但在其他浏览器中则不然,它们将自己放置在下一个可用空间中。此外,当前规范的某些方面,如grid-gap,根本就不存在。清单 5-2 和 5-3 展示了使用 grid 时在 IE 和 Firefox 中实现相同布局的代码。两者都将使用相同的 HTML(清单 5-1 )。它们各自的输出如图 5-4 和 5-6 所示,而图 5-5 显示的是没有厂商前缀的 IE。

<body>
  <div class="grid-container">
    <aside>My Aside</aside>
    <section>Section 1</section>
    <section>Section 2</section>
    <section>Section 3</section>
    <section>Section 4</section>
  </div>
</body>

Listing 5-1Grid HTML

html, body {
  padding: 36px;
  margin: 0;
}

.grid-container {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-template-rows: 5rem 5rem;
  grid-gap: 1rem;
}

aside {
  grid-row: 1/3;
  background: lightgray;
}

section {
  border: solid 1px gray;
}

Listing 5-2Grid Without Vendor Prefixes

img/487079_1_En_5_Fig4_HTML.jpg

图 5-4

Firefox 中的网格

当在 IE 中运行清单 5-2 中的相同代码时,不会呈现网格,元素只是简单地堆叠在一起(图 5-5 )。

img/487079_1_En_5_Fig5_HTML.jpg

图 5-5

IE 中没有供应商前缀的网格

这是因为显示值 grid 不存在。要在 Internet Explorer 中访问网格功能,需要使用供应商前缀。

html, body {
  padding: 36px;
  margin: 0;
}

.grid-container {
  margin: -.5rem;
  display: -ms-grid;
  -ms-grid-columns: 1fr 1fr 1fr;
  -ms-grid-rows: 5rem 5rem;
}

aside {
  background: lightgray;
  -ms-grid-row-span: 2;
  margin: .5rem;
}

section {
  border: solid 1px gray;
  margin: .5rem;
}
section:nth-of-type(1) {
  -ms-grid-column: 2;
  -ms-grid-row: 1;
}

section:nth-of-type(2) {
  -ms-grid-column: 3;
  -ms-grid-row: 1;
}
section:nth-of-type(3) {
  -ms-grid-column: 2;
  -ms-grid-row: 2;
}
section:nth-of-type(4) {
  -ms-grid-column: 3;
  -ms-grid-row: 2;
}

Listing 5-3Grid with Internet Explorer Vendor Prefixes

使用-ms供应商前缀并用网格间隙代替边距,可以实现相同的布局(图 5-6 )。

img/487079_1_En_5_Fig6_HTML.jpg

图 5-6

IE 中带有供应商前缀的网格

后退

当浏览器不支持某个属性时,解决供应商前缀的一个更好的方法是创建一个后备。当浏览器遇到它不支持的属性或值时,它将忽略该属性或值,因此,将保持以前设置的值。如果元素没有预先设置的值或者没有继承值,将使用默认值。

例如,(在撰写本文时)cross-fade 有一个实验版本,在 Safari 中位于供应商前缀(-webkit)之后,在 Firefox 中不受支持。要开始使用它,可以创建一个后备。清单 5-4 和 5-5 显示了交叉淡入淡出及其回退的使用(图 5-8 显示了期望的输出)。

html, body {
  box-sizing: border-box;
  padding: 36px;
  margin: 0;
}

.container {
  background-image: url(child.png);
  background-repeat: no-repeat;
  background-size: contain;
  background-position: bottom;
  background-image: -webkit-cross-fade(url(beach.png), url(child.png), 50%);
  background-image: cross-fade(url(beach.png) 50%, url(child.png) 50% );
  box-sizing: border-box;
  padding: 1rem;
  height: 30rem;
  max-width: 100%;
  width: 100%;
}

Listing 5-5Cross-Fade Fallback CSS

<body>
  <div class="container"></div>
</body>

Listing 5-4Cross-Fade Fallback HTML

首先,设置一个背景图像,然后使用供应商前缀通过交叉淡入淡出覆盖它,最后,通过标准交叉淡入淡出再次覆盖它。不支持交叉淡入淡出或供应商前缀版本的浏览器,如 Firefox(图 5-7 ),将只显示背景图像。

img/487079_1_En_5_Fig7_HTML.jpg

图 5-7

后退到背景图像

支持厂商前缀的浏览器,如 Safari(图 5-8 ),将显示实验版本。

img/487079_1_En_5_Fig8_HTML.jpg

图 5-8

使图像交替淡变

最后,支持最终版本的浏览器将显示规范定义的交叉渐变。

支持 At 规则

@supports at-rule 允许检查是否支持特定的属性和值对,从而允许相应地定制用户体验。除了 IE 之外,这个功能一般都得到很好的支持。当属性受支持时,表达式@supports(property:value {}返回 true,而当属性不受支持时,@supports not (property:value){}返回 true。只有当选择器返回 true 时,才会应用选择器中的样式。这些可以用andor操作符连接起来,以创建新的表达式。一般来说,最好使用@supports来逐步增强新特性,而回退可以用来提供与旧浏览器的向后兼容性。

为了看到@supports的实际效果,让我们看看背景滤镜,它在 Opera 中有效,但在 Firefox 中无效。清单 5-6 和 5-7 显示了使用@supports来创建使用支持的条件样式。

html, body {
  padding: 36px;
  margin: 0;
}

.container {
  background-image: url('art.png');
  padding: 1rem;
}
p {
  background-color: rgba(255, 255, 255, 0.6);
  backdrop-filter: blur(20px);
  margin: 5rem;
  padding: 1rem;
}

@supports not (backdrop-filter: blur(20px) ) {
  p {
    background-color:white;
  }
}

Listing 5-7Cross-Fade Fallback CSS

<body>
  <div class="container">
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing</p>
  </div>
</body>

Listing 5-6Cross-Fade Fallback HTML

支持backdrop-filter时,段落背景模糊,不透明度为 60%(图 5-9 )。如果不是,段落背景被设置为完全不透明的白色,以增加从模糊中获得的易读性(图 5-10 )。

img/487079_1_En_5_Fig10_HTML.jpg

图 5-10

撤退

img/487079_1_En_5_Fig9_HTML.jpg

图 5-9

backdrop-filter

项目默认值

重置浏览器默认值有助于创建跨浏览器的一致性。创建应用程序默认值有助于创建应用程序的一致性。这在使用基于组件的架构时尤其重要。将主题从布局中分离出来也有助于一致性。可以在元素本身和基类上设置样式,以便在整个应用程序中重用。如果改变主题,这些改变只需要在一个地方更新。此外,当创建新视图时,唯一关心的是布局,因为主题已经被考虑了。清单 5-8 和 5-9 是一个示例摘录;图 5-11 显示了输出。

img/487079_1_En_5_Fig11_HTML.jpg

图 5-11

主题

body {
  --border: solid 1px rgba(0, 0, 0, .2);
  --dark: rgba(0, 0, 0, .87);
  --light: rgba(255, 255, 255, .87);
  --shadow: box-shadow: 5px 5px 5px var(--dark);
  color: rgba(0, 0, 0, .87);
  font-family: sans-serif;
}

h1 { font-family: cursive; }

a {
  font-size: .75rem;
  font-variant: small-caps;
  text-decoration: none;
}

button {
  background: none;
  border: var(--border);
  border-radius: 45px;
  box-shadow: var(--shadow);
  box-sizing: border-box;
  font-size: .75rem;
  font-variant: small-caps;
  padding: .5rem 1rem;
}

.actions {
  align-items: center;
  border-top: var(--border);
  display: flex;
  justify-content: flex-end;
  margin-top: 1rem;
}
.actions > * { margin-left: 1rem;}

.card {
  border: var(--border);
  border-radius: 3px;
  margin-bottom: 1rem;
}
.card > div { padding: 1rem; }
.card .header {
  background: rgba(0, 0, 0, .87);
  color: rgba(255, 255, 255, .87);
}

/*  Layout */
.container {
  column-width: 30rem;
}

Listing 5-9Default Styles CSS

<body class="view">
  <h1>Theme</h1>
  <div class="container">
    <div class="card">
      <div class="header">Header</div>
      <div class="body">
        <p>Lorem ipsum dolor sit amet, ... </p>
      </div>
      <div class="actions">
        <button>My Button</button>
        <a>My Link</a>
      </div>
    </div>
    <div class="card"> ... </div>
    <div class="card"> ... </div>
    </div>
</body>

Listing 5-8Default Styles HTML

通过设置变量、元素的默认样式,并创建默认的容器类,如清单 5-9 中的.card类,可以创建一个主题。可以作为主题的一部分包含的属性是围绕外观和感觉的东西,比如颜色、版式、边框、填充等等。从那里开始,创建视图变得更加容易,因为剩下的主要问题是布局。通过设置默认主题,即使在基于组件的架构中,外观和感觉,或者品牌,也可以保持一致。此外,更新主题可以像更改自定义属性值一样简单。

摘要

这一章讲述了浏览器的差异和在它们之间标准化 CSS 的技术。还讨论了处理不同浏览器中 CSS 支持差异的技术。最后,主题化被提出。下一章将着眼于使用过渡和动画来支持用户交互。

六、交互和过渡

当我们想到 HTML 和 CSS 时,我们通常会想到“静态”当想到交互或动画时,JavaScript 更常出现在脑海中。然而,CSS 包括几个特性,这些特性允许作为用户交互的结果来操纵元素。在这一章中,我们将看看如何使用 CSS 来响应用户交互,以及如何使用动画和过渡来支持这些交互。

用户交互响应

在 CSS 中响应用户交互最常用的方式之一是使用伪元素:hover:focus和:active

当使用定点设备与元素交互时,伪元素:hover匹配。最常见的是,当用户将鼠标悬停在元素 1 上时,如链接或按钮。这可以用来给用户一个可视的指示,指示该元素可以被交互。

当一个元素获得焦点时,比如使用键盘或者被点击,就会触发:focus伪类。焦点是很重要的,因为它向用户提供了一个视觉指示器,指示他们当前正在交互或者将要交互的元素。当输入字段处于焦点时,改变输入字段的边框样式将告诉用户他们将要输入哪个字段,这对于用户定位他们当前在页面中的位置非常有帮助。并非所有元素都可以获得焦点。除了 video 元素之外,按钮、锚标记和表单项(如 input 和 select)是唯一可以获得焦点的元素,而无需向元素添加tabindex属性。

当一个元素被激活时,比如一个按钮被按下或者一个链接被点击,伪元素被触发。按钮样式的变化,例如在按下物理按钮时消除阴影,反映了按下物理按钮这一动作在现实世界中的预期。尽管用户可能无法清楚地说出原因,但像这样的小交互会让用户感觉交互更自然。

img/487079_1_En_6_Figa_HTML.gif Accessibility and Focus

当应用焦点时,大多数浏览器在元素周围会有默认行为。如果压缩默认行为,则需要重新应用一些焦点的视觉指示,以便用户可以在视觉上将处于焦点的元素与其他元素区分开来。 2 此外,焦点不应该改变上下文、功能、意义或可操作性。 3 4

通过交互,可以设置响应,比如改变元素的外观、大小甚至位置。如果动画提供了将要发生的变化的信息,那么向视觉变化添加过渡将有助于用户理解所应用的变化。例如,当展开一个可折叠部分时,动画显示可折叠部分的打开将帮助用户保持他们在页面中的位置,特别是因为下面的内容将被移动到不同的位置,可能在视口之外。

当响应 CSS 触发的事件(如悬停、聚焦或活动)时,在 CSS 中保留相关的转换比使用 JavaScript 更容易维护。这使得触发和反应保持在一起,并且它们的关联保持清晰和明显。这有助于在样式表中保留可视说明。

改变

创建过渡和动画时,虽然不是必需的,但经常使用 CSS transform 属性。Transform 允许使用 CSS 样式的元素在二维空间中进行转换。变换函数基于变换矩阵。matrix()函数是matrix3d()的简写,它采用六个参数 a、b、c、d、tx 和 ty,在图 6-1 中以粗体显示。

img/487079_1_En_6_Fig1_HTML.png

图 6-1

变换矩阵

参数 a、b、c 和 d 描述线性变换,tx 和 ty 描述要应用的平移。CSS 提供了基于前面矩阵的变换函数来操作元素,如平移、缩放、旋转、倾斜和透视。使用translate()函数来改变一个项目的位置,比如将某个东西滑动到视图中,通常会比操纵它的位置更有效。对于scale()来说,改变一个元素的高度或宽度也是一样的,比如展开或折叠一个菜单或手风琴。rotate()功能常用于微电影;继续 accordion 示例,它可用于旋转 accordion 标题中的箭头或插入符号,以区分相关面板是打开的还是关闭的。当面板被打开时,箭头可以同时旋转,以告知用户所述面板的状态。虽然看起来微不足道,但像这样的小细节,如果信息丰富,可以帮助用户定位和理解他们正在看什么和正在发生什么。关于转换功能的细节可在表 6-1 中找到。

表 6-1

转换函数

|

功能

|

描述

|

尺寸

|
| --- | --- | --- |
| matrix() | matrix3d()的简写。参见前面的描述。需要六个参数。 | 2D |
| matrix3d() | 三维上的线性变换和平移。参见前面的矩阵描述。取 16 个值。 | 三维(three dimension 的缩写) |
| translate(tx, ty) | 通过向量进行平移,其中 x 是第一个平移值,y 是第二个平移值。要单独操作 x 轴或 y 轴,可以使用 translateX(tx)和 translateY(ty)。 | 2D |
| translate3d(tx, ty, tz) | 与 translate()相同,但在三维空间中。TranslateZ(tz)可用于平移 z 索引上的元素。此 tz 值不能是百分比,它必须是长度。 | 三维(three dimension 的缩写) |
| scale(sx, sy) | 缩放向量,其中x缩放高度,y缩放宽度,初始值为 1。要单独缩放高度或宽度,可以使用 scaleX(sx)和 scaleY(sy)。 | 2D |
| scale3d() | 与 scale()相同,但是是三维的。ScaleZ(tz)可用于平移 z 索引上的元素。 | 三维(three dimension 的缩写) |
| )``) | 将元素从变换原点旋转所提供的角度。 | 2D |
| rotate3d(x, y, z, a) | 围绕三维空间中的固定轴旋转元素,其中xyz描述旋转轴,a描述旋转角度。 | 三维(three dimension 的缩写) |
| skew(``x,``y) | 根据 x 轴和 y 轴上提供的角度扭曲元素。要按轴倾斜元素,可以使用 skewX(≈x)和 skewY(≈y)。 | 2D |
| perspective(z) | 为三维元素提供透视,其中 0 是默认值。当 z 增大时,元素变大,当 z 减小时,元素缩小。 | 三维(three dimension 的缩写) |

过渡

当元素的样式改变时,过渡允许从初始状态到新状态的转变在视觉上是平滑的。顾名思义,transition 属性控制值如何随时间从一种状态变化到另一种状态的可视化方面。

transition 属性是以下内容的简写属性:属性、持续时间、计时功能和延迟。其语法在清单 6-1 中描述,其属性在表 6-2 中定义。??

transition: property duration timing-function delay;

Listing 6-1Transition Property Shorthand Syntax

表 6-2

转换属性值

|

值名

|

行为

|

基础资料

|
| --- | --- | --- |
| transition``-property | 定义转换将影响的属性 | 全部 |
| transition``-duration | 定义完成过渡需要多长时间 | 0s |
| transition``-timing``-function | 定义在过渡期间如何应用值的加速度路缘 | 缓解 |
| transition``-delay | 定义过渡开始前的延迟时间 | 0s |

清单 6-2 和 6-3 显示了悬停过渡。

html, body {
  padding: 36px;
  margin: 0;
}

a {
  align-items: center;
  background: gray;
  border: solid 1px white;
  color: white;
  display: flex;
  font-size: 36px;
  height: 100px;
  justify-content: center;
  text-decoration: none;
  transition: all 250ms ease-in-out;
}

a:hover {
  background: white;
  border-color: gray;
  color: gray;
  border-radius: 45px;
}

Listing 6-3CSS for Transition Example

<body>
  <a href="">
    <span>Transitions</span>
  </a>
</body>

Listing 6-2HTML for Transition Example

在前面的列表中,将鼠标悬停在链接上,会导致背景色、边框颜色、字体颜色和边框半径在 250 毫秒内逐渐变化(参见图 6-2 )。

img/487079_1_En_6_Fig2_HTML.png

图 6-2

一段时间内的动画代码输出

User Experience

当一个动作被执行时,通过增强元素之间的关系,过渡可以是一个很好的方式来引导用户通过应用程序。然而,为了实现这个目标,动画应该是信息性的聚焦性的表现性的6 对于较小、不太复杂的动画,动画应该持续 200 到 500 毫秒,或者在较小的屏幕上,在 200 到 300 毫秒的范围内。 7

关键帧动画

与用户触发事件时只能发生一次的过渡不同,动画可以无限期重复。当一个元素被添加到 DOM 时,比如一个元素从display:nonedisplay:block,它们也可以被应用。打开菜单时可能会出现这种情况。菜单项对用户来说是隐藏的,它们需要滑动到视图中,而不是突然显示出来。通过动画显示菜单元素,用户隐含地理解了菜单项的来源。动画还提供了对动画步骤的更多控制,允许比过渡更复杂。通过动画中的百分比,关键帧规则设置何时需要发生什么变化。清单 6-4 和 6-5 显示了使用关键帧的示例。

img/487079_1_En_6_Fig3_HTML.png

图 6-3

一段时间内的动画代码输出

html {
  padding: 0;
  margin: 0;
}

body {
  box-sizing: border-box;
  padding: 36px;
  margin: 0;
}

body > div {
  box-sizing: border-box;
  margin-bottom: 3rem;
}

@keyframes myAnimation {
  0% {
    background: gray;
    border-color: white;
    color: white;
    border-radius: 0px;
    transform: scale(0);
  }
  25% {
    transform: rotate(5deg) scale(.25);
  }
  50% {
    transform: rotate(-10deg) scale(.5);
  }
  75% {
    transform: rotate(35deg) scale(.75);
  }

  100% {
    background: white;
    border-color: gray;
    color: gray;
    border-radius: 45px;
    transform: rotate(0) scale(1);
  }
}

.animations {
  animation: myAnimation 500ms ease-in-out 1;
  background: white;
  border: solid 1px gray;
  border-radius: 45px;
  box-sizing: border-box;
  color: gray;
  font-size: 2rem;
  padding: 2rem;
  text-align: center;
  width: 100%;
}

Listing 6-5Keyframes CSS

<body>
  <div class="animations">Animations</div>
</body>

Listing 6-4Keyframes HTML

背景颜色、边框颜色、颜色、边框半径和比例仅在 0%和 100%时定义,因此是插值的。元素将以每个百分比旋转到指定的角度。即使 100%没有指定旋转角度,在动画结束时,元素会将其旋转设置为元素上设置的值,即 0。

为了触发关键帧,使用了 animation 属性(参见清单 6-6 )。动画属性最多可以取七个值:名称、持续时间、计时功能、延迟、迭代计数、方向和填充模式(详见表 6-3 )。

表 6-3

动画属性值

|

值名

|

行为

|

基础资料

|
| --- | --- | --- |
| animation-name | 定义动画将使用的关键帧 at-rule | 没有人 |
| animation-duration | 定义动画需要多长时间才能完成 | 0s |
| animation-timing``-function | 定义在动画过程中如何应用值的加速度路缘 | 缓解 |
| animation-delay | 定义动画开始前的延迟时间 | 0s |
| animation-iteration``-count | 定义动画播放的次数 | one |
| animation-direction | 定义动画应该向前、向后播放,还是向前和向后切换 | 标准 |
| animation-fill-mode | 定义动画完成前后如何将样式应用于目标 | 没有人 |

animation: name duration timing-function delay iteration-count direction
           fill-mode;

Listing 6-6Animation Property

另一个可以用于动画的属性是animation-play-state,它允许开发者暂停和开始动画。继续播放时,动画将从暂停的地方重新开始,而不是从序列的开头开始。animation-play-state的默认值是running。然而,它需要被单独定义为自己的属性,而不是清单 6-4 中描述的动画速记的一部分。给予用户暂停动画的能力,特别是如果动画对于理解应用程序的内容或状态是不必要的,可以从根本上提高应用程序的可用性。例如,当考虑自动前进的转盘时,添加暂停面板自动递增的能力将允许用户控制他们查看内容的速度。

当从 DOM 中移除一个对象时,也可以使用动画,例如当添加一个显示值 none 时,但是因为display:none属性将在动画结束之前应用和完成,所以这不能仅用 CSS 来完成。如果在关闭菜单时,display:none被添加到菜单项,不管元素上设置的任何动画或过渡,菜单将突然消失,因为在菜单项被隐藏之前动画没有时间运行。为了解决这个问题,JavaScript animationend事件与 CSS 一起使用来监听动画状态。animationend将在动画完成时触发,此时display:none可以添加到需要隐藏的元素中(参见清单 6-7 和 6-8 )。

@keyframes roll {
  0%   { transform: translateX(-75vw) rotate(-360deg);  }
  100% { transform: translate(0) rotate(0)}
}

@keyframes roll-reverse {

  0% { transform: translate(0) rotate(0)}
  100%   { transform: translateX(-75vw) rotate(-360deg);  }
}

.animation-container {
  background: linear-gradient(lightgrey, grey);
  border-radius: 50%;
  display: none;
  height: 100px;
  margin: 1rem auto;
  width: 100px;
}

.show {
  display: block;
  animation: roll 1s cubic-bezier(0.280, 0.840, 0.420, 1);
}

.close {
  display: block;
  animation: roll-reverse 1s cubic-bezier(0.280, 0.840, 0.420, 1);
}

Listing 6-8Animation End Event CSS

<body>
  <div class="show-hide">
    <button onclick="toggleAnimation()" id="button">
      Show
    </button>
    <div
      class="animation-container"
      id="animationContainer">
    </div>
  </div>

  <script>
    function showContainer() {
      animationContainer.classList.add('show');
    }

    function hideContainer() {
      animationContainer.addEventListener('animationend', cleanup);
      animationContainer.classList.replace('show', 'close');
    }

    function cleanup() {
      animationContainer.classList.remove('close');
      animationContainer.removeEventListener('animationend', cleanup);
    }
  </script>
</body>

Listing 6-7Animation End Event HTML and JavaScript

当元素被“关闭”或隐藏时,首先添加一个带有退出动画的类。一旦动画结束,animationend事件监听器被触发,只有这时 display 属性值才能被更改为 none。使用transitionend事件监听器可以实现相同的转换。添加和删除类,而不是在 JavaScript 中处理关闭动画,有助于在 CSS 样式表中保持与显示相关的逻辑,增加可维护性并保持关注点的分离。

计时功能

无论是创建过渡还是动画,要定义的一个公共值是计时函数。它决定了值在动画完成所需时间内的变化速度。计时有助于使动画感觉更自然,并更紧密地反映物理世界的相互作用。当制作一个弹跳球的动画时,人们会期望球落地后加速。如果动画是线性的,球总是以相同的速度移动,动画看起来会关闭。有两种特定类型的计时功能可用。

缓解功能

缓动函数基于以法国工程师皮埃尔·贝塞尔命名的贝塞尔曲线定义平滑过渡。曲线是参数化的, 8 和三次变量由四个点定义:P 0 ,P 1 ,P 2 ,P 3 。P 0 和 P 3 分别定义曲线的起点和终点。P 1 和 P 2 代表赋予曲线形状的控制点。每个点由(x,y)坐标定义。

CSS cubic-bezier在代表动画初始和最终状态的(0,0)和(1,1)固定点预定义 P 0 和 P 3 。剩下要定义的是 P 1 和 P 2 ,它们的 x 值需要保持在[0,1]范围内,而 y 值可能存在于边界框之外。

CSS 函数如下:cubic-bezier(x1, y1, x2, y2)

虽然时序可以自定义,但为了方便起见,CSS 包括命名的常见时序函数,包括线性、缓入、缓出和缓出(见表 6-4 )。

表 6-4

命名缓动功能 9

|

名字

|

公式

|

曲线

|
| --- | --- | --- |
| 线性 | cubic-bezier(0.0, 0.0, 1.0, 1.0) | img/487079_1_En_6_Figb_HTML.gif |
| 缓和 | cubic-bezier(0.25, 0.1, 0.25, 1.0) | img/487079_1_En_6_Figc_HTML.gif |
| 渐强 | cubic-bezier(0.42, 0.0, 1.0, 1.0) | img/487079_1_En_6_Figd_HTML.gif |
| 缓进缓出 | cubic-bezier(0.42, 0.0, 0.58, 1.0) | img/487079_1_En_6_Fige_HTML.gif |
| 缓出 | cubic-bezier(0.42, 0.0, 0.58, 1.0) | img/487079_1_En_6_Figf_HTML.gif |

要创建弹跳效果,一个或两个 y 值应设定在[0,1]范围之外。为此,需要编写一个自定义函数,如下面的函数:cubic-bezier(0, 0.71, 0.64, 1.23)

曲线绘制在图 6-4 中。

img/487079_1_En_6_Fig4_HTML.png

图 6-4

样本反弹曲线

步进函数

虽然目前没有很好地支持跨浏览器,但也可以使用步进函数来代替曲线,该函数将动画划分为相等的时间段。两个值用于定义动画的计时:步数(n)和步位置(见表 6-5 )。语法如下:

 animation-timing-function: steps(n, step-position);

表 6-5

命名步进功能 10

|

名字

|

功能

|

步伐

|
| --- | --- | --- |
| 步进启动 | steps(1, start) | img/487079_1_En_6_Figg_HTML.gif |
| 步骤结束 | steps(1, end) | img/487079_1_En_6_Figh_HTML.gif |
| 跨接启动 | steps(3, jump-start) | img/487079_1_En_6_Figi_HTML.gif |
| 跳转结束 | steps(3, jump-end) | img/487079_1_En_6_Figj_HTML.gif |
| 无跳转 | step(3, jump-none) | img/487079_1_En_6_Figk_HTML.gif |
| 跳转-两者都有 | step(3, jump-both) | img/487079_1_En_6_Figl_HTML.gif |

应用时,代码和输出将如清单 6-9 和 6-10 以及图 6-5 所示。

img/487079_1_En_6_Fig5_HTML.png

图 6-5

跳跃启动输出

body {
  box-sizing: border-box;
  padding: 36px;
  margin: 0;
}

body > div {
  box-sizing: border-box;
  margin-bottom: 3rem;
}

@keyframes jumpStart {
 0% {
    width: 0;
    background-color: white;
    border: 1px solid gray;
 }
 100% {
    width: 90vw;
    background-color: gray;
    border: 1px solid gray;
 }
}
.jump-start {

  animation-name: jumpStart;
  animation-duration: 5s;
  animation-iteration-count: infinite;
  margin-bottom: 4px;
  animation-timing-function: steps(5, jump-start);
}

Listing 6-10jump-start Sample Code CSS

<body>
  <div class="jump-start">jump-start</div>
</body>

Listing 6-9jump-start Sample Code CSS

请注意,动画已经部分开始。因为使用了jump-start,宽度为 0 且颜色为白色的初始状态被跳过,动画从宽度为最终状态宽度 20%的容器开始。如果使用了jump-end,容器将从宽度 0 开始,但从未达到 100%的宽度。动画结束时,容器的宽度只有 80%。

img/487079_1_En_6_Figm_HTML.gif Accessibility and Timing

考虑计时时,确保内容在一秒钟内不闪烁超过三次是很重要的。这是为了防止由于使用者的光敏性而诱发癫痫发作。 11

性能考虑因素

当考虑动画对性能的影响时,并不是所有的动画都是一样的。导致布局改变或视图被重画的动画特别费力。 12 例如,高度、宽度或位置的变化会影响布局,并导致页面上的元素被重新定位。导致视图重画的属性包括颜色、背景位置和可见性。影响布局和绘画的动画会比不影响布局和绘画的动画性能差。

通常,为了获得最佳性能,使用 transform 属性是最好的方法,因为它可以依赖于 GPU。只要有可能,最好使用不透明度、平移、旋转和缩放来尝试和坚持动画。 十三

当出现性能问题时,使用will-change属性会很有诱惑力。Will-change提前通知浏览器将要被动画化的变化,允许浏览器针对这些变化进行优化;然而,如果被滥用,它可能弊大于利。正确使用will-change的一些指南包括以下内容:

  • 稀疏使用——应该只在实际需要的时候使用。浏览器已经试图优化一切。不必要的使用实际上会降低页面速度。

  • 仅在需要时打开–应在动画触发前打开,然后再次关闭以释放用于优化的浏览器资源。

  • 足够的时间——优化耗时;因此,will-change需要在动画设置开始之前,用足够的时间应用到元素上才能生效。 14

摘要

本章介绍了过渡、动画及其差异,以及用于更改动画和变换应用时间的函数。还包括处理动画时的性能和可访问性考虑。第七章将介绍预处理器及其架构考虑因素和优势。

七、预处理器

对于 CSS,有几个预处理程序可用。它们将获取数据,以自己特定的语法编写,然后输出 CSS 供浏览器使用。这样做的好处包括可以使用 CSS 中还没有的功能,如颜色编辑功能或嵌套规则。在 CSS 变量被语言本身支持之前,他们也给了我们访问 CSS 变量的权限。一些最流行的处理器包括 Sass、Less 和 Stylus。

Note

本章中的例子将使用 SCSS1 这些技术可以使用其他的预处理器;但是,功能可用性和语法会因使用的预处理器而异。

对架构的启示

由于增加了功能,例如混合和扩展类的能力,使用预处理程序和使用纯 CSS 时,组织和构建代码的方式可能会非常不同。如今,用预处理器之外的方式计算值的能力带来了编写枯燥语义代码的能力。它可以只在一个地方定义,然后在整个样式表中重用,这与其他编程语言中使用的一些面向对象原则没有太大区别。

使用预处理程序的缺点是它们给应用程序增加了一层复杂性,而这在使用纯 CSS 时是不存在的。尽管一些预处理程序,如 Less、 2 可以直接在浏览器中运行,但不建议在生产中使用,因为它的性能和可靠性不如普通 CSS。因此,当使用预处理程序时,我们需要某种构建步骤来将代码编译成 CSS。

调试也可能是一项挑战,尤其是在使用一些更复杂或更高级的代码特性时。这是因为生成的 CSS 与编写的代码没有一对一的匹配。例如,被添加到一个类中的属性和特性可能来自一个 mixin(本章后面将详细介绍 mixin ),而不是规则集的一部分。被应用的 CSS 是输出,而不是 mixin 本身,所以追溯到哪个 mixin 创建了输出可能是困难的。Sourcemaps 可以在这方面提供帮助。sourcemap 是一个可以用 CSS 生成的文件,它将输出链接回生成它的代码。但是同样,这需要作为构建过程的一部分进行专门设置。

因此,首先,在选择要使用的处理器之前,应该问一下增加的复杂性是否必要。

嵌套

嵌套让我们有了清晰的视觉层次,这是 CSS 所没有的。下面的 CSS(清单 7-1 )可以嵌套(清单 7-2 ),让层次结构一目了然。

nav {
  padding: 0;
  margin: 0;
  ul {
    padding: 0;
    li {
      padding: 10px;
      border: solid 1px blue;
      background: yellow;
      color: blue;
    }
  }
}

Listing 7-2Nested SCSS

nav {
  padding: 0;
  margin: 0;
}
nav ul {
  padding: 0;
}
nav ul li {
  padding: 10px;
  border: solid 1px blue;
  background: yellow;
  color: yellow;
}

Listing 7-1CSS

虽然很容易知道列表项上设置的样式将只应用于导航列表项,但是嵌套使得创建过于具体的规则变得非常容易。在前面的例子中,在无序列表中嵌套列表项是多余的,不会增加任何值。仅将其嵌套在导航下,比其当前位置高一级,就足够了。

然而,嵌套可以使某些情况变得清晰。我们来看看清单 7-3 。

a:link, a:visited {
  color: gray;
  font-variant: small-caps;
  border: dotted 1px rgba(0, 0, 0, 0);
  text-decoration: none;
  &:hover { border: dotted 1px cornflowerblue; }
  &:focus { border: solid 1px cadetblue; }
  &:active { border: double 1px darkcyan; }
}

Listing 7-3Nested SCSS

“与”符号指的是父元素,所以当它是一个链接或一个已访问的链接时,鼠标悬停在锚标记上。这里的嵌套非常清楚地表明了悬停、焦点和活动选择器都是a:linka:visited的子元素。

如果没有嵌套,代码应该是这样的(清单 7-4 ):

a:link,
a:visited {
  color: gray;
  font-variant: small-caps;
  border: dotted 1px rgba(0, 0, 0, 0);
  text-decoration: none;
}
a:link:hover,
a:visited:hover {
  border: dotted 1px cornflowerblue;
}
a:link:focus,
a:visited:focus {
  border: solid 1px cadetblue;
}
a:link:active,
a:visited:active {
  border: double 1px darkcyan;
}

Listing 7-4Nonnested CSS

如果没有嵌套,很难一眼看出链接和访问过的链接也有悬停、焦点和活动状态的样式。此外,嵌套代码更简洁,不重复根元素,减少了打字错误或错误的机会。

嵌套时必须小心,以免创建过于具体的规则。当元素嵌套太深时会发生这种情况。但是,它有助于提高代码的可读性。

颜色函数和变量

变量,虽然今天在 CSS 中可用,如第二章所讨论的,但最初是通过使用预处理器来实现的。CSS 版本(自定义属性)虽然受预处理程序变量的影响,但确实比预处理程序变量有一些优势。自定义属性可以通过 JavaScript 访问和更改,而预处理器变量则不能。在创建 CSS 输出时,预处理器变量不再是变量;它们被它们的赋值所取代。然而,CSS 自定义属性是静态变量,可以在任何时候操作,包括在运行时。

虽然变量可以用于任何值,如默认填充量,但当与颜色函数结合使用来定义应用程序的主题时,它们是非常强大的。应用的品牌颜色包括表 7-1 中给出的颜色。

表 7-1

颜色值和用法

| ![img/487079_1_En_7_Figa_HTML.png](https://gitee.com/OpenDocCN/vkdoc-html-css-pt2-zh/raw/master/docs/arch-css/img/487079_1_En_7_Figa_HTML.png) |

可以根据颜色的用途将颜色设置为语义名称,然后在需要改变颜色值或饱和度时使用颜色函数进行操作(清单 7-1 )。

颜色函数会因所用的预处理器而异;大多数都包括使颜色变亮、变暗或改变色调、饱和度或透明度的功能。在清单 7-5 中,我们使用scale-color()获取一种颜色,然后可以改变以下颜色属性的任意组合:红色、绿色、蓝色、饱和度、亮度和 alpha。当亮度设置为 10%时,我们使颜色比原始颜色浅 10%,并保持所有其他值不变。

$primary: #AEC5EB;
$accent: #E9AFA3;
$links: #AEC5EB;
$background: #F9DEC9;
$dark: #3A405A;
$light: #FAFAFA;

$border: solid 1px $light;
$dark-text: $dark;
$light-text: $light;

$spacing: 1.25rem;

body {
  background: scale-color($background, $lightness: 10%);
  color: $dark-text;
  padding: $spacing;
}
a:link, a:visited {
  color: $link;
}
a:hover, a:focus {
  color: scale-color($link, $lightness: -10%);
}
button {
  color: $light-text;
  background: $primary;
  border: $border;
  padding: $spacing;
}
section, article {
  background: scale-color($background, $lightness: 20%);
  padding: $spacing;
  margin-bottom: $spacing;
}

Listing 7-5Colors

通过使用颜色转换函数和变量,我们不仅不必记住每种颜色的确切值和我们可能使用的任何变化,而且我们还增加了保持主题一致的能力。此外,如果颜色要改变,这可以在一个地方完成。颜色变化的可能性就是为什么颜色名称应该基于它们的用法而不是它们的实际颜色。例如,如果变量名是$pink,并且强调色被改为紫色,我们现在必须到处查找变量名并更新它,或者我们将拥有一个不代表分配给它的颜色的变量名。这种情况使得可维护性变得非常困难,代码也很混乱。选择语义变量名对于代码的可维护性非常重要。

混入类

Mixins 允许开发人员创建可以在整个应用程序中轻松重用的属性和值的集合。

简单混合

清单 7-6 中的简单例子展示了如何使用一个简单的 mixin 在一个地方定义一组属性,然后将它们包含在另一个上下文中。@include属性用于将先前定义的 mixin 分配给新的上下文——在本例中是一个元素——但是它也可以很容易地包含在类定义中。

@mixin card {
  background: white;
  box-sizing: border-box;
  margin-bottom: 1rem;
  padding: 1rem;
  box-shadow: 1px 1px 3px silver
}

div {
  @include card;
}

Listing 7-6Simple Mixin

因素

mixin 也可以接受参数,以便根据传递的参数改变 mixin 的输出,如清单 7-7 中的$elevation参数所示。

@mixin card($elevation) {
  background: white;
  box-sizing: border-box;
  margin-bottom: 1rem;
  padding: 1rem;

  $offset: $elevation * 1;
  $blur: $elevation * 2;
  box-shadow: #{$offset}px #{$offset}px #{$blur}px silver;
}

div {
  @include card(3);
}

Listing 7-7Mixin with Arguments

争论

逻辑也可以添加到 mixin 中。在清单 7-8 中,基于非零值$elevation,样式被不同地应用。

@mixin card($elevation) {
  background: white;
  box-sizing: border-box;
  margin-bottom: 1rem;
  padding: 1rem;

  @if $elevation == 0 {
    border: solid 1px silver;
  } @else {
    $offset: $elevation * 1;
    $blur: $elevation * 2;
    box-shadow: #{$offset}px #{$offset}px #{$blur}px silver;
  }
}

body {
  padding: 2rem;
}
h1 {
  margin: 0;
}
header {
  @include card(0)
}
div {
  @include card(2);
}

Listing 7-8Mixin with Arguments and Logic

使用 mixins 的优点,尤其是对于参数,是它允许干代码。代码只需编写一次,在一个地方进行管理,但可以应用于多个类。前面的代码(清单 7-8 )将编译成清单 7-9 所示的内容,并显示图 7-1 。

img/487079_1_En_7_Fig1_HTML.jpg

图 7-1

混合输出

body {
  padding: 2rem;
}

h1 {
  margin: 0;
}

header {
  background: white;
  box-sizing: border-box;
  margin-bottom: 1rem;
  padding: 1rem;
  border: solid 1px silver;
}

div {
  background: white;
  box-sizing: border-box;
  margin-bottom: 1rem;
  padding: 1rem;
  box-shadow: 2px 2px 4px silver;
}

Listing 7-9CSS Output

Mixins 在防止重复代码或无意义类名的需要方面非常强大。如果没有 mixins,早期的代码可能需要无限数量的类,或者必须多次重写边框和阴影,然后在多个位置维护它。另一个很好的应用是当一个特定的参数可能应用于一个元素的许多方面,但是根据上下文而不同,而元素的其余部分需要保持一致,不管情况如何。给用户的信息框可能是一个例子,其中需要信息、成功、警告和错误。除了颜色之外,盒子需要看起来一样(清单 7-10 和 7-11 和图 7-2 )。

img/487079_1_En_7_Fig2_HTML.jpg

图 7-2

信息框

@mixin message($color) {
  background: lighten($color, 40%);
  border: solid 1px $color;
}

body {
  padding: 2rem;
}

.message {
  padding: 1rem;
}

.info {
  @include message(blue);
}

.success {
  @include message(green);
}

.warning {
  @include message(orange);
}

.error {
  @include message(red);
}

Listing 7-11Informational Boxes SCSS

<body>
  <p class="message info">Information</p>
  <p class="message success">Success</p>
  <p class="message warning">Warning</p>
  <p class="message error">Error</p>
</body>

Listing 7-10Informational Boxes HTML

尽管填充符可能已经包含在 mixin 中,但是它被分离到它自己的类中,因为当添加 mixin 时,它每次都进行计算并输出所有代码;因此,mixins 不是静态信息的好用例。静态样式只是以编程方式复制到每个类中,增加了 CSS 的大小,从而增加了上传时间。对于静态样式,类、元素的缺省值或者使用@extend at-rule 是更好的选择。

@扩展

与 mixins 不同,Extend 防止在生成的 CSS 中出现重复代码。当 mixin 为包含它的每个选择器复制声明块时,extend 创建一个声明块并合并选择器。

这种方法的优点是为基本样式创建基类,语义命名的类将指向这些基本样式。代码既不重复也不拷贝,防止在 HTML 元素上使用大量无意义的类。为了代码的可维护性,这也意味着元素的样式在 CSS 中得到控制。如果不再需要来自扩展另一个规则的样式,我们只需要移除@extend。通过简单地将类名添加到 HTML 中,而不是使用@extend,我们将不得不编辑 HTML 来改变外观。通过使用@extend,而不是将相同的类名添加到多个元素中,我们继续保持关注点的分离。我们的元素可以有与其用途相匹配的类名,而不是它们如何显示,我们通过 CSS 处理样式。

重温“Mixins”一节中的例子,而不是将消息和类型都添加到每个类中,我们可以创建一个类来确定类型,并由.message设置默认值(参见清单 7-12 和 7-13 以及图 7-3 )。

img/487079_1_En_7_Fig3_HTML.jpg

图 7-3

信息框–重新审视

@mixin message($color) {
  background: lighten($color, 40%);
  border: solid 1px $color;
}

body {
  padding: 2rem;
}

.message {
  padding: 1rem;
}

.info-message {
  @include message(blue);
  @extend .message;
}

.success-message {
  @include message(green);
  @extend .message
}

.warning-message {
  @include message(orange);
  @extend .message
}

.error-message {
  @include message(red);
  @extend .message
}

Listing 7-13Informational Boxes SCSS – Revisited

<body>
  <p class="info-message">Information</p>
  <p class="success-message">Success</p>
  <p class="warning-message">Warning</p>
  <p class="error-message">Error</p>
</body>

Listing 7-12Informational Boxes HTML – Revisited

请注意,两个示例具有相同的结果外观。但是后者只允许一个类而不是两个类来指定元素的整个类。对于代码的可维护性来说,@extend的强大之处在于能够在一个地方声明整个类,而无需在 Sass 和编译后的 CSS 中复制粘贴或复制代码(CSS 输出见清单 7-14 )。

body {
  padding: 2rem;
}

.message, .error-message, .warning-message, .success-message, .info-message {
  padding: 1rem;
}

.info-message {
  background: #ccccff;
  border: solid 1px blue;
}

.success-message {
  background: #4dff4d;
  border: solid 1px green;
}

.warning-message {
  background: #ffedcc;
  border: solid 1px orange;
}

.error-message {
  background: #ffcccc;
  border: solid 1px red;
}

Listing 7-14Informational Boxes – Revisited Output CSS

注意,message 类现在也有多个其他选择器,但是在输出中没有重复。

@导入

导入允许用户创建可以放置变量、混合和可重用代码的部分文件。Sass 导入的工作方式与 CSS 导入类似,它将包含的 SCSS 复制到导入它们的样式表中。因此,必须谨慎使用。例如,通过重复导入整个主题到每个组件中,很容易使代码膨胀。共享 mixins 和变量,因为它们不被复制,而是在一个样式中产生一个输出,这是使用@import的完美应用,因为不像类,它们不被复制。

在处理组件时,创建导入文件以使信息可以从应用程序中的任何地方访问变得非常有趣,因为通常情况下,例如在使用现成的 Angular 或在 JavaScript 和 Shadow DOM 中创建组件时,CSS 是有作用域的,因此与 CSS 的其余部分相比,它位于应用程序的一个单独的文件或区域中。向部分添加变量和混合——一个要导入到其他文件中的文件,它本身并没有用处——有助于保持代码干燥。

Note

分部有时在名称的开头用下划线来表示,以区别于样式表。

@import的另一个用例是防止应用程序的 CSS 样式表成为文件不可维护的巨石。通过将 CSS 分成更小的部分导入到主样式表中,代码可以更容易地被查找、协作和维护(参见清单 7-15 )。

@import "_variables"
@import "nav"
@import "carousel"
 .
 .
 .
a:link, a:visited { ... }
 .
 .
 .

Listing 7-15@import

摘要

在这一章中,我们研究了通过使用预处理器而使用的一小部分功能。我们研究了混合、导入、扩展、颜色函数和变量,以及它们如何影响我们组织和构建应用程序的 CSS。在下一章,我们将看看 JavaScript 如何与 CSS 交互,尤其是在现代框架的环境中。

八、框架、库和 JavaScript

在现实世界的应用程序中,您的 CSS 并不是孤立运行的。本章涵盖了现代前端 web 应用程序的一些重要考虑因素,包括您对 CSS 或 JavaScript 框架的选择如何影响您的应用程序风格。

Java Script 语言

好的,是的,这是一本关于 CSS 的书,那么为什么我们突然开始谈论 JavaScript 了呢?事实是,大量的前端开发是使用某种框架和/或 UI 库完成的,其中许多依赖于 JavaScript。此外,由于状态变化或用户交互,JavaScript 经常被用来操纵 CSS。

根据 Stack Overflow 的 2019 年度调查,过去连续七年最受欢迎的编程语言是 JavaScript。前十名的细分见图 8-1 。 1

img/487079_1_En_8_Fig1_HTML.jpg

图 8-1

根据堆栈溢出,2019 年最受欢迎的编程、脚本和标记语言

最受欢迎的 web 框架仍然是 jQuery,其次是 React 和 Angular。十大细分如图 8-2 所示。 2

img/487079_1_En_8_Fig2_HTML.jpg

图 8-2

根据堆栈溢出,2019 年最受欢迎的 Web 框架

使用 JS 操作 CSS

通过 jQuery 等库和element.ClassList等普通 JavaScript 属性,我们已经通过 JS 操纵 CSS 很多年了。但是这对特异性和层叠到底有什么影响呢?表 8-1 列出了一些通过 JS 影响视觉输出的常见方式及其对特异性的影响。

表 8-1

CSS 改变 JavaScript 方法

|

属性和方法

|

它的作用

|

对特异性的影响

|
| --- | --- | --- |
| 元素。类别列表添加()移除()更换()切换()项目()包含() | 读取和操作附加到特定元素的类。 | 因为样式是通过引用类来应用的,所以级联和继承不会受到太大的影响。 |
| element.style例子:elem.style =``"color: blue, font-size: 12px"elem.setAttribute("style", "color:blue; font-size: 12px"elem.style.color = "blue" | 在元素上内联添加样式。 | 很难覆盖,因为它们是内联的。如果元素已经有内联样式,它们将被 JavaScript 应用的样式覆盖。 |

这既是一个巨大的优势,也是一个巨大的风险,JavaScript 能够轻易地覆盖 CSS 声明的任何内容,但不是必须这样做。

如果我们不想在 JavaScripts 中混合 CSS 规则,那么通过 classList 给我们的方法允许在不接触 CSS 本身的情况下向元素添加和移除类。这带来了样式定义留在 CSS 文件中而不是在 JS 和 CSS 文件之间分割的巨大好处。

影响样式或 DOM 本身带来的好处是,所应用的 CSS 的特殊性无关紧要,因为它将被覆盖。通过这种技术,不管任何其他上下文,作为开发人员,我们可以规定,如果用户执行了某个动作,或者达到了某个特定的状态,将应用一组特定的样式,而不管其他样式或上下文。这种技术通常用于显示和隐藏对话框,例如通知消息。这种技术的优点是样式的改变可以和它的触发器或动作保持一致,并且不管上下文如何,被应用的样式不会被先前存在的 CSS 覆盖。

有些情况下,JavaScript 允许我们做纯 HTML 和 CSS 解决方案无法做到的事情。一个完美的例子是使用事件监听器来控制动画的定时、开始或结束(参见清单 8-1 to 8-3 和图 8-3 )。

img/487079_1_En_8_Fig3_HTML.png

图 8-3

动画监听器输出

var image, button;

(function() {
  'use strict';
  image = document.getElementById('image');
  image.addEventListener('animationend', reEnableButton);
  button = document.getElementById('button');
})();

function reEnableButton() {
  button.disabled = false;
  image.classList.remove('rotate');
}

function rotateImage() {
  image.classList.add('rotate');
  button.disabled = true;
}

Listing 8-3Animation Listener JavaScript

html, body {
  padding: 36px;
  margin: 0;
}

@keyframes rotate {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
.rotate { animation: rotate ease-in-out 500ms 1; }

.image { text-align: center; }
img { max-width: 100%; }
button {
  display: block;
  margin: 1rem auto;
  padding: 1rem;
  width: 25%;
}

Listing 8-2Animation Listener CSS

<body>
  <div class="image">
    <img src="art.png" alt="modern art" id="image">
    <button onClick="rotateImage()" id="button">Rotate Image</button>
  </div>
</body>

Listing 8-1Animation Listener HTML

按钮触发 JavaScript 禁用按钮,并添加一个类rotate,使图像旋转一次。因为在页面加载时,我们设置 JavaScript 来监听动画的结束,一旦动画结束,我们可以重置页面。该按钮被重新启用,并且 rotate 类被删除。在这个例子中,即使我们用 JavaScript 操作 CSS,CSS 类仍然在 CSS 文件中定义和维护,因此,继承和级联没有被改变或影响。

基于组件的体系结构

当使用基于组件的架构时,不仅要根据应用程序的品牌/规范来设计应用程序的主题,而且 UI 库本身也非常重要。每个库在易用性方面都有不同程度的主题性和复杂性,以及实际上可能的样式。当选择一个 UI 库时,理解可定制性以及一个库或框架是如何可定制的,可以让你省去很多麻烦。封装——将组件 CSS 限制在组件本身——允许编写只适用于特定组件而不适用于应用程序其余部分的 CSS。如果一个 UI 库的组件有非常严格的封装和很少的主题选项,那么设计风格将会非常困难。

有许多不同的库和框架可以创建或使用组件。每一个都有稍微不同的实现。我们不会看所有的。当观察现代 JavaScript 框架如何创建组件并与组件交互时,大多数框架要么使用 web 组件,要么模仿它们。值得注意的是,特别是如果组件被模拟,它的行为将与我在下文中描述的略有不同。Angular 有一个模拟模型,并继续支持等效的阴影穿透组合器(::ng-deep),但可以设置为使用 web 组件,或者设置为根本没有封装。在 React 中,这取决于 CSS 在项目中是如何设置的。选项也涵盖了整个范围。准确理解框架提供了多少封装将有助于更好地决定如何构建 CSS。

库和框架

根据定义,库是应用程序将使用的声明的集合。框架是一种抽象,为应用程序提供基本的功能或框架。一个框架可以包含一个或多个库。

UI 库如jQuery UI3Angular Material4提供了一系列可以添加到应用程序中的组件或小部件。它们有现成的样式和功能。要定制他们的外观,他们需要有主题。主题化既可以通过工具来完成,这些工具可以提供必要的 CSS,比如 themerollers,也可以按照指南手动完成。无论哪种方式,所述库的主题性和可定制性将会变化。可变性是组件如何构造以及作者如何使元素易于定制的直接结果。超出主题允许范围的进一步定制通常会非常困难,导致使用非常特殊的选择器,比如使用!important。因此,在考虑库的时候,了解它的主题化功能以及定制它的容易程度是非常重要的,这样它的元素就可以匹配你的应用程序。

库本身的架构也可能影响它的使用方式,并且在某些情况下可能提供多种方法。Bootstrap 5 很有意思,因为它的结构允许两种完全不同的实现,各有利弊。

第一种,也可能是最常见的实现,是直接在页面中导入 CSS 和 JavaScript,从本地源,通过 CDN,或者使用包管理器,如 NPM、NuGet 或 RubyGems。该框架整体上是可用的,并且正在被应用。这意味着许多类已经添加了样式,可以在网站上使用了。一些组件,比如模态,具有依赖于与之相关的 JavaScript 的功能。这些也将随时可用。

这种方法的缺点在于三个方面:

  1. 命名不再是语义性的。

  2. 样式本质上是由 HTML 控制的。

  3. 所有内容都是完整导入的,即使没有被使用。

考虑一个包含三个页面的静态网站,所有三个页面使用相同的基本布局。布局包含一个主要部分和一个位于主要部分右侧的侧边部分(见清单 8-4 和 8-5 以及图 8-4 )。

img/487079_1_En_8_Fig4_HTML.jpg

图 8-4

自举输出

html, body {
  padding: 36px;
  margin: 0;
}

Listing 8-5Bootstrap CSS

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Bootstrap</title>
  <meta charset="UTF-8">

  <!-- bootstrap -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>

<body class="container">
  <h1>Food</h1>

  <div class="row">
    <main class="col-md-9">
      <div class="jumbotron">
        <h2>Yum</h2>
        <p>Gummi bears chocolate bar powder brownie… </p>
        <button class="btn btn-warning">Call To Action</button>
      </div>
      <h2 id="cupcakes">Cupcakes</h2>
      <p>Chocolate chocolate bar tart cookie chocolate… </p>
      <a class="btn btn-light">Read More</a>
    </main>
    <aside id="bacon" class="col-md-3">
      <h2>Bacon</h2>
      <p>Bacon ipsum dolor amet pork loin chicken ham… </p>
      <p>Jowl spare ribs turkey cupim, pork chop sirloin… </p>
      <a class="btn btn-light">Read More</a>
    </aside>
  </div>

  <!-- Bootstrap scripts -->
  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

</body>
</html>

Listing 8-4Bootstrap HTML

这种方法的好处是简单。无论是使用自定义主题,还是使用前面例子中的默认主题,都可以快速启动并运行。许多基本样式已经存在,可以用来创建一个布局。最大的缺点是代码的可维护性。如果多个页面上有多个行动号召按钮,保持一致性就变得非常困难。

<button class="btn btn-warning">Call To Action</button>

如果更新了btnbtn-warning类,那么应用程序中包含这个通用类的所有按钮都将被更新,不管是不是动作调用。这个类没有给出它的用途,或者更糟,比如在这个例子中,它是因为它的颜色而被使用,而不是作为警告。唯一的另一个选择是找到应用程序中所有的动作调用,并更新它们的类名。

不再是样式表控制元素的外观,样式现在与 HTML 紧密绑定在一起。布局也是如此,想要将侧边改为占页面的三分之一,而不是四分之一,将涉及到进入每个页面,并更新 HTML。

另一种选择是利用 Bootstrap 提供的可用 Sass 混合(参见清单 8-6 和 8-7 )。

@import './node_modules/bootstrap/scss/functions';
@import './node_modules/bootstrap/scss/variables';
@import './node_modules/bootstrap/scss/mixins';

@import './node_modules/bootstrap/scss/jumbotron';
@import './node_modules/bootstrap/scss/buttons';

html {
  padding: 36px;
}

body {
  padding: 36px 15px;
  margin: 0 auto;
  font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
  font-weight: 400;
  line-height: 1.5;
  color: #212529;
  box-sizing: border-box;
}

h1, h2 {
  margin-top: 0;
  margin-bottom: .5rem;
  font-weight: 500;
  line-height: 1.2;
}
h1 { font-size: 2.5rem ;}
h2 { font-size: 2rem ;}

.container {
  box-sizing: border-box;
  @include make-row(15px);
  & > * {
    box-sizing: border-box;
    @include make-col-ready(1rem);
  }
}

button { @extend .btn }
.call-to-action {
  box-sizing: border-box;
  @extend .jumbotron;
  button {
    @include button-variant($yellow, $yellow);
  }
}
a.read-more {
  @extend .btn;
  @include button-variant($gray-100, $gray-100);
}

@media (min-width: 756px) {
  body {  max-width: 540px; }
}

@media (min-width: 768px) {
  body {  max-width: 720px; }
  main { @include make-col(9); }
  aside { @include make-col(3) }
}

@media (min-width: 992px) {
  body {  max-width: 960px; }
}

@media (min-width: 1200px) {
  body {  max-width: 1140px; }
}

Listing 8-7Bootstrap Mixins SCSS

<!DOCTYPE html>
<html lang="en">

<head>
  <title>Bootstrap</title>
  <meta charset="UTF-8">
  <!-- Application CSS -->
  <link rel="stylesheet" href="./styles.css">
</head>

<body>
  <h1>Food</h1>
  <div class="container">
    <main>
      <div class="call-to-action">
        <h2>Yum</h2>
        <p>Gummi bears chocolate bar powder brownie… </p>
        <button>Call To Action</button>
      </div>
      <h2>Cupcakes</h2>
      <p>Chocolate chocolate bar tart cookie chocolate… </p>
      <a class="read-more">Read More</a>
    </main>
    <aside>
      <h2>Bacon</h2>
      <p>Bacon ipsum dolor amet pork loin chicken ham pancetta… </p>
      <a class="read-more">Read More</a>
    </aside>
  </div>
</body>
</html>

Listing 8-6Bootstrap Mixins HTML

这种方法不容易启动和运行。因为它使用 SCSS,它将需要处理 SCSS 到 CSS 的能力。还需要了解 SCSS 和框架中 mixins 可用的内容。然而,一旦过了设置和学习曲线,我们会得到一些很大的好处。因为我们现在通过@include@extend将样式分配给类,而不是将通用类名应用于 HTML 中的元素,所以我们知道我们的元素在所有页面上看起来都是一样的。整个应用程序中的元素也可以从一个地方更新,而不是在站点中搜索特定概念的所有实例。最后,只有我正在使用的引导部分被导入,这减少了页面重量。

!Important

每当试图对一个组件库进行主题化或者调整 CSS 框架的样式时,特殊性有时是一个挑战,因为库或框架可能已经使用了非常具体的选择器;所以,用!important可能很有诱惑力。这里有龙!

虽然有些情况下真的没有其他选择,或者重要的是小恶,但这种情况很少。

使用!important增加了声明的优先级,使得覆盖或包含在普通级联中变得非常困难。您不再能够以更具体的选择器为目标来改变元素的样式。你现在需要另一个更具体重要的。这种恶性循环使得代码难以调试和维护,甚至更难扩展。

所以当覆盖样式时,如果你使用!important,要小心谨慎,要有意图而不是沮丧。

了解所选库的架构及其功能,将有助于做出明智的决定,即如何构建代码以获得更好的长期可维护性和性能。

Web 组件

在文档对象模型(DOM)中,自定义元素可以通过使用影子 DOM 附加到 DOM 来创建和封装。阴影 DOM 是可以附加到渲染文档的 DOM 元素的子树,由阴影主机、阴影根和阴影树组成(见图 8-5 )。

img/487079_1_En_8_Fig5_HTML.png

图 8-5

影子天赋

使用这种技术创建的组件是完全封装的,作者可以完全控制消费者能够设计的样式,因为从父页面或组件的角度来看,Shadow DOM 中的所有内容都类似于一个黑盒。有一段时间,我们可以通过使用>>>::deep这样的阴影穿透组合符来忽略封装,但这些组合符已经被弃用或从大多数浏览器中删除,以支持当前正在完善的即将到来的 CSS 阴影部分 6 规范。然而,即使在实现了这个新规范之后,组件的作者仍将控制用户能够摆弄的东西;特异性、!important和阴影穿透组合将继续无法编辑组件作者不允许更改的样式。

从架构上来说,web 组件的好处是可以将样式化的 web 组件放到任何 UI 中,而不用担心被父应用程序的 CSS 修改。见清单 8-8 至 8-11 和图 8-6 。

img/487079_1_En_8_Fig6_HTML.jpg

图 8-6

Web 组件输出

html, body {
  background: #fafafa;
  padding: 36px;
  margin: 0;
}

section.components {
  display: flex;
  margin: 0 -1rem;
}

my-card { margin: 1rem; }

.actions { text-align: center; }

button {
  background: rgb(187, 255, 120);
  border: none;
  border-radius: 1rem;
  box-shadow: 1px 1px 1px 1px gray;
  margin-top: 2rem;
  padding: .5rem 3rem;
}

.dark {
  font-family: monospace;
  font-size: 1.0625rem;
}

p, h2 { font-variant: small-caps; }

Listing 8-11Web Component Page CSS (styles.css)

:host {
  font-family: sans-serif;
  --primary: mediumvioletred;
  --background: white;
  --text: #242529;
  --buttonText: white;
}
:host(.dark) {
  --background: #242529;
  --text: white;
}

.card {
  background: var(--background);
  color: var(--text);
  box-shadow: 1px 1px 1px var(--primary), 0px 0px 2px lightgrey;
  padding: 1rem;
}

h2 { margin: 0 1rem 0 0; }

.actions {
  text-align: right;
  margin-top: 1rem;
}

button {
  background: var(--primary);
  color: var(--buttonText);
  font-family: sans-serif;
  padding: .5rem 1.5rem;
  border: none;
}

Listing 8-10Web Component CSS (card.css)

const actionButtonEvent = new CustomEvent('actions', {
  bubbles: true

,
  detail: { text: 'ok button' }
});

class MyCardComponent extends HTMLElement {
  constructor() {
    super();

    const shadow = this.attachShadow({ mode: 'open' });

    const card = document.createElement('div');
    card.setAttribute('class', 'card');

    const titleText = this.getAttribute('title');
    if (titleText) {
      const title = document.createElement('h2');
      title.innerText = titleText;
      card.appendChild(title);
    }

    const slot = document.createElement('slot');
    card.appendChild(slot);

    const actions = document.createElement('div');
    actions.setAttribute('class', 'actions');
    const button = document.createElement('button');
    button.innerText = 'OK';
    button.setAttribute('type', 'button');
    button.addEventListener('click', () => {
      this.dispatchEvent(actionButtonEvent);
    });
    actions.appendChild(button);
    card.appendChild(actions);

    const style = document.createElement('style');
    style.textContent = `@import './card.css'`;

    card.appendChild(style);
    shadow.appendChild(card);
  }
}

(function() {
  'use strict';
  customElements.define('my-card', MyCardComponent);
  document.querySelector('my-card').addEventListener('actions', e => console.log('outer actions event', e));
})();

Listing 8-9Web Component JavaScript (script.js)

<html lang="en">

<head>
  <title>Web Components</title>
  <link rel="stylesheet" href="./styles.css">
  <meta charset="utf-8">
</head>

<body>
  <h1>Components</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sed neque semper ante mattis tempor. Morbi volutpat ex ante, eget vulputate ligula pulvinar gravida.</p>
  <p></p>
  <section class="components">
    <my-card title="Coffee">
      <slot name="content">
        <p>Aged cortado, carajillo saucer wings aftertaste...</p>
      </slot>
    </my-card>

    <my-card title="Cats" class="dark">
      <slot name="content">
        <p>Chew master's slippers I is not fat, I is fluffy...</p>
      </slot>
    </my-card>
  </section>
  <div class="actions">
    <button type="button">More Components -></button>
  </div>
  <script src="./script.js"></script>
</body>

</html>

Listing 8-8Web Component HTML

请注意,即使样式直接应用于两个样式表中的按钮元素,按钮样式也不会相互交互。这是非常强大的,因为这意味着组件和应用程序可以独立构建,并且可以互相使用,而不用担心命名冲突。然而,有些东西是可以转移的。通过:host和较少支持的:host-context选择器,组件可以知道它的上下文。在这种情况下,组件专门查看它的主机是否有一个 dark 类。如果是的话,那么它的风格就不同了。然而,这些是作者预先计划好的风格。如果将另一个类名传递给这个特定的组件,它将继续显示其默认值,如图 8-7 左侧所示。

img/487079_1_En_8_Fig7_HTML.jpg

图 8-7

包括插槽的节点树

有些样式会渗入到组件中。槽中的元素或直接分配给宿主的类(自定义标记)受组件样式和页面样式的影响。注意图 8-6 中的段落标签;如下所述,它们从父组件和组件中获取样式。

左侧组件:

Browser default (serif) -> :host (sans-serif)

右侧组件:

Browser default (serif) -> :host (sans-serif) -> .dark (monospace)

:host比浏览器默认值更具体,反过来,.dark:host更具体。

那么为什么标题也变成了等宽,而按钮却不受影响呢?通过给.dark添加一个无衬线字体系列,我们基本上将 monospace 设置为:host上的默认字体系列,这是我们能插入组件的最大限度。按钮在组件 CSS 中指定了自己的字体,覆盖了默认的:host,因此不受影响。进一步尝试通过特定性来深入组件内部的元素,比如即使没有为该属性设置样式,也不能进行.dark button { ... }操作。

我们之所以能够设计段落标签的样式,并将继续能够对它们做我们想做的任何事情,是因为它们在一个槽中。插槽的内容实际上是由父插槽控制的。查看 DOM 树,可以看到槽内容位于影子树之外,如图 8-7 所示。

阴影树中的槽只不过是一个占位符。槽的内容在shadow-root的兄弟节点中,而不是在shadow-tree本身中。因此,它不像组件的其余部分那样封装,可以像页面上的任何其他元素一样设计样式。但是,因为它是主体的子元素,所以分配给影子主体的样式通常会级联到这些元素。

使用 Web 组件的样式化应用程序

当创建一个使用组件的应用程序时,很容易开始只考虑小的可重用项目,而忽略了全局。应用程序中字体、颜色、按钮样式等的一致性是一件好事,我想我们都同意这一点。然而,如果我们在每一个组件中重写这些风格,我们就为自己设置了差异和可维护性的噩梦。组件封装的紧密程度会影响方法。

然而,不管封装如何,从这样一个文件开始,其中一些可重用的值被设置为语义的、易于读取的变量被导入到所有位置,这在两个方面有所帮助:在任何地方都使用相同的文件,因此获得了一致性,并且因为它包含的那些值也被维护在一个位置,所以如果主要品牌颜色从蓝色切换到紫色,人们就不必在应用程序中寻找该颜色的每个实例。当使用诸如 Sass 或更低版本的预编译器时,这也是设置一些 mixins 的好地方。有关预编译器的更多信息,请参见第七章。清单 8-12 显示了这样一个文件的摘录。

/∗ brand colors ∗/
--primary: #8A4F7D;
--accent: #88A096;
--border-color: #DDDDDD;
--link-color: var(--accent);
--background: #FAFAFA;
--font-family: sans-serif;

--box-shadow: 1px 1px 1px var(--primary), 0px 0px 2px lightgrey;

/∗ breakpoints ∗/
--small: 500px;
--medium: 800px;
--large: 1200px;

...

Listing 8-12Sample Variable File

如果应用程序仍然有一个基本样式表,其样式应用于所有组件,这是一个放置默认值的好地方,例如链接在悬停和聚焦时应该是什么样子,标题应该是什么样子,以及应用程序的基本字体和颜色是什么。这是一个设置主题的好地方。(参见清单 8-13 )。

@import 'variable.css';

html, body {
  Background: var(--primary);
  padding: 0;
  margin: 0;
  font-family: var(--font-family);
  color: black
}

h1, h2, h3, h4, h5, h6 {
  color: var(--primary);
}

a:link,
a:visited {
  color: var(--accent);
}
a:hover,
a:focus {
  text-decoration: underline;
}
...

Listing 8-13Sample Theme File

一旦建立了这两个文件,组件应该只需要担心布局和异常,即特定于该组件的事情,而不需要担心其他事情。如果你发现自己一遍又一遍地复制和粘贴相同的东西,这将是对属于这些文件中的一个样式或一组样式的一个很好的衡量。如果是这种情况,是时候考虑这些样式是否需要导入或者在某个地方设置为默认值了。

如果组件被紧密封装,并且主题文件不可能在整个应用程序中级联样式,那么可以导入的变量就变得非常关键。将整个主题文件导入到每个组件中只会使应用程序膨胀,因为即使在一个地方维护,它本质上也会被复制到每个组件中。这里的可能性包括使用预处理程序来创建 mixins,或者将主题文件分解成更小的块、按钮、表格、链接等等,以便只导入需要的部分。

摘要

在这一章中,我们讨论了 CSS 和 JS 之间常见的交互机制,以展示 JS 是如何与我们的样式表交互(有时会干扰)的。我们还研究了我们使用的库的架构如何影响我们构建代码的方式。在下一章中,我们将深入各种架构最佳实践以及具体的 CSS 架构模式,展示它们的优缺点。

九、架构模式

为了给你的 CSS 产生一个架构方法,清楚地建立你的目标,然后确定一个通用的方法是很重要的。在这一章中,我们提出了目标、指导方针和方法供您考虑。

方法

当从事 web 开发时,建立一个对外部因素和业务需求敏感的一致的方法是很重要的。我们的开发团队还需要考虑一些因素,以确保我们能够快速产生预期的结果,并且随着内容的添加和更改,网站能够继续按照预期的方式工作。

Note

由于元素组件都是在 Web 环境中具有特定含义的技术词汇,我们将使用词语小部件来指代由 HTML+CSS 组成的可重用构件。

虽然许多框架会用本地或基于框架的 web 组件实现一个小部件,但这既不是好的 CSS 架构的要求,也不是它的目标。

目标

回到第一章,我们的目标是通过高内聚和低耦合实现关注点的分离。我们希望在改善开发人员体验的同时,将维护成本和总体开发时间降至最低。以这些高级目标为起点,我们可以为我们的 HTML 和 CSS 推荐以下具体目标:

  • 我们应该能够用 HTML 和 CSS 设计一个模块化的小部件,它可以独立存在而不依赖于外部 CSS。

  • 当我们将一个小部件放入我们的网站时,我们希望它呈现我们网站和品牌的整体外观和感觉。

  • 没有必要人为地提高特异性或优先级(比如通过添加 ID 选择器或!important注释)来按照预期样式化小部件。

  • HTML 文档中的一般内容编辑(插入图像、添加段落、调整表格中的行/列、向列表中添加项目等。)不需要更新样式表。

  • 对于新加入我们团队的人来说,应该很容易知道如何对我们的页面和小部件的设计进行调整。

  • 我们的风格应该易于阅读、理解和排除故障。

  • 我们的网站设计应该容易重新主题,而不影响网站的布局和功能。

  • 进行设计更改(比如颜色和排版)应该很简单,不需要在样式表的 CSS 属性上使用搜索和替换。

  • 我们应该能够独立地调整 Widget-A 和 Widget-B 的样式,而不用担心对其中一个的更改会无意中影响到另一个。

  • 应该尊重用户关于风格的意愿,以改善他们的体验和整体可访问性。

在他 2012 年关于 CSS 架构的博客文章中,Philip Walton 提出了一个重要的提醒:“如果你的 CSS 需要对你的 HTML 结构有深入的了解才能工作,那么从你的 HTML 中去掉所有的表示代码是达不到目的的。”

指导方针

从前面列出的目标出发,我们可以得出一些一般性的指导原则,这些原则可能有助于选择正确的方法。这些并不是一成不变的规则,而是你可以选择的一组合理的默认位置。

  • 避免使用反映标签名称、属性值或伪类的类名(例如,不再使用<button type="button" class="button"><input type="password" class="password">)。

  • 避免使用通用的类名,如contentcontainerwrapperrightleft。这些名称对于它们的用法或意图没有提供任何有意义的理解,并且它们容易与我们项目中的其他样式表或第三方库发生命名冲突。

  • 通常,避免使用 id 作为选择器。

  • 首选选择器特性在[0 1 0]和[0 2 2]之间。再低的话,你就有可能将样式泄露到网站的其他部分。再高一点,你就有覆盖继承或者无意中使一个值变得有粘性的风险。更高的特异性也有过于冗长、脆弱或难以理解的风险。有目的且小心地使用更高特异性的选择器。

  • 避免在样式表中使用!important和在 HTML 中使用内嵌样式。

  • 为开发人员的体验和正在进行的更改的方便性和准确性优化您的 CSS 架构决策,而不是过度关注样式表的性能。

  • 选择一致的命名策略。

  • 分开关注。

  • 仅在必要时使用子代和同级选择器(理想情况下仅在一个小部件中),但避免使用后代选择器,因为它们具有未知的影响。

方法学

对于代码中的大多数东西,有不止一种方法来处理任何给定的问题。CSS 架构也不例外。在考虑如何构建代码和命名类时,有四种模式脱颖而出:BEM、OOCSS、SMACSS 和 ITCSS,如表 9-1 所列。我们将在下面的文本中讨论每一个,强调它们的一些优点和缺点。

表 9-1

CSS 方法

|

|

创造者

|

方法:官方网站

|
| --- | --- | --- |
| Two thousand and eight | 妮可·沙利文 | OOCSS: https://github.com/stubbornella/oocss/wiki |
| Two thousand and nine | Yandex | 嗯:https://en.bem.info/methodology/ |
| Two thousand and twelve | 斯努克 | SMACSS: http://smacss.com/ |
| Two thousand and fifteen | 哈里·罗伯茨 | 痒 3 : https://itcss.io/ |

以下方法的一个共同点是在 HTML 和 CSS 中大量使用类。他们都同意一个特定的观点——类在 HTML 中的唯一用途是用于 CSS 或 JavaScript 绑定。

需要指出的是,许多 CSS 专业人员混合搭配了这些方法中他们最喜欢的部分。像往常一样,选择对你和你的团队有用的,但是要始终如一。

OOCSS

面向对象的 CSS (OOCSS)最初是由妮可·沙利文在 Web Directions North 上提出的,它借用了面向对象设计的概念来为 CSS 提供结构。OOCSS 中的对象就是我们所说的小部件,Sullivan 将其定义为“一种重复的视觉模式,可以抽象为 HTML、CSS 甚至 JavaScript 的独立片段。该对象可以在整个站点中重用。 4

OOCSS 有两条核心规则,旨在生成灵活、模块化和可交换的小部件。他们是

  • 将结构与皮肤分开

  • 将容器与内容分开

这些规则将使用清单 9-1 中的 HTML 来说明。

<body>
  <div class="sidebar theme-light">
    <nav>
      <ul>
        <li><a href="/home">Home</a></li>
        <li><a href="/about">About</a></li>
      </ul>
    </nav>
    <form class="login">
      <input type="text" placeholder="Username">
      <input type="password" placeholder="Password">
      <button type="submit">Login</button>
    </form>
  </div>
  <main>
    <section class="hero theme-light">
      <p>Lorem ipsum dolor sit amet...<p>
      <button class="call-to-action">Subscribe</button>
    </section>
  </main>
</body>

Listing 9-1OOCSS Example HTML

将结构与皮肤分开

结构(或布局)是指元素在页面上的位置,或者这些元素的功能和交互。布局属性包括影响元素大小和位置的项目,如heightwidthmarginpaddingoverflow

皮肤(或主题)是指元素的视觉方面。主题属性包括colorborderbox-shadowfontopacity等。

结构和皮肤应该通过不同的类来应用,如清单 9-2 所示。

/* OOCSS wants this */
.theme-light {
  color: slategray;
  background-color: lightgoldenrodyellow;
  border: 1px solid navy;
}
.sidebar {
  padding: 1rem;
  float: left;
  width: 200px;
}
.hero {
  margin: 1rem 1rem 1rem 250px;
  padding: 1rem;
}

/* Not this */
.sidebar {
  color: slategray;
  background-color: lightgoldenrodyellow;
  border: 1px solid navy;
  padding: 1rem;
  float: left;
  width: 200px;
}
.hero {
  color: slategray;
  background-color: lightgoldenrodyellow;
  border: 1px solid navy;
  margin: 1rem 1rem 1rem 250px;
  padding: 1rem;
}

Listing 9-2OOCSS Separate Structure from Skin

这种方法允许将主题应用于广泛的元素,并且只在一个位置进行维护。为了按预期实现 OOCSS,有必要向 HTML 中添加类,以避免仅仅依赖 HTML 的语义。

将容器与内容分开

这基本上意味着更喜欢基于属性而不是位置的样式。因此,给定清单 9-1 中的 HTML,我们就有了清单 9-3 中所示的 CSS。

/* Given this default */
button {
  background-color: lightblue;
}

/* OOCSS wants you to do this */
.call-to-action {
  background-color: lightgreen;
}

/* Not this */
.hero button {
  background-color: lightgreen;
}

Listing 9-3OOCSS Separate Container from Content

这个建议有几个目的,比如更好的一致性和可维护性。一些具体目标:

  • 不管在什么位置,按钮看起来都是一样的,除非 HTML 通过类指定了其他东西。

  • 所有带有call-to-action类的元素将具有相同的外观,而不管标签或位置。

  • 查看按钮的 HTML,我可以很容易地找到它的规则集。

  • 避免人为夸大特异性,允许在必要时可预测地覆盖样式。

虽然这些是没有提供太多细节的通用指南,但是遵循这些建议没有明显的问题。

但是,请记住 CSS 要求每个属性都有一个值,所以任何没有提供的值都将使用默认值。此外,不同的元素(比如,<a><button>)对于这些属性有不同的默认值和不同的默认外观。因此,当使用 OOCSS 这样的方法并依赖于没有附带类型选择器的类选择器时,考虑一下当这个类应用于具有不同默认值的新元素时会发生什么。

不列颠帝国勋章

BEM 代表块、元素、修饰符,它总结了所使用的命名约定和组织的整体方法。BEM 中的“块”指的是我们的可重用小部件的概念。在 BEM 中使用“Block”和“Element”是很不幸的,因为它们的命名重叠了相似但不完全相同的 HTML 概念。(为了避免混淆,我们将在本章中使用斜体的 BEM 概念。)

虽然 BEM 是一个成熟的前端方法,但它是 CSS 开发人员中最流行的命名约定。命名遵循以下模式:

block__element--modifier

在命名之间使用两个下划线和连字符是为了在一个节名中使用一个连字符,例如

login-form__password-field--visible

边界元法中的一个常见问题是如何决定一个给定的项目应该是一个还是一个元素。一般原则是,如果代码段不能与其父容器分开使用,那么它就是一个元素。如果可以独立重用,那么就是一个5

清单 9-4 中的 HTML 显示了一个 BEM 命名的例子,它使用了与 OOCSS 例子相同的基本 HTML 结构。

<body>
  <div class="sidebar sidebar--theme-light">
    <!-- Mix: both an element and a block -->
    <nav class="sidebar__nav nav">
      <ul>
        <li class="nav__item"><a href="/home">Home</a></li>
        <li class="nav__item">
          <a href="/about">About</a>
        </li>
      </ul>
    </nav>
    <form class="login-form">
      <input type="text" placeholder="Username">
      <input
        type="password"
        class="login-form__password-field--visible"
        placeholder="Password">
      <button
        class="login-form__submit"
        type="submit">Login</button>
    </form>
  </div>
  <main>
    <section class="hero hero--theme-light">
      <p>Lorem ipsum dolor sit amet...<p>
      <button
        class="hero__call-to-action">Subscribe</button>
    </section>
  </main>
</body>

Listing 9-4BEM Example HTML

在很多情况下,你可能会参考尼古拉斯·加拉格尔简化的边界元命名约定 6 ,它看起来像这样:

ComponentName-descendent--modifier

您可能已经注意到,这种表示法用骆驼大小写替换了带连字符的部分名称,并使用了一种更好的命名约定,不会与 HTML 重叠。更新我们的 HTML 示例可能会给出类似清单 9-5 中的代码。

<body>
  <div class="Sidebar Sidebar--themeLight">
    <!-- Mix: both an element and a block -->
    <nav class="Sidebar-nav Nav">
      <ul>
        <li class="Nav-item"><a href="/home">Home</a></li>
        <li class="Nav-item">
          <a href="/about">About</a>
        </li>
      </ul>
    </nav>
    <form class="LoginForm">
      <input type="text" placeholder="Username">
      <input
        type="password"
        class="LoginForm-passwordField--visible"
        placeholder="Password">
      <button
        class="LoginForm-submit"
        type="submit">Login</button>
    </form>
  </div>
  <main>
    <section class="Hero Hero--themeLight">
      <p>Lorem ipsum dolor sit amet...<p>
      <button class="Hero-callToAction">Subscribe</button>
    </section>
  </main>
</body>

Listing 9-5Simplified BEM HTML

BEM 的一个目标是使内部的结构扁平化,这样如果 HTML 的嵌套发生变化,相关的 CSS 就不必改变。在清单 9-4 的例子中,我们可以从使用<ul><li>标签改为使用nav__item<div>标签,CSS 不需要改变。正如这个例子所示,BEM 不鼓励使用 HTML 元素作为 CSS 选择器。

BEM 的另一个目标是可预测和一致的命名以及简单的可维护性。例如,在一个项目中执行文本搜索来找到一个给定类名被使用的地方应该是非常容易的,这样就有可能放心地从我们的样式表中删除未使用的规则。

BEM 的一个好处是,基于,样式表可以非常容易地分成多个文件,并且具有很高的可信度。

BEM 中修改器的使用违背了 OOCSS 的建议,即创建表示皮肤或主题的通用且可重用的样式。使用 SCSS 混音至少可以部分克服这一点,但这是一个值得注意的区别。虽然它使 BEM 非常可预测,但它大大降低了组合可重用样式的能力,有利于非常明确。

SMACSS

CSS 的可扩展和模块化架构(SMACSS)是 CSS 方法论和同名书籍 7 ,都是 Jonathan Snook 写的。其核心是规则集的分类系统。有五个类别:

  • 基础–基础规则建立默认值。每个规则通常只适用于一个元素。这是标准化和基本字体大小的好地方。

  • 布局(Layout)——这是我们放置维度和定位声明的地方,以及我们的小部件协同工作可能需要的任何粘合剂。

  • 模块——这些是我们在 SMACSS 中找到可重用部件的地方。这里定义了这些模块化设计单元,如标注、侧栏和登录表单。

  • 状态–大多数 web 应用程序在设计组件中可视化地展示了大量的状态,这属于这里。这可以包括诸如可见/隐藏、活动/不活动、悬停、聚焦和展开/折叠的值。

  • 主题–影响外观、感觉和品牌(但不影响布局或功能)的声明通常是主题性的。这类似于 OOCSS 中的皮肤概念。

对事物进行分类的目的不是为了制造人为的障碍,而是为了更好地整理设计中的重复模式。SMACSS 并不惩罚违反分类准则的例外情况,而只是建议例外情况应被证明是有利的。

类别命名

基础类别中的规则通常不使用类,也不需要命名约定。对于布局规则,在类名上使用前缀l-(小写的“L”后跟一个连字符),比如.l-leftnav。对于州规则,使用一个is-前缀,例如.is-visible。这些规则如清单 9-6 所示,以说明它们如何适应 HTML 文档。

<body class="l-leftnav">
  <div id="sidebar" class="sidebar-left">
    <!-- Mix: both an element and a block -->
    <nav class="nav">
      <ul class="l-stacked">
        <li><a href="/home">Home</a></li>
        <li><a href="/about">About</a></li>
      </ul>
    </nav>
    <form class="login">
      <input type="text" placeholder="Username">
      <input
        type="password"
        class="is-visible password"
        placeholder="Password">
      <button type="submit">Login</button>
    </form>
  </div>
  <main>
    <section class="hero hero-light">
      <p>Lorem ipsum dolor sit amet...<p>
      <button class="call-to-action">Subscribe</button>
    </section>
  </main>
</body>

Listing 9-6SMACSS Naming

正如您在清单 9-6 中看到的,模块(小部件)只是有一个有意义的名字。我们可以通过“子类化”来进一步澄清模块,在名字中增加一个新的部分,类似于 BEM 中的修饰符。例如,.hero-light将是.hero的子类。清单 9-7 展示了如何在样式表中使用命名约定。

/* SMACSS wants you to do this */
.l-leftnav #sidebar { ... }
.login input[type=password] { ... }
.l-stacked > * { ... }
.hero { height: 8rem; border: 2px solid green; }
.hero-light { border-color: lightgreen; }

/* Not this */
#sidebar.sidebar-left { ... }
.login .password { ... }
.l-leftnav .nav li { ... }
.hero { height: 8rem; border: 2px solid green; }
.hero.hero-light {
  height: 8rem;
  border: 2px solid lightgreen;
}

Listing 9-7SMACSS Example CSS

推荐

中小企业联合会的建议有两个总体目标:

  1. 改善语义–这意味着让开发者更容易理解小部件和元素是如何以及为什么被使用的。

  2. 增加正交性——在 SMACSS 书中,Snook 将目标表述为“减少对特定 HTML 的依赖”, 9 ,这仅仅意味着 HTML 的重组应该对我们的 CSS 产生最小的影响。

基于这些目标,该方法推荐了一些指导原则:

  • 基于布局类而不是页面名称触发特定于布局的更改。

  • 在选择器中使用模块(小部件)类名,而不是 HTML 元素。

  • 避免在输入字段、按钮和表格等公共元素上设置主题默认值。

正如你可能已经注意到的,这些建议与我们在本章开始时建立的建议非常接近,这些建议来自于我们在第一章中对软件架构的探索。

ITCSS

由 Harry Roberts 创建的倒三角形 CSS (ITCSS)试图使用 CSS 的自然优先级来解决选择器特异性。与 OOCSS 和 SMACSS 完全不同,样式表根据它们的用途被分成层 10 ,如图 9-1 所示。

img/487079_1_En_9_Fig1_HTML.png

图 9-1

ITCSS 图

三角形的底部(在图表的顶部)代表最广泛和最不具体的规则,最明确的规则在三角形的点附近。这些层进一步定义如下:

  • 设置–用于预处理程序,这一层包括变量定义和站点范围的设置,如字体和颜色。通常不应输出任何 CSS。

  • 工具–进一步重用的全局混合和函数。这主要是为预处理程序设计的,通常应该避免输出 CSS。

  • 通用–样式、CSS 变量和框大小设置的标准化和重置。从这个布局上应该产生 CSS。这类似于 SMACSS 中的基本规则类别。

  • 元素–标准 HTML 元素的默认样式。

  • 对象–基于类的选择器,定义可重用的框架对象,如 OOCSS 媒体对象。

  • 组件(Components)——这是我们的小部件发挥作用的地方,大多数样式都在对象或组件层。

  • Utilities–可能需要能够覆盖三角形中早期所有内容的助手类。例如,显示/隐藏类。

ITCSS 没有定义命名或设计实践,而是推荐了样式规则本身的特定结构和组织,而没有提供关于允许何种规则的太多细节。 11 因此 ITCSS 完全兼容 OOCSS、SMACSS 和 BEM。

处理

软件架构师经常扮演的重要角色之一是帮助他们的团队建立良好的过程和实践。重要的是,您的流程要适合更大的网站发布和交付框架。

假设您正在构建一个网站,从公司营销团队管理的内容管理系统(CMS)中提取内容。虽然可以修改 CMS 产生的输出或训练营销团队向某些元素添加类,但这可能会给内容团队带来太大的压力。更可靠的做法可能是简单地编写 CSS,使您收到的内容达到预期的效果。在这个例子中,web 开发团队控制了一些 HTML,但不是全部。

决策

通常会有两种或两种以上的方法可以用来实现一个目标,这将由你来比较这些选项并作出决定。您选择的方法可能是第一个这样的决定,但是清单 9-8 展示了一些基于问题“我们如何在出错时用红色突出显示密码?”的代码决定

/* CSS Rulesets */
.red-border: { border: 2px solid red; }
.password: { color: black; }

<!-- Sample HTML -->
<form class="login">
  <input type="text" placeholder="Username">
  <input type="password" placeholder="Password">
  <button type="submit">Login</button>
</form>

/*** How do we highlight the password in red on error? ***/

/* OPTION 1 - Add CSS Selector */
.login [type=password]:invalid,
.red-border: {
  border: 2px solid red;
}
.password: { color: black; }

/* OPTION 2 - Use SCSS Mixin */
.login [type=password]:invalid { .red-border; }

<!-- OPTION 3 - Add class to HTML -->
<input
  class="red-border"
  type="password"
  placeholder="Password">

Listing 9-8CSS Reuse Dilemma

清单 9-8 中的例子展示了基于状态的代码重用的三个选项,但是清单 9-9 展示了在页面上设置默认字体的两种不同方式。

/* Cascade from the <body> tag */
body { font-family: helvetica, arial, sans-serif; }
p { font-family: 'Lucida Handwriting', cursive; }

/* Set explicitly on every element */
* { font-family: Consolas, 'Liberation Mono', monospace; }
p, p * {
  font-family: 'Lucida Handwriting', cursive;
}

Listing 9-9Set Default Font

大多数样式表作者选择从 body 标签级联字体默认值,如清单 9-9 所示,而不是使用通用选择器;然而,两者在技术上都是可能的。因为通用选择器的特异性为零,所以任何规则都会覆盖它;然而,由于它是在每个单独的元素上设置的,简单地重置<p>元素上的字体将而不是改变任何子元素的字体,比如锚标签、定义、按钮、标签等等。为了实现这种行为(当依赖于从body继承时,这是默认的),我们需要将更改单独应用到每个子元素,或者再次使用通用选择器。

Note

类名只为页面作者和开发人员提供意义。您对类名的选择应该反映这一点。您的类名不会向用户代理或系统传达任何意义,也不会向您页面的访问者显示。 12

更有可能的是,无论您选择哪种方法,您的样式表都不会满足本章开始时陈述的所有目标。你需要决定哪些目标对你的团队和情况最重要。您可能需要问的一些具体问题包括

  • 你的项目有多大/会有多大(部件、文件等)。)?

  • 你的团队有多大/你期望发展到多大?

  • 需要支持可互换主题吗?

  • 您的开发人员对 HTML 或 CSS 有更多的控制权吗(许多应用程序涉及从外部来源提取内容、数据或样式)?

林挺

有助于确保跨浏览器兼容性、可访问性和良好代码的一种方法是通过林挺。Linters 是检查语法和格式错误的工具。根据所使用的工具,有些人还会检查效率低下或有问题的模式。W3 有一个验证服务 13 ,但是许多人更喜欢一边验证代码一边运行。CSS Lint 等工具可以通过命令行 15 在浏览器14中运行,甚至作为编辑器扩展运行。 16

预编译的 CSS,比如 Sass 或更低版本,如果无效就不能编译,所以林挺不是问题。然而,linters 确实存在于预编译的 CSS 语言中,并且在调试和标记潜在的有问题的规则组合和冗余方面非常有用。

测试

像大多数代码一样,CSS 是可以测试的。有许多库可以做到这一点。有两种主要的方法可以测试 CSS:通过单元测试或者快照。

单元测试

CSS 的单元测试包括检查期望应用的样式实际上是在元素上设置的。诸如堂吉诃德 17 之类的库测试浏览器中正在呈现的内容是作者想要的。作者可以通过类或 ID 选择特定的元素,并验证任何 CSS 属性。

使用预编译器时,也可以测试 mixins。与函数类似,mixins 有输入和输出;因此,可以根据传递的输入来测试输出。咖啡师 18 或真 19 等库可以确保 mixin 输出符合作者的意图。

视觉回归测试

另一种测试 CSS 的方法是通过视觉回归测试。像 Cypress 20 或 Jest 21 这样的库提供了一个获取和比较快照的框架。通过对照参考快照检查当前快照,开发人员可以在 CSS 更改时得到意外副作用的通知。任何不匹配的快照都会产生一个标记,供作者检查。

代码审查

虽然林挺和自动化测试是以可预测的方式验证 CSS 的绝佳方式,但是可以通过自动化验证的 CSS 架构是有限的。有意义的代码审查是验证的好方法

  • 实现的一致性

  • 架构决策被遵循

  • 最小代码重复

此外,代码评审是一个公开讨论架构决策和这些决策的具体结果的机会。代码评审不应该作为评判其他开发人员的方式,而应该作为一个分享知识、互相学习、确保您的团队和项目长期成功的论坛。

摘要

在这一章中,你已经学习了我们可以使用 CSS 提供的一切来构建样式表的架构模式。具体来说,我们讨论了

  • 有助于评估各种选项的目标和指南

  • CSS 方法论 OOCSS、BEM、SMACSS 和 ITCSS

  • 你可以用来评估你的决策以保持正确的过程

现在,您已经准备好在自己的项目中选择和实现 CSS 架构方法了!祝你好运,编码愉快!

posted @   绝不原创的飞龙  阅读(66)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示