Bootstrap

【CSS in Depth 2 精译_098】17.3:CSS 动画延迟技术与填充模式设置 + 17.4:通过 CSS 动画传递意图的秘诀

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

  • 第五部分 添加动效 ✔️
  • 【第 17 章 动画】 ✔️
    • 17.1 关键帧
    • 17.2 3D 变换下的动画设置
      • 17.2.1 添加动画前页面布局的构建
      • 17.2.2 为布局添加动画
    • 17.3 动画延迟与填充模式 ✔️
    • 17.4 通过动画传递意图 ✔️
      • 17.4.1 对用户交互的反馈 ✔️
      • 17.4.2 吸引用户注意力 ✔️

《CSS in Depth》新版封面

《CSS in Depth》新版封面

译者按
本篇为最后一章中知识点最为密集的两节内容。虽然文字较多,但作者列举的三个典型案例聚焦的都是一些细节问题。这样的细节换作五年前的我一定是不屑一顾的。那会儿只要能实现功能点就行,根本不会在意用户体验和整体设计。而如今,在 IT 行业逐渐回归理性、产品体验和质量慢慢提上日程的当下,能否妥善处理好这些细节,就成了区分专业人士和半吊子人士最简单有效的方法。几乎所有优秀的产品、舒适的用户体验,背后都离不开对细节精益求精的把控和对细分领域长时间的刻意练习与深度思考。谁能提前在这些方面蓄力,谁就能率先进入新的“蓝海”,看到不一样的风景。

17.3 动画延迟与填充模式 Animation delay and fill mode

动画特效可以使用 animation-delay 属性进行延迟,该属性的行为与 transition-delay 属性类似。我们可以利用它来让动画交错发生,这和上一章中导航菜单交错产生过渡效果的方式差不多。通过让每个元素的动画延迟不同的时间,就能让这些元素一个接一个地飞入,如图 17.6 所示。

图 17.6 元素利用交错的动画特效飞入屏幕的页面效果

【图 17.6 元素利用交错的动画特效飞入屏幕的页面效果】

给这四个网格元素添加动画延迟效果的样式代码如代码清单 17.8 所示。不过这段代码并没有完全按照我们预期的方式运行。先把它们同步到本地样式表,再来研究一下问题出在哪儿、又该怎么解决。

代码清单 17.8 错开动画特效开始时间的示例样式代码

.flyin-grid__item {
  animation: fly-in 600ms ease-in;
}

.flyin-grid__item:nth-child(2) {
  animation-delay: 0.15s; /* 让每个元素的动画开始时间都比前一个略晚一些 */
}
.flyin-grid__item:nth-child(3) {
  animation-delay: 0.3s; /* 让每个元素的动画开始时间都比前一个略晚一些 */
}
.flyin-grid__item:nth-child(4) {
  animation-delay: 0.45s; /* 让每个元素的动画开始时间都比前一个略晚一些 */
}

要是在浏览器中加载示例页面,估计就会发现问题了。虽然动画确实是在预期的时间点开始播放的,但有些元素提前渲染到了页面上,随即立刻消失,然后又开始播放动画(如图 17.7 所示)。这就不太合理了,看起来也不是我们想要的效果。我们希望所有的元素一开始都是不可见的,只有在各自的动画开始播放的时候才会出现。

图 17.7 后面的元素在动画尚未开始播放时就提前出现在了最终位置

【图 17.7 后面的元素在动画尚未开始播放时就提前出现在了最终位置】

出现这样的问题,原因就在于设置在 transformopacity 属性的样式只在动画播放时才会生效。动画开始之前,网格元素在页面上是可见的,并且位于各自的正常位置;而当动画一开始播放,它们就瞬间变为 0% 关键帧上设置的样式效果。我们希望动画能在时间上延迟生效( apply backward in time),就像一直在第一帧位置暂停,直到该它播放的时候才正式开始。这样的效果可以通过 animation-fill-mode 属性来实现(如图 17.8 所示)。

图 17.8 利用 animation-fill-mode 属性,动画样式可以在动画播放的前后位置生效

【图 17.8 利用 animation-fill-mode 属性,动画样式可以在动画播放的前后位置生效】

上图中的深色方框表示动画特效的持续时间。属性 animation-fill-mode 的初始值为 none,其含义是:动画样式无论是在动画播放以前、还是在动画结束之后,都不会在对应元素上生效;若声明 animation-fill-mode: backwards,浏览器则会取出动画中第一帧的样式值,并在动画播放之前将其应用到对应元素上;若属性值为 forward,则会在动画结束后让最后一帧的样式值在对应元素生效;若值为 both,则会同时向动画前后沿用相应的样式。

要修复动画开始时的元素跳动问题,需要设置动画填充模式为后置填充(即 backwards)。根据代码清单 17.0 同步更新本地样式表。

代码清单 17.9 动画填充模式设为后置填充的示例样式代码

.flyin-grid__item {
  animation: fly-in 600ms ease-in;
  animation-fill-mode: backwards; /* 在动画开始前,应用第一帧上的动画样式 */
}

这样就能让动画一开始就暂停在第一帧位置,等待动画的播放。此时,在动画开始之前,网格元素向后平移至 800px 位置、并旋转了 90 度角、且不透明度为 0,时刻准备迎接动画的播放。

因为动画结束时元素就停在它们本来的位置上,所以无需设置前置填充(即 forward);内容卡片就这样圆满地从动画的最后一帧稳稳过渡到了元素的静止位置。

不妨再留一些时间驻足欣赏一下我们构建出的示例页吧。我们几乎将整本书中的 CSS 技术都汇聚在了这一个页面上,例如:Flexbox 弹性盒布局与 Grid 网格布局、元素的定位、响应式设计、自定义字体、颜色的选取、适当的阴影与渐变效果、以及 CSS 过渡与动画特效的结合等等。代码本身也根据不同的用途组织为相应的图层、以及轻量且便于理解的模块了。

17.4 通过动画传递意图 Conveying meaning through animation

人们对动画有个普遍误解,认为它们只是用来增强页面的趣味性,并没有什么实际用处。不得不说有时候确实如此(正如上一个案例),但也不总是这样。效果最好的动画特效往往并不是最后才加上的,而是融入到开发过程中。它们往往向用户传递着页面内容的某些特殊含义。

17.4.1 对用户交互的反馈 Responding to user interaction

动画可以向用户表明某个按钮被点击了,或者某个消息已被接收。如果您曾经提交过表单,回想一下是否经常记不清自己点没点过注册按钮,就知道这有多重要了。

我们不妨在一个新页面上构建一个包含提交按钮的小表单,然后添加一个旋转的指示器,让用户知道表单正在发送,浏览器正在等待服务器响应。该表单如图 17.9 所示,由一个文本标签、一个文本域(text area)和一个按钮组成。

图 17.9 一个带 “Save” 字样(保存)的简化表单

【图 17.9 一个带 “Save” 字样(保存)的简化表单】

我们来新建一个页面和空白样式表来实现上面这个表单。将代码清单 17.10 中的示例 HTML 添加到示例页。

代码清单 17.10 带保存按钮的表单 HTML 标记

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <form>
      <label for="trip">Tell us about your first trip to the zoo:</label>
      <textarea id="trip" name="about-my-trip" rows="5"></textarea><!-- 文本域 -->
      <button type="submit" id="submit-button">Save</button><!-- 提交按钮 -->
    </form>
  </body>
</html>

先实现一些基础样式,让页面具有合适的布局和外观;之后在加入一些有意义的动画特效来增强用户体验。根据代码清单 17.11 同步更新本地样式表。

代码清单 17.11 调整表单布局并设置基础样式

body {
  font-family: Helvetica, Arial, sans-serif;
}

form {
  max-width: 500px; /* 限定表单的最大宽度 */
}

label,
textarea {
  display: block;
  margin-bottom: 1em;
}

textarea {
  width: 100%;
  font-size: inherit;
}

button {
  padding: 0.6em 1em;
  border: 0;
  font: inherit;
  color: white; /* 白色字体 */
  background-color: oklch(54% 0.14 260deg); /* 蓝色背景 */
  transition: background-color 0.3s linear;
}
button:hover {
  background-color: oklch(46% 0.11 260deg); /* 鼠标悬停时按钮颜色加深 */
}

假设该表单时某个大型 Web 应用中的一部分。当用户点击 “Save” 字样的保存按钮时,该表单就会将数据发送到服务器,没准还会在收到响应后再页面上添加一些新内容。但是等待网络响应需要时间,如果在这期间能有一些形象的指示信息(visual indication)告诉他们数据以及提交了,并且很快就会有反馈,那么用户心里就会比较踏实。而动画正是提供此类指示信息的通用解决方案。

我们可以修改 “Save” 按钮,并增加一种 “加载中”(“is-loading”)的状态。此时按钮标签将被隐藏,取而代之的是一个旋转图标(如图 17.10 所示)。用户提交表单时,我们使用 JavaScript 给按钮添加样式类 is-loading,从而实现动画效果。

图 17.10 用户点击保存时,按钮会出现一个旋转图标

【图 17.10 用户点击保存时,按钮会出现一个旋转图标】

我们可以采用不同的方式设计出图中的旋转图标。这是我比较喜欢的一种设计:一个旋转的月牙形状,外观简约小巧但效果还不错。添加旋转图标需要对 CSS 做两处改动:先用边框和圆角半径制作出月牙形状,然后设置动画让它旋转起来。为此,还需要少量的 JavaScript 脚本,在按钮被点击时添加 is-loading 样式类。

相关的 CSS 代码如代码清单 17.12 所示。该示例代码会在按钮的一个设置了绝对定位的伪元素上添加动画特效。将它们同步更新到本地样式表。

代码清单 17.12 定义旋转动画以及 is-loading 状态的示例样式代码

button.is-loading {
  position: relative;
  color: transparent; /* 隐藏按钮文字 */
}
button.is-loading::after {
  position: absolute;
  content: "";
  display: block;
  width: 1.4em;
  height: 1.4em;
  top: 50%; /* 将伪元素定位到按钮中心位置 */
  left: 50%; /* 将伪元素定位到按钮中心位置 */
  margin-left: -0.7em; /* 将伪元素定位到按钮中心位置 */
  margin-top: -0.7em; /* 将伪元素定位到按钮中心位置 */
  border-top: 2px solid white;
  border-radius: 50%;
  animation: spin 0.5s linear infinite; /* 循环播放旋转动画 */
}

@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg); /* 令每次循环都旋转一周 */
  }
}

这样按钮的 is-loading 状态就定义好了。一旦触发,按钮的文字将在 color: transparent 的作用下变为不可见,并且伪元素通过绝对定位放在了按钮的中心位置。

这里的定位稍微有点麻烦。topleft 属性分别将伪元素下移了按钮高度的一半、并向右平移了按钮宽度的一半,这样伪元素的 左上角(top-left corner 恰好位于按钮的中心点。然后,负的外边距又将伪元素分别向上、向左拉回了 0.7em,而 0.7em 正好是按钮宽高的一半。这四个属性作用在一起,就能让伪元素在按钮的水平和垂直方向上同时居中对齐。您可以临时添加样式类 is-loading,并在浏览器的开发者工具 DevTools 中分别调整这些尺寸,体会一下它们是怎样让伪元素居中的。

伪元素定位后,再来看看动画的实现。这里用到了一个新的关键字来限定动画重复次数:infinite。也就是说,只要按钮添加了样式类 is-loading,动画便会一直重复。动画设置了从 0 度到 360 度的旋转变换,从而让伪元素可以完成一整圈旋转。由于单次动画结束时元素恰好停在了初始位置,使得每次动画的重复播放都能做到无缝衔接。

将代码清单 17.13 中的 script 标签添加到示例页面。这样借助 JavaScript 脚本,就能在单击按钮时添加 is-loading 类。记得把这段代码放到 <body> 标签的开头。

代码清单 17.13 点击按钮时添加 is-loading 类的示例 JavaScript 代码

<script type="module">
  var input = document.getElementById('trip');
  var button = document.getElementById('submit-button');

  button.addEventListener('click', (event) => {
    event.preventDefault();  // 组织表单提交
    button.classList.add('is-loading');  // 显示“加载中”旋转图标
    button.disabled = true;
    input.disabled = true;

    /* 此处代码用于 JavaScript 提交表单数据 */
  });
</script>

点击保存按钮时,preventDefault() 阻止了正常的表单提交。这样,在用户利用应用程序中的 JavaScript 提交表单数据时,就会停留在当前页面,而不是跳转离开。在此期间,输入框时禁用状态,并且按钮也添加了 is-loading 样式类,旋转图标也会渲染出来。加载页面并点击按钮,看看旋转指示器的最终效果。

我们在这里并没有真正提交表单数据,因为没有可供提交数据的服务器。而在实际的应用开发过程中,一旦服务器响应,我们就可以重新启用表单并移除 is-loading 类。为方便演示,本例只要刷新页面就能重置表单并移除样式类。

17.4.2 吸引用户注意力 Drawing the user’s attention

动画也可以用来吸引用户的注意力。如果预见到用户可能会在文本域中输入较多的内容,我们可以提醒用户在输入时及时保存;使用动画快速晃动按钮,就可以提示用户保存他们输入的内容(如图 17.11 所示)。

图 17.11 通过快速左右移动按钮来产生摇晃效果

【图 17.11 通过快速左右移动按钮来产生摇晃效果】

多次快速左右变换元素,就可以产生类似的晃动效果。我们可以定义一个关键帧动画,并使用 shake 类将动画应用到按钮元素上。根据代码清单 17.14 同步更新样式表。

代码清单 17.14 定义摇晃动画效果的示例样式代码

.shake {
  animation: shake 0.7s linear;
}

@keyframes shake {
  0%,
  100% { /* 动画运行期间,在多个位置使用相同的关键帧定义 */
    transform: translateX(0);
  }
  10%,
  30%,
  50%,
  70% {
    transform: translateX(-0.4em); /* 元素左移 */
  }
  20%,
  40%,
  60% {
    transform: translateX(0.4em); /* 元素右移 */
  }
  80% {
    transform: translateX(0.3em); /* 最后一次晃动减小平移量 */
  }
  90% {
    transform: translateX(-0.3em); /* 最后一次晃动减小平移量 */
  }
}

我们在这个动画中做了一些新的尝试:在整个动画过程中多次应用相同的关键帧定义。

在开始(0%)和结束(100%)关键帧,元素位于默认位置。因为这两个关键帧使用了相同的值,所以可以只定义一次属性值,并用逗号分隔。10%30%50%70% 处的关键帧也一样,都是把元素向左平移;而 20%40%60% 处的关键帧则是把按钮向右平移。80%90% 这两个关键帧分别向右侧和左侧平移,只不过幅度要小一些。

动画一共摇动了按钮四次,其中第四次幅度有所降低,用来模拟运动即将结束时按钮的减速过程。您也可以暂时把 shake 类添加到按钮上,这样页面加载时就能直接看到动画效果。

注意

样式表中的动画可以多次反复调用,因此动画的定义无需和最终使用动画的样式模块放在一起。我喜欢把所有的 @keyframes 定义聚集在一块儿,放到样式表的末尾。

最后,在我们认为用户可能需要保存输入内容的时候,使用 JavaScript 播放动画。这可以使用 keyup 时间监听器和超时函数来实现。当用户向文本域输入字符的时候,我们可以设置一个一秒的超时函数,向按钮添加 shake 类。如果用户在一秒结束之前输入了其他内容,我们就清除计时,并重新设置一个新的。试根据代码清单 17.15 同步更新本地示例页中的 script 标签内容。

代码清单 17.15 一秒延迟后添加 shake 样式类的示例 JavaScript 脚本

<script type="module">
  var input = document.getElementById('trip');
  var button = document.getElementById('submit-button');

  var timeout = null; // 定义一个引用超时设置的变量

  button.addEventListener('click', function(event) {
    event.preventDefault();
    clearTimeout(timeout); // 取消等待中的超时函数(如果有的话)
    button.classList.add('is-loading');
    button.disabled = true;
    input.disabled = true;
  });

  input.addEventListener('keyup', function() {
    clearTimeout(timeout); // 取消等待中的超时函数(如果有的话)
    // 等待一秒后添加 shake 样式类:
    timeout = setTimeout(function() {
      button.classList.add('shake');
    }, 1000);
  });
  // 动画结束后移除 shake 样式类
  button.addEventListener('animationend', function() {
    button.classList.remove('shake');
  });
</script>

现在加载页面,并在文本域中输入一些文字内容。等待一秒后,保存按钮将出现晃动。只要我们持续输入内容,计时器就会不断重置,摇晃动画便不会触发,直至下一次停止输入且超过一秒以后。这样晃动的按钮不会总去打断用户,只在用户中途停下来时才会提醒。

我们还用到了 JavaScript 中的 animationend 事件。摇晃动画播放结束时才会触发这个事件。事件触发后,shake 类会从按钮上移除,这样在用户下次输入并停下来时便于重新添加 shake 类,接着再次执行动画。

像这样使用 JavaScript 添加和移除样式类可能是操作动画最简单的方式了,但如果您特别熟悉这门语言,JavaScript 还提供了一整套处理 CSS 动画交互的 API,其中包含暂停、取消以及逆向播放动画等功能。想要了解更多信息,可以查看 MDN 的线上文档 Animation

无论是加载标识还是保存按钮的晃动特效,这些动画效果都想用户传达了很多信息。用户无需阅读任何说明就能知晓一切。这些效果能立马传递各自携带的信息,让用户界面不那么突兀(less obtrusive)。

我们在开发 Web 应用时,应该经常思考是否可以使用动画向用户提供有价值的反馈,即便是微不足道的动画特效。可能是在发送邮件时,可以飞出屏幕边缘的某个文本域;或者是删除草稿时,让草稿缩小并消失的某个动画特效。动画无需多么显眼或者炫酷(flamboyant),只需要给用户适当的反馈,让他们知道自己的操作确实是在按预期进行就行了。

想要使用一组出色的预定义关键帧动画集,推荐一个优秀的网站:animista。该网站提供了一组数量庞大且功能丰富的动画库供人们选用,例如像果冻一样的弹跳、滚动、摇晃等动画特效。



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

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

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

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

  • 第一章 层叠、优先级与继承(已完结)
    • 1.1 层叠
    • 1.2 继承
    • 1.3 特殊值
    • 1.4 简写属性
    • 1.5 CSS 渐进式增强技术
    • 1.6 本章小结
  • 第二章 相对单位(已完结)
    • 2.1 相对单位的威力
    • 2.2 em 与 rem
    • 2.3 告别像素思维
    • 2.4 视口的相对单位
    • 2.5 无单位的数值与行高
    • 2.6 自定义属性
    • 2.7 本章小结
  • 第三章 文档流与盒模型(已完结)
    • 3.1 常规文档流
    • 3.2 盒模型
    • 3.3 元素的高度
    • 3.4 负的外边距
    • 3.5 外边距折叠
    • 3.6 容器内的元素间距问题
    • 3.7 本章小结
  • 第四章 Flexbox 布局(已完结)
    • 4.1 Flexbox 布局原理
    • 4.2 弹性子元素的大小
    • 4.3 弹性布局的方向
    • 4.4 对齐、间距等细节处
    • 4.5 本章小结
  • 第五章 网格布局(已完结)
    • 5.1 构建基础网格
    • 5.2 网格结构剖析 (上)
      • 5.2.1 网格线的编号(下)
      • 5.2.2 网格与 Flexbox 配合(下)
    • 5.3 两种替代语法
      • 5.3.1 命名网格线
      • 5.3.2 命名网格区域
    • 5.4 显式网格与隐式网格(上)
      • 5.4.1 添加变化 (中)
      • 5.4.2 让网格元素填满网格轨道(下)
    • 5.5 子网格(全新增补内容)
    • 5.6 对齐相关的属性
    • 5.7 本章小结
  • 第六章 定位与堆叠上下文(已完结)
    • 6.1 固定定位
      • 6.1.1 创建一个固定定位的模态对话框
      • 6.1.2 在模态对话框打开时防止屏幕滚动
      • 6.1.3 控制定位元素的大小
    • 6.2 绝对定位
      • 6.2.1 关闭按钮的绝对定位
      • 6.2.2 伪元素的定位问题
    • 6.3 相对定位
      • 6.3.1 创建下拉菜单(上)
      • 6.3.2 创建 CSS 三角形(下)
    • 6.4 堆叠上下文与 z-index
      • 6.4.1 理解渲染过程与堆叠顺序(上)
      • 6.4.2 用 z-index 控制堆叠顺序(上)
      • 6.4.3 深入理解堆叠上下文(下)
    • 6.5 粘性定位
    • 6.6 本章小结
  • 第七章 响应式设计(已完结)
    • 7.1 移动端优先设计原则(上篇)
      • 7.1.1 创建移动端菜单(下篇)
      • 7.1.2 给视口添加 meta 标签(下篇)
    • 7.2 媒体查询(上篇)
      • 7.2.1 深入理解媒体查询的类型(上篇)
      • 7.2.2 页面断点的添加(中篇)
      • 7.2.3 响应式列的添加(下篇)
    • 7.3 流式布局
    • 7.4 响应式图片
    • 7.5 本章小结
  • 第八章 层叠图层及其嵌套
    • 8.1 用 layer 图层来操控层叠规则(上篇)
      • 8.1.1 图层的定义(上篇)
      • 8.1.2 图层的顺序与优先级(下篇)
      • 8.1.3 revert-layer 关键字(下篇)
    • 8.2 层叠图层的推荐组织方案
    • 8.3 伪类 :is() 和 :where() 的用法
    • 8.4 CSS 嵌套的使用
      • 8.4.1 嵌套选择器的使用
      • 8.4.2 深入理解嵌套选择器
      • 8.4.3 媒体查询及其他 @规则 的嵌套
    • 8.5 本章小结
  • 第九章 CSS 的模块化与作用域
    • 9.1 模块的定义
      • 9.1.1 模块和全局样式
      • 9.1.2 一个简单的 CSS 模块
      • 9.1.3 模块的变体
      • 9.1.4 多元素模块
    • 9.2 将模块组合为更大的结构
      • 9.2.1 模块中多个职责的拆分
      • 9.2.2 模块的命名
    • 9.3 CSS 的作用域
      • 9.3.1 CSS 作用域的就近原则
      • 9.3.2 划定作用域的边界
      • 9.3.3 CSS 中的隐式作用域
      • 9.3.4 关于 CSS 作用域与层叠图层
    • 9.4 CSS 模式库
    • 9.5 本章小结
  • 第十章 CSS 容器查询
    • 10.1 容器查询的一个简单示例
      • 10.1.1 容器尺寸查询的用法
    • 10.2 深入理解容器
      • 10.2.1 容器的类型
      • 10.2.2 容器的名称
      • 10.2.3 容器与模块化 CSS
    • 10.3 与容器相关的单位
    • 10.4 容器样式查询的用法
      • 10.4.1 将模块与所在容器解耦
      • 10.4.2 减少重复代码
    • 10.5 本章小结
  • 第 11 章 颜色与对比
    • 11.1 通过对比进行交流
      • 11.1.1 模式的建立
      • 11.1.2 还原设计稿
    • 11.2 颜色的定义
      • 11.2.1 色域与色彩空间
      • 11.2.2 CSS 颜色表示法(RGB、Hex、HSL、HWB、LAB/OKLAB、LCH/OKLCH)
    • 11.3 利用 OKLCH 处理颜色(上篇)
      • 11.3.4 从页面其他颜色衍生出新颜色(下篇)
    • 11.4 思考字体颜色的对比效果
    • 11.5 本章小结
  • 第 12 章 CSS 排版与间距
    • 12.1 间距设置
      • 12.1.1 使用 em 还是 px
      • 12.1.2 对行高的深入思考
      • 12.1.3 行内元素的间距设置
    • 12.2 Web 字体
    • 12.3 谷歌字体
    • 12.4 @font-face 的工作原理
      • 12.4.1 字体格式与回退处理
      • 12.4.2 同一字型的多种变体形式
    • 12.5 性能因素考量
      • 12.5.1 font-display 属性解析
      • 12.5.2 可变字体的用法
    • 12.6 调整字间距,提升可读性
      • 12.6.1 正文的字间距
      • 12.6.2 标题、小元素和间距
    • 12.7 本章小结
  • 第 13 章 渐变、阴影与混合模式
    • 13.1 渐变
      • 13.1.1 使用多个颜色节点(上)
      • 13.1.2 颜色插值方法(中)
      • 13.1.3 径向渐变(下)
      • 13.1.4 锥形渐变(下)
    • 13.2 阴影
      • 13.2.1 利用渐变和阴影打造立体感
      • 13.2.2 使用扁平化设计创建元素
      • 13.2.3 创建混合风格的按钮外观
    • 13.3 混合模式
      • 13.3.1 为图片上色
      • 13.3.2 混合模式的类型
      • 13.3.3 图片纹理的添加
      • 13.3.4 融合混合模式的用法
    • 13.4 本章小结
  • 第 14 章 蒙版、形状与剪切
    • 14.1 滤镜
      • 14.1.1 滤镜的类型
      • 14.1.2 背景滤镜
    • 14.2 蒙版
      • 14.2.1 带渐变效果的蒙版特效
      • 14.2.2 基于亮度来定义蒙版
      • 14.2.3 其他蒙版属性
    • 14.3 剪切路径
      • 14.3.1 多边形的裁剪路径
      • 14.3.2 Firefox 内置的剪切路径工具
      • 14.3.3 其他剪切路径类型
    • 14.4 浮动与形状
      • 14.4.1 浮动
      • 14.4.2 形状的定义
    • 14.5 本章小结
  • 第 15 章 过渡
    • 15.1 状态间的由此及彼
    • 15.2 定时函数
      • 15.2.1 定制贝塞尔曲线
      • 15.2.2 阶跃
    • 15.3 非动画属性
      • 15.3.1 不可添加动画效果的属性
      • 15.3.2 淡入与淡出
    • 15.4 过渡到自然高度
    • 15.5 自定义属性的过渡设置
    • 15.6 本章小结
  • 第 16 章 变换
    • 16.1 旋转、平移、缩放与倾斜
      • 16.1.1 变换原点的更改
      • 16.1.2 多重变换的设置
      • 16.1.3 单个变换属性的设置
    • 16.2 变换在动效中的应用
      • 16.2.1 放大图标(上)
      • 16.2.2 带“飞入”特效的文本标签的创建(下)
      • 16.2.3 过渡特效的交错渲染(下)
    • 16.3 动画的性能
    • 16.4 三维变换
      • 16.4.1 控制透视距离
      • 16.4.2 高级三维变换效果的实现
    • 16.5 本章小结
  • 附录
    • 附录A:CSS 选择器参考
    • 附录B:CSS 预处理器简介
;