渐变、阴影与混合模式

本章概要

至此,我们已经介绍了很多 CSS 知识,想必您已经对 CSS 的基本工作原理有了更深入的理解。我们不仅介绍了多种布局方法,也花了不少精力去考察如何让样式代码组织高效且易于维护,甚至还从头开始探讨了创建一个网站必备的核心要素。最后还教会您如何关注间距、排版及其他页面设计相关的细节。

接下来的两章内容将重点介绍特定页面效果的实现技巧,以及增强页面视觉趣味性的相关方法。其中一些效果您今后可能会经常用到,特别是渐变与阴影方面的内容;而其他效果可能只是偶尔会考虑。但可以肯定的是,您绝不会将所有特效都堆到一处。因此后续内容也不会创建面面俱到的综合示例页,而是聚焦单独的示例来进行讲解。

以图 13.1 所示的按钮为例,这里用到了渐变背景和投影两种特效,使页面具有了立体感。背景色从顶部的中度蓝(颜色值 #57b)过渡到了底部的深蓝(#148)。您可能甚至都没留意到这层渐变,但是再配上底部和右侧边缘的阴影效果,就让按钮整体呈现出了立体感。

图 13.1 设置了渐变背景和阴影效果的示例按钮
图 13.1 设置了渐变背景和阴影效果的示例按钮

本章将重点介绍渐变与阴影的工作原理,并探讨几个实际应用。然后再介绍一种非常有意思的页面特效,称为 混合模式(blend modes。它可以把多个背景图片和背景色按照不同的方式混合在一起使用。

13.1 渐变

Gradients

前面的章节已经介绍了纯色背景和一些背景图片的使用方法,但是 background 属性还有很多功能有待探索。其实,background 是以下八个样式属性的简写:

本章将介绍上述属性。此刻您需要了解的是,使用简写属性(background)虽然可以设置指定的样式,但同时也会把其他属性值重置为初始值。因此,在需要对多个背景属性进行操作时,我往往会单独设置想要的属性。

background-image 属性非常有意思。除了之前介绍的通过 URL 来引用某个图片的用法外(第 7 章中的 background-image: url(coffee-beans.jpg)),该属性还可以接受一个渐变函数。例如,定义一个从白色过渡到蓝色的渐变色,如图 13.2 所示。

Figure 13.2 A linear gradient from one color to another
图 13.2 由一种颜色线性渐变到另一种颜色的效果示意图

渐变是一个非常有用的特效。我们先来看看渐变的工作原理,然后在列举几个实际案例。要尝试渐变设置,需要创建一个新页面和样式表。添加代码清单 13.1 中的 CSS 样式,其中用到了 linear-gradient() 函数来定义渐变效果。

代码清单 13.1 基础线性渐变示例

.fade {
  height: 200px;
  width: 400px;
  background-image: linear-gradient(to right, white, blue); /* 从左至右、从白色渐变为蓝色 */
}

渐变效果其实就是背景图片,渐变本身不会影响元素的尺寸大小。为了方便演示,我给该元素明确设置了宽高。该元素是没有内容的,因此必须手动给定一个高度才能看到渐变效果。

linear-gradient 函数通过三个参数来定义渐变效果:角度(angle)、起始颜色(starting color)与终止颜色(ending color)。本例中的角度值即 to right,表示渐变效果将从元素左侧开始(即白色),平滑过渡到右侧(即蓝色)。此外,也可以采用其他颜色表示方法,例如十六进制法(#0000ff)或者 OKLCH 值(oklch(45% 0.31 264deg))或者关键字 transparent。试根据代码清单 13.2 同步更新页面元素,查看本地渐变效果。

代码清单 13.2 带渐变背景效果的示例元素 HTML 代码

<!doctype html>
<html lang="en-US">
<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="./style.css">
</head>
<body>
  <div class="fade"></div><!-- 带渐变背景效果的示例元素 -->
</body>
</html>

CSS 提供了多种方式来指定渐变的角度值。在本例中,我们使用了 to right,当然也可以使用 to top 或者 to bottom,甚至可以指定某个对角(corner),例如 to bottom right,表示渐变效果将从元素的左上角开始,逐渐过渡到元素右下角。

为了更精确地控制角度,也可以使用更具体的单位,例如“度”。角度值 0deg 表示垂直向上(相当于 to top);比它更大的角度值会沿着顺时针方向变动,因此 90deg 表示向右渐变,180deg 表示向下渐变,360deg 又回到了向上渐变。因此,代码清单 13.3 中的声明与前面的示例等价。

代码清单 13.3 使用度(deg)为单位的渐变示例代码

.fade {
  height: 200px;
  width: 400px;
  background-image: linear-gradient(90deg, white, blue); /* 这里的 90deg 相当于前面的 to right */
}

度是最常用的单位,此外还有一些其他单位可以用来表示角度值,如下所示:

可以尝试一下在渐变中设置不同的角度值,看看效果如何。

注意
以上介绍的角度单位适用于任意需要设置 CSS 角度值的地方,包括各种颜色表示法中的色调的角度值,例如 oklch(45% 0.31 0.75turn) 就是一个合法的 OKLCH 颜色值。

13.1.1 使用多个颜色节点

Multiple color stops

大部分渐变只涉及两种颜色,从一种颜色过渡到另一个即可。也可以定义包含多个颜色的渐变效果,其中每个颜色都可以成为一个 颜色节点(color stop)。图 13.3 为包含了三种颜色节点(蓝绿色、白色、蓝色)的渐变样式示例。

Figure 13.3 Gradient with three color stops
图 13.3 包含三个颜色节点的渐变色效果

通过向 linear-gradient() 函数添加更多颜色,就可以插入多个颜色节点。试根据代码清单 13.4 所示代码同步更新样式表,然后在页面上查看渐变效果。

代码清单 13.4 包含多个颜色节点的线性渐变样式声明

.fade {
  height: 200px;
  width: 400px;
  background-image: linear-gradient(90deg, turquoise, white, blue); /* 指定多个颜色节点 */
}

一个渐变样式可以接受任意数量的颜色节点,各节点间通过逗号分隔。渐变函数会自动均匀平铺这些颜色节点。在本例中,最左侧(即 0% 位置)从蓝绿色开始,然后过渡到中间位置(即 50%)的白色,再到最右侧(即 100%)的蓝色。我们也可以在渐变函数中为每个颜色节点手动指定位置。例如代码清单 13.4 中的渐变样式等效于以下代码:

linear-gradient(90deg, turquoise 0%, white 50%, blue 100%)

上述代码不难看出,颜色节点的位置是可以按需调节的,不必非得均匀分布。指定具体位置时也不必非得使用百分数,换成像素单位制、相对单位 em 以及其他长度单位也是完全有效的写法。

13.1.1.1 条纹

Stripes

如果在同一个位置设置了两个颜色节点,那么渐变会直接从一种颜色变为另一种颜色,而不是平滑过渡。如图 13.4 所示的渐变色效果,从蓝绿色开始,直接变为白色,随后又变为蓝色。整体呈现条纹状。

图 13.4 通过在同一位置放置两种颜色节点的渐变声明实现的条纹效果
图 13.4 通过在同一位置放置两种颜色节点的渐变声明实现的条纹效果

实现该渐变效果的样式代码如代码清单 13.5 所示。注意渐变中有四个颜色节点,其中两个为白色。

代码清单 13.5 同一个位置放置两个颜色节点,从而实现条纹效果

.fade {
  height: 200px;
  width: 400px;
  background-image: linear-gradient(
    90deg,
    /* 相同位置上的各颜色节点: */
    turquoise 40%, 
    white 40% 60%, 
    blue 60%
  );
}

第一个颜色节点是位于 40% 处的蓝绿色(turquoise),所以纯色效果从左侧边缘一直延伸到 40% 处;又因为第二个颜色节点为白色,且同样位于 40% 位置,因此渐变从这里开始变为白色;随后另一个白色节点位于 60% 处,从而令 40% 到 60% 之间的渐变效果均为白色。最后一个颜色节点也位于 60% 处,颜色为蓝色,因此从这里开始突变为蓝色,并一直延伸到元素右边缘。

这些颜色节点的位置同样可以换成任何有效的长度值,因此除了使用 40%,还可以换成 160px 或者 10em 等。

13.1.1.2 重复渐变

Repeating gradients

尽管前面的例子只是为了演示,但也可以用来实现一些有趣的效果,特别是搭配另一个略有不同的渐变函数 repeating-linear-gradient() 时。该函数与 linear-gradient() 函数的用法基本相同,唯一的区别在于该函数能够重复渲染。利用这一特性可以实现类似理发店门外的旋转招牌效果,非常适合作为进度条的样式(如图 13.5)。

图 13.5 使用重复线性渐变的条纹状进度条
图 13.5 使用重复线性渐变的条纹状进度条

对于重复渐变,最好使用特定的长度而不是百分比值,因为设置的值决定了要重复渲染的图片尺寸。相应的条纹效果样式代码详见代码清单 13.6。试根据以下代码同步更新本地样式表。

代码清单 13.6 创建沿对角线倾斜的条纹进度条

.fade {
  height: 1em;
  width: 400px;
  /* 浅蓝与深蓝色交替生成条纹效果 */
  background-image: repeating-linear-gradient(
    -45deg,
    oklch(60% 0.1 257deg) 0px 10px,
    oklch(40% 0.1 257deg) 10px 20px
  );
  border-radius: 0.3em;
}

有时,将一个半成品的代码片段改造成自己需要的效果,比从零开始实现要容易一些。您可以在 CSS-TRICKS 网站的这篇 Stripes in CSS 文章中找到更多灵感。

13.1.2 颜色插值方法

Color interpolation

在第 11 章中,我们学习了 CSS 颜色的各种表示方法以及色彩空间方面的知识。就在前不久,浏览器对渐变效果的计算都还仅限于 sRGB 色彩空间;如今,使用其他色彩空间来设置渐变已经成为了人们新的选择。在色彩渐变过程中,浏览器对于中间色的计算结果很可能会因为所选色彩空间的不同而存在显著差异。因为在不同的色彩空间中,色彩的几何排布情况各不相同,从而导致一个色彩空间中两个颜色点之间的颜色与另一个色彩空间截然不同。

图 13.6 给出了两个示例渐变效果。它们都是从左侧的黄色过渡到右侧的蓝色,但由于分属不同的色彩空间,用到的插值算法也不同,从而导致了风格迥异的中间渐变色效果。

图 13.6 色彩空间截然不同的两种渐变效果可能存在显著差异
图 13.6 色彩空间截然不同的两种渐变效果可能存在显著差异

在本例中,sRGB 色彩空间下的渐变设置会在其中心区域穿过某个灰色区间,这在 RGB 相关的颜色理论支撑下是解释得通的。黄色 yellowRGB 颜色值为 rgb(255 255 0),而蓝色 blue 的值为 rgb(0 0 255)。如果每个参数维度都按线性插值计算,则会在渐变的中心位置得到一个红、绿、蓝分量皆为 127.5 的过渡色,即本例中的灰色,有时也被称为 “灰色死区(gray dead zone 1)”。在 sRGB 色彩空间下,位于色轮(color wheel)对侧或大致对侧的任意两种颜色在设置渐变效果时都会看到类似的结果,其渐变色必将穿过色轮中间的灰色地带。

另一方面,黄色和蓝色在 OKLCH 色彩空间下可以分别表示为 oklch(97% 0.21 110deg)oklch(45% 0.31 264deg)。此时的渐变计算主要是将色调的角度由 110 度变为 264 度,渐变色依次穿过绿色(green)、青色(teal)及蓝绿色(cyan)。与 sRGB 时直接穿过色轮的中心区域不同,OKLCH 色彩空间下的颜色渐变会绕道而行,留下沿途经过的各种中间色。

警告
在 CSS 渐变中指定色彩空间仍是一个相对较新的功能特性。截至 2024 年年中,Firefox 尚未提供相应支持。因此在启用该特性时,还应该准备一个回退版的渐变设置,以适应不支持该特性的应用场景。

在渐变中手动指定颜色空间的具体写法如代码清单 13.7 所示。这里为 Firefox 及其他老版本浏览器预备了一个回退方案。注意观察,我们在 linear-gradient() 函数的第一个参数中添加了一个关键词 in oklch。根据下列代码同步更新样式表:

代码清单 13.7 指定色彩空间为 OKLCH 的 CSS 渐变写法示例

.fade {
  height: 200px;
  width: 400px;
  background-image: linear-gradient(90deg, yellow, blue); /* 老版本浏览器的回退方案 */
  background-image: linear-gradient(90deg in oklch, yellow, blue); /* 指明色彩空间为 OKLCH 时的渐变写法 */
}

在确保您当前的浏览器支持 OKLCH 色彩空间的情况下,可以利用浏览器的开发者工具 DevTools,通过禁用或启用第二个 background-image 规则集来查看二者的显著区别。

在指定某个具体色彩空间时,必须使用关键字 in,然后再跟上所需的色彩空间名称。也可以指定其他 CSS 支持的色彩空间名称,如极坐标色彩空间中的 hslhwblchoklch,以及矩形色彩空间下的 srgbsrgb-linearlaboklabxyzxyz-d50 或者 xzy-d65(注意,不是每个色彩空间都有对应的 CSS 颜色语法函数)。如果不指定色彩空间,默认按 sRGB 进行插值运算。

由于每种色彩空间渲染出的渐变效果各不相同,因此当需要设置特定的渐变效果时,多尝试几种色彩空间不失为寻求最优方案的一个有效途径。通常情况下,如果渐变涉及的两种色调存在明显差异,此时指定某个极坐标色彩空间(如 oklchhsl)效果可能会更好一些;若要从更鲜艳的色彩过渡到中性色调(如灰色、黑色、白色或透明色等),则最好选用矩形色彩空间(如 srgb)。

注意
CSS 渐变中涉及的颜色插值算法与第 11 章中讲过的 color-mix() 函数中的用法相同。例如,声明 color-mix(in srgb, white 20% blue) 的渲染效果,从数学的角度看,与 srgb 色域下由白到蓝渐变、且距白色节点 20% 处的渐变色相比,效果是一样的。

另外,谷歌的 Chrome 团队还针对当前所有可用的色彩空间在 Code Pen 网站上制作了几个渐变效果的演示案例,具体的渲染效果详见:https://codepen.io/keithjgrant/full/GRzjGqL。您不妨快速浏览一遍,了解各个色彩空间在渐变设置中的差异。值得注意的是,在这些演示案例中个别示例还暴露了一些浏览器的 Bug,这个问题稍后再做处理。

13.1.2.1 极坐标色彩空间下的高级设置

More options in polar color spaces

在极坐标色彩空间下,色调是一个可以循环取值的参数(circular value)。因此,在设置两个不同色调的渐变效果时,渐变运算可以分别沿着色轮的两个不同方向进行。例如,由红色渐变为黄色,通常会出现橙色的渐变色。此时采用的是较短的渐变路径,也是 CSS 渐变默认的方向。

然而上述渐变也可以沿色轮中较长的路径进行,使其依次经过品红(magenta)、紫色(purple)、蓝色(blue)、蓝绿色(teal)以及绿色(green)。若两种颜色在色轮上的距离较近,那么沿较长路径定义的渐变效果将渲染出一道绚丽彩虹色。您甚至可以将两个颜色节点指定为同一颜色,从而得到长路径下的完整彩虹效果,如图 13.7 所示的色轮路径示意图。

图 13.7 极坐标色彩空间下的渐变设置可以沿着色轮的两条长短不一的路径分别进行

【图 13.7 极坐标色彩空间下的渐变设置可以沿着色轮的两条长短不一的路径分别进行】

渐变路径的变更可以通过在渐变函数中添加关键字 long hue 实现。根据代码清单 13.8 同步更新本地样式表,并查看最终的渐变效果。

代码清单 13.8 long hue 在渐变函数中的示例应用

.fade {
  height: 200px;
  width: 400px;
  background-image: linear-gradient(90deg, yellow, blue);
  background-image: linear-gradient(
    90deg in oklch longer hue, /* 令渐变沿较长的色调路径进行 */
    yellow,
    blue
  );
}

在渐变函数中,第一个参数很可能因为声明了这些配置项而书写得较长,例如上述代码。此时应该像示例代码那样,将样式声明分为多行进行书写。

CSS 提供了四个不同的关键字来设置渐变的色调方向,它们仅在极坐标色彩空间(polar color space)下有效:

不妨尝试在渐变设置中使用上述关键词,并查看其相应的渲染效果。在本例中,increasing hue 等效于 shorter huedecreasing hue 等效于 longer hue。究其原因,示例中的黄色和蓝色在色轮上的角度分别为 110 度和 164 度(这些数值是通过浏览器的开发者工具、将黄色 yellow 和蓝色 blue 分别转换为对应的 OKLCH 颜色值后得到的)。但如果使用了不同的颜色节点(color stops),情况则可能大不相同。

13.1.2.2 浏览器 Bug 与颜色问题

Browser bugs and color quirks

CSS 许多新推出的颜色特性对于浏览器而言都相对较新,并且其中也存在一些错误(bugs),尤其是在渐变中使用 OKLCHHSL 色彩空间时:对于某些色调(特别是蓝色),如果其中一个颜色节点为白色、黑色、或透明,可能会出现意外状况。

例如,在 OKLCHHSL 色彩空间下、由蓝色渐变到某种非饱和色(如白色或黑色)的过程中,都会产生绿色或紫色的渐变色,具体效果视非饱和色的颜色深度以及被测浏览器而定;再比如,在从红色渐变到某非饱和色的过程中,中间位置会生成橙色(orange)或酒红色(burgundy)的渐变色。希望这些问题都能在被波及浏览器的后续版本更新中得以修复。在将白色、黑色、灰色或透明色设为渐变效果的颜色节点时,目前避开上述问题的最佳方案,是使用 sRGB 或者其他矩形色彩空间。

如果实在要使用 OKLCHHSL 色彩空间,上述问题也可以通过这样的方式来解决:采用同样的色彩空间来指定渐变需要的颜色;然后在保持相同色调的情况下,适度微调色度(或饱和度)参数,以此作为渐变中的其他颜色节点(color stop)。比如说,在 OKLCH 色彩空间下定义一个由蓝色 blueoklch(100% 0.001 264deg) 的渐变效果,而非直接从蓝色 blue 渐变到白色 white。该方案的更多详细介绍,可以参考我写的这篇文章:https://mng.bz/2Kp0

13.1.3 径向渐变

Radial gradients

线性渐变应该是您今后会用到的最常见的渐变类型了。此外,CSS 还提供了其他渐变类型,其中一个便是本节要介绍的 径向渐变(radial gradient。径向渐变不是从元素的一端开始并沿着单一方向过渡到另一端,而是从一个点开始,全方位像外扩展。其基本示例如图 13.8 所示。

图 13.8 由某个点向外扩展的径向渐变示意图
图 13.8 由某个点向外扩展的径向渐变示意图

径向渐变可通过函数 radial-gradient() 来定义。具体示例如代码清单 13.9 所示。试根据下列代码同步更新本地样式表。

代码清单 13.9 径向渐变基础示例代码

.fade {
  height: 200px;
  width: 400px;
  background-image: radial-gradient(white, blue); /* 从中心处的白色渐变到边缘处的蓝色 */
}

默认情况下,渐变在元素中是从中心开始,并平滑过渡到元素边缘。渐变整体呈椭圆形,它会随元素尺寸大小进行变化(也就是说,对于较宽的元素,其径向渐变也较宽,反之亦然)。

与线性渐变一样,径向渐变同样支持颜色节点(color stops)。您既可以设置多个节点,并利用百分比或长度单位准确定义节点在渐变中的位置;同时也可以像线性渐变一样指定某个色彩空间。

径向渐变函数的第一个参数还可以实现如下高级配置:

利用 repeating-radial-gradient() 函数可以重复生成图样,形成同心圆环。其参数与 radial-gradient() 函数中的参数相同。

这些特性大部分可以通过典型示例解释清楚,图 13.9 列举了几个例子以及相应的样式代码。建议您在页面中尝试一下,或者修改为自己的代码。

图 13.9 径向渐变典型示例
图 13.9 径向渐变典型示例

在实际开发中,我发觉很少遇到要对径向渐变作任何复杂操作的情况,因为基本的应用形式已经能够满足绝大部分需求。如果想更深入地了解径向渐变的工作原理,可以参考 MDN 相关文档:https://mng.bz/1Gmj

13.1.4 锥形渐变

Conic gradients

CSS 渐变的最后一个类型为 锥形渐变。相比径向渐变从椭圆的中心向外逐渐改变颜色,锥形渐变则是沿圆形方向围绕椭圆逐渐改变颜色。锥形渐变的一个典型示例如图 13.10 所示。

图 13.10 锥形渐变沿圆形方向改变颜色示意图

【图 13.10 锥形渐变沿圆形方向改变颜色示意图】

锥形渐变可通过 conic-gradient() 函数、或者其重复渐变版本 repeating-conic-gradient() 来定义。该渐变的具体写法如代码清单 13.10 所示。

代码清单 13.10 锥形渐变的基础示例

.fade {
  height: 200px;
  width: 400px;
  background-image: conic-gradient(white, blue);
}

如本例所示,锥形渐变在渐变结束时(即最后一个颜色节点和第一个节点之间)会出现一个突然的过渡效果。这会在两个颜色间产生硬边缘(hard edge)。为此,可以让首尾两个颜色节点均为相同的颜色来解决该问题,例如写作:conic-gradient(white, blue, white)

与线性渐变、径向渐变一样,锥形渐变函数的第一个参数也可以进一步实现如下高级设置:

与其他渐变函数一样,您还可以指定颜色节点的具体位置。位置参数既可以用百分比表示,又可以写为某角度值。建议您在测试页面尝试这些配置项。有关锥形渐变的更多示例,可参考 MDN 的在线文档:https://mng.bz/PZGn

利用图形界面来配置渐变应该会很有帮助。推荐一个我爱用的网站资源 https://gradient.style/。它支持 OKLCH 颜色语法及 CSS 的这三种渐变类型。

前面演示的大部分示例都用到了色彩对比极为鲜明的颜色组合,这么做旨在凸显渐变的视觉效果,让渐变的行为特征清晰可辨;不过在实际的项目开发中,使用对比度较低的颜色组合效果往往会更好。

比如,不要从白色直接过渡到黑色,可以从白色渐变到浅灰色;或者在两种相仿的蓝色之间设置渐变。这样不会让用户产生不适感。在某些特定场合下,用户甚至都察觉不到渐变的存在,但依然可以给页面带来些许立体感。后面我会展示一些关于渐变的实际应用案例。不过在那之前,我们先来看看阴影。

13.2 阴影

Shadows

阴影是另一种可以给页面增加立体感的特效。有两个属性可以创建阴影,box-shadow 可以为元素盒子生成阴影,text-shadow 则可以为渲染后的文字生成阴影。前面章节已经用过一两次 box-shadow 属性了,本节再进一步研究一下它的工作原理。

声明 box-shadow: 15px 15px black 生成了如图 13.11 所示的阴影效果。其中 15px 代表偏移量,即阴影从元素的位置偏移了多少距离(方向先水平、再垂直)。如果偏移量都设为 0,则阴影将直接渲染到元素后方(behind the element)。black 指明了阴影的颜色。

图 13.11 一个基础的盒阴影效果

【图 13.11 一个基础的盒阴影效果】

默认情况下,阴影与元素的大小和尺寸完全相同。如果元素设置了 border-radius,则阴影相应地也会带有圆角。阴影的水平偏移量(x)、垂直偏移量(y)以及颜色在定义是都是不可或缺的,还有两个可选配置:模糊半径(blur radius)和扩展半径(spread radius)。完整的语法结构如图 13.12 所示。

图 13.11 盒阴影语法规则

【图 13.11 盒阴影语法规则】

模糊半径用于控制阴影边缘模糊区域的大小,可以为阴影生成一个更柔和、略带透明的边缘效果。扩展半径则用于控制阴影的大小,设为正值可令阴影全方位变大;为负值则会变小。

13.2.1 利用渐变和阴影打造立体感

Adding depth with gradients and shadows

下面利用渐变和阴影来实现图 13.13 所示的按钮效果。从上到下的渐变设置可以让按钮产生弧形的 3 D 效果,而阴影设置进一步增强了这种效果。此外,我们还将在本例中通过 :active 伪类来实现另一种阴影效果,并令其在按钮按下时生效。

图 13.13 使用了渐变和阴影的按钮效果

【图 13.13 使用了渐变和阴影的按钮效果】

本例中的渐变幅度很小,小到您甚至都不会立即注意到;但它确实能让按钮带有一丝圆润饱满的感觉。阴影做了一些模糊处理,这样看起来更自然。点击按钮时,阴影效果没有了,取而代之的是出现在按钮边框内部的 内嵌(inset 阴影。通过在盒阴影的开头添加关键字 inset,阴影效果渲染在了元素内部而非外部。该内嵌阴影效果覆盖了背景图片效果(即渐变)。

内嵌阴影让按钮有了一种被摁下的感觉,就仿佛用户真的在网页上按下了按钮一样;只要松开鼠标,按钮又恢复了最初的效果。这是通过按钮的 :active 状态实现的。

为按钮创建一个新页面并关联一个新的样式表,然后将如下的按钮 HTML 标记添加到页面中:

<button class="button">Sign up now</button>

AI写代码html
1

接着将代码清单 13.11 中的样式添加到样式表。这些样式覆盖了浏览器默认的字号和边框效果,同时还设置了按钮的大小,并添加了一个带盒阴影效果的渐变背景。

代码清单 13.11 带有渐变及阴影效果的按钮样式代码

.button {
  padding: 1em;
  border: 0;
  font-family: Helvetica, Arial, sans-serif;
  color: white;
  border-radius: 0.5em;
  background-image: linear-gradient( /* 从中蓝色渐变到略深一点的蓝色 */
    to bottom,
    oklch(57% 0.11 263deg),
    oklch(40% 0.13 263deg)
  );
  box-shadow: 0.1em 0.1em 0.5em oklch(26% 0.07 263deg); /* 模糊半径为 0.5em 的深蓝色阴影 */
}

.button:active {
  /* 两个内嵌式盒阴影效果 */
  box-shadow: inset 0 0 0.5em oklch(26% 0.07 263deg),
              inset 0 0.5em 1em rgb(0 0 0 / 0.4);
}

background-image 属性设置了由两个相仿蓝色组成的渐变。盒阴影的偏移量并不多,仅向右、向下各偏移了 0.1em;模糊效果也比较温和,只有 0.5em。阴影偏移量越大,图片从页面被“抬升”的感觉就越明显,相应的立体感也就更强。在按钮激活状态下,盒阴影效果也随之改变。

注意,上述代码给激活状态下的按钮设置了多个阴影效果,并用逗号隔开。利用这种形式可以给元素添加多个阴影效果。

其中第一个内嵌阴影(即 inset 0 0 0.5em oklch(26% 0.07 263deg))的偏移量均为 0,略带模糊效果。这样会在元素的边缘内添加一个环形阴影;第二个内嵌阴影(即 inset 0 0.5em 1em rgb(0 0 0 / 0.4))在垂直方向略有些偏移,从而使按钮顶部更加明显。RGB 颜色表示法定义了一个半透明的黑色。建议您自己动手改改这些参数值,看看它们究竟是如何影响最终的渲染效果的。

将屏幕上的元素设计得如同真实世界的实物(该方法也称为 拟物化设计(skeuomorphism),这种设计理念在前几年应用广泛。在真实世界中,物体是不具备完美的纯色质感的。即使是光滑的表面,也会受到各种光线反射的影响,产生高亮区域(highlights)和阴影区域(shadows)。

为按钮添加圆润饱满的外观、角度合适的阴影,就可以让它看起来像个真实的物件。其他常见的拟物化设计元素还有针织边框以及类似皮革纹理的图片等。虽然这种设计理念已经不太常见了,但它也可以作为 CSS 渐变与阴影效果协同工作的经典案例。下面让我们再看看可用于按钮样式设计的一些其他方法。

13.2.2 使用扁平化设计创建元素

Creating elements with a flat design

在拟物化设计追求尽量贴近真实世界的同时,扁平化设计则坦然接受现代社会业已数字化的事实。扁平化设计讲究色彩明快统一、外观简洁明了,这就意味着尽量少使用渐变、阴影和圆角。颇具讽刺意味的是,扁平化设计理念的日益兴盛,恰恰是在 CSS 推出这些千呼万唤方才出台的特效规范之后。在此之前,阴影、渐变和圆角边框只能通过图片来实现。

扁平化设计并不是说完全不使用这些特效,用还是要用的,但是得用得巧用得妙。例如,前面使用的渐变是从浅蓝色过渡到中蓝色,现在也可以使用两个不同的蓝色渐变,只是渐变的幅度小到几乎察觉不出;再比如,让某个元素拥有特别小的阴影效果,小到几乎可以忽略不计。同时设置渐变和阴影的情况倒比较少见。

下面我们将用扁平化的方式重新设计一下按钮。最终的按钮效果如图 13.14 所示。它看上去已经不太像真实世界里的物件了,尽管按钮下方还是有一道淡淡的阴影。

图 13.14 具有扁平化外观的按钮效果

【图 13.14 具有扁平化外观的按钮效果】

上述效果的具体样式代码详见代码清单 13.12,其中包括了鼠标悬停与按钮激活两个状态。根据其中的代码注释同步更新本地 CSS 样式。同时还要注意,前面示例代码中的圆角边框效果已经被移除了。

代码清单 13.12 包含鼠标悬停及按钮激活状态的按钮扁平化设计

.button {
  padding: 0.8em;
  border: 0;
  font-family: Helvetica, Arial, sans-serif;
  color: white;
  background-color: oklch(57% 0.11 263deg); /* 纯色背景(不带渐变) */
  box-shadow: 0 0.2em 0.2em rgb(0 0 0 / 0.15); /* 阴影极浅的阴影效果 */
}
.button:hover {
  background-color: oklch(53% 0.13 263deg); /* 鼠标悬停状态下,颜色略微加深 */
}
.button:active {
  background-color: oklch(40% 0.13 263deg); /* 按钮激活状态下,颜色略微加深 */
}

上述代码中的盒阴影效果出现了一些变化。由于仅在垂直方向存在偏移量,这样就只有向下的阴影效果,与之前看上去很自然的阴影角度不太一样;同时又采用了 RGB 颜色表示法,令其红、黄、蓝三原色的值均为 0(即渲染为黑色),且 α 值为 0.15(几乎完全透明)。鼠标悬停与按钮激活状态下的外观也是扁平化的,只是将背景色变成了偏暗一些的蓝色。

13.2.3 创建混合风格的按钮外观

Creating buttons with a hybrid look

时至今日,扁平化设计风格依然流行,只是一直在演化。其中一种通用的设计方案是介于扁平化设计与拟物化设计之间。下面再来实现一版如图 13.15 所示的按钮效果。这次的设计融合了两种不同风格的设计元素。

图 13.15 另一种风格的扁平化按钮设计

【图 13.15 另一种风格的扁平化按钮设计】

新版设计还算简约,只是按钮底部多出一个厚厚的边框,看上去很像某个 3 D 立方体的正面。这条深色的线其实并不是通过 border 实现的,而是一个不加模糊效果的 box-shadow。这样阴影的边缘就能产生和圆角边框相同的圆弧效果。

在激活状态下,按钮向下移动了几个像素,给人一种摁下按钮时按钮会往页面里陷的错觉。试根据代码清单 13.13 同步更新样式表。

代码清单 13.13 当下流行的按钮样式

.button {
  padding: 0.8em;
  border: 0;
  border-radius: 0.5em; /* 改回了圆角边框 */
  font-family: Helvetica, Arial, sans-serif;
  color: white;
  background-color: oklch(57% 0.11 263deg);
  box-shadow: 0 0.4em oklch(40% 0.13 263deg); /* 按钮下方添加了一块阴影(不带模糊效果) */
  text-shadow: 1px 1px oklch(40% 0.13 263deg); /* 略微带了些文字阴影 */
}
.button:active {
  background-color: oklch(53% 0.13 263deg);
  transform: translateY(0.1em); /* 点击时,按钮下移 */
  box-shadow: 0 0.3em oklch(40% 0.13 263deg); /* 减少了阴影区域的大小,以抵消按钮的位移量 */
}

这里的按钮以一种截然不同的方式来处理 box-shadow 属性:没有模糊效果的叠加,也没有复制阴影,而是让阴影的边缘保持清晰。这样看起来就像一个厚厚的底部边框;但是与真正的边框相比还是有差别的,因为它的圆角弧度可以完美贴合元素的圆角边框。

此外,按钮文字也设置了阴影效果,文字阴影与盒阴影极其相似,不同之处在于前者只对渲染出的文字产生阴影效果,而非整个元素盒子。文字阴影的语法也与盒阴影殊无二致:水平偏移量(x-offset)、垂直偏移量(y-offset)、模糊半径(blur radius,可选)以及阴影的颜色。但与 box-shadow 不同的是,文字阴影 text-shadow 属性不支持 inset 关键字和扩展半径。上述示例中,我们给文字添加了一个深蓝色的阴影,且每个方向各偏移了 1px

在按钮激活状态下,我们也做了一些新的修改。先是用到了 transform 属性和 translateY() 函数,使元素在屏幕上下移了 0.1em。相关用法将在第 16 章深入解析 transform 变换时详细论述。接着我们又把盒阴影的垂直偏移量减少了同样的距离(由 0.4em 改为 0.3em)。这样,一旦按下按钮,按钮就会移动,但盒阴影固定不动。您可以点击按钮看看最终效果。

渐变和阴影可以通过各种各样的方式来组合使用。随着时间的推移,又会有新的设计理念流行起来。我们要做的,无非是今后见到某个网站上的新设计时,主动停下来花些时间琢磨琢磨,用浏览器的开发者工具检查一下,看看它究竟是如何实现的。不要舍不得动手,毕竟实践出真知。

13.3 混合模式

Blend modes

大部分情况下,无论是使用真正的图片还是设置渐变,元素一般只会使用一张背景图片。但某些情况下可能需要用到两张或者更多背景图片,CSS 也提供了相应支持。

背景图片属性(property)可以接受任意数量的属性值,每个值之间用逗号分隔即可。例如:

background-image: url(bear.jpg), linear-gradient(to bottom, #57b, #148);

使用多个背景图片时,列表中排在前面的图片会渲染到排序靠后的图片之上。在本例中,图片 bear.jpg 将遮住线性渐变。此时除非图片恰巧时半透明的,否则渐变效果是不可见的。此外还可以定义多个渐变背景,只要罗列出的第一个渐变特效包含透明或半透明的颜色,这些渐变就能相应地叠加到一起。

然而,有时可能需要对两个完全不透明的背景进行叠加,此时就可以利用 混合模式(blend mode 来加以解决。

如果熟悉图片编辑软件,那您可能见过混合模式的效果。混合模式用来控制叠放的图片怎样融合到一起。有些模式命名令人费解(enigmatic),比如滤色(screen)、颜色加深(color-burn)、强光(hard-light)等等。混合模式的典型案例如图 13.16 所示。它是由两张背景图片通过 正片叠底(multiply 的混合模式组合而成的。两个背景都使用了同一张图片,但是背景的位置不同。

图 13.16 两张背景图片以正片叠底(multiply)的混合模式组合在一起

【图 13.16 两张背景图片以正片叠底(multiply)的混合模式组合在一起】

最终呈现的效果很有意思,两张图片的副本虽然叠放在了一起,但彼此仍清晰可见。而且混合模式不会像普通的透明度设置那样,冲淡或者削弱背景整体的颜色。

下面我们来创建一个包含两个背景图片的元素,并将其混合成如图 13.16 所示的效果。新建一个页面,并添加一个类名为 blenddiv 元素。后续的几个示例还会反复用到该元素。

<div class="blend"></div>

不妨就用一个空元素来实现想要的效果。根据代码清单 13.14 提供的示例代码同步更新本地样式表,并关联到示例页面。

代码清单 13.14 混合两张背景图片

.blend {
  min-height: 70vmin;
  background-image: url(images/bear.jpg), url(images/bear.jpg); /* 两个背景图用逗号隔开 */
  background-size: cover; /* 同一属性值对两张背景图同时生效 */
  background-repeat: no-repeat; /* 同一属性值对两张背景图同时生效 */
  background-position: -30vw, 30vw; /* 给每张背景图片设置不同的初始位置 */
  background-blend-mode: multiply; /* 指定混合模式 */
}

大部分与背景相关的属性都可以接受多个值,彼此间用逗号分隔即可。例如本例中的 background-position 就用到了两个值,第一个值对第一张背景图片生效,第二个值则用于控制第二张背景图片的位置。同理,background-sizebackground-repeat 也支持多个属性值,但如果只设置一个值,则表示对所有的背景图片均有效。本例还指定了属性 min-height 的值,旨在确保元素高度不会为 0(因为是空元素)。

此外,background-size 属性还接受两个特殊的关键字:covercontain。值为 cover 时浏览器会调整背景图片的大小,使其填满整个元素,但也可能因此导致图片边缘被裁掉一部分;而值为 contain 时则可以确保整个背景图可见,尽管元素的某些区域可能不会被背景图覆盖(即 “黑边(letterboxing 1)” 效果)。该属性也可以接受长度值,用于直接设置背景图片的宽高。

尝试修改混合模式的其他属性值,例如 color-burn 或者 difference,看看它们都有那些不同的效果。这很有趣,但您可能会疑惑这些混合模式有哪些实际应用。这里列举了一些实际应用场景:

下面我们先来看看这些应用场景的实例,之后再通过一个明细列表简要介绍当前可用的所有混合模式。

13.3.1 为图片上色

Tinting an image

通过使用混合模式,我们可以把一张全彩图片着色成单一色调的图片。下面以棕熊照片为例,演示将其着色为蓝色的具体方法,最终效果如图 13.17 所示。

图 13.17 使用单一蓝色色调着色的照片

【图 13.17 使用单一蓝色色调着色的照片】

属性 background-blend-mode 不仅合并了多个背景图片,同时还合并了 background-color。所有这些叠放的图层,最后都会被混合模式拼合在一起。因此可以把背景色设置为想要的色调并将其混合到图片中。为此,请根据代码清单 13.15 同步更新本地样式表。

代码清单 13.15 将背景颜色的色调混合到背景图片上

.blend {
    min-height: 70vmin;
    background-image: url("images/bear.jpg");
    background-color: #148; /* 蓝色背景 */
    background-size: cover;
    background-repeat: no-repeat;
    background-position: center;
    background-blend-mode: luminosity; /* 使用明度混合模式 */
}

明度混合模式将前景图层(即棕熊图片)的明度(luminosity),与背景图层(即蓝色背景层)的色调与饱和度混合。换句话说,混合后的最终结果是完全使用背景色图层的颜色,但是亮度和对比度均来自棕熊图片。

这里的关键在于,明度混合模式(以及其他几种类似的混合模式)最终的渲染效果,取决于哪个图层在其他图层之上——这一点至关重要。在本例中,背景色图层位于最底层,其他图片则叠放在背景色图层之上。

再比如,假设将蓝色图层放在棕熊图片的上面而非下面(使用渐变色来代替背景颜色),最终的渲染效果就会不同。此时为了达到同样的效果,就必须改为颜色混合模式 color —— 颜色混合模式与明度混合模式恰好相反,它获取的是前景图层中的色调和饱和度、并混合背景图层的明度来生成最终的渲染效果。

13.3.2 混合模式的类型

Types of blend modes

CSS 目前支持 15 中混合模式。每种模式使用不同的数学公式来控制图片的混合效果。对于每个像素而言,就是取一个图层上的像素颜色,与其他图层上对应像素的颜色拼合计算,生成一个新的像素颜色,最终生成一张混合图片。

所有的混合模式如表 13.1 所示。这些混合模式又可以划分为五个大类:变暗(darkening)、变亮(lightening)、对比(contrasting)、复合(compositing)与比较(comparing)。

表 13.1 混合模式可分为以下五个大类

效果分类 混合模式的取值 描述
变暗 multiply 前景颜色越亮,背景色显示出来的越多
darken 选择两种颜色中较暗的那个
color-burn 加深背景色,增加对比度
变亮 screen 前景色越暗,背景色显示出来的越多
lighten 选择两种颜色中较亮的那个
color-dodge 加亮背景色,降低对比度
对比 overlay 对暗色使用 multiply、亮色使用 screen 来增加对比度,对比效果较柔和
hard-light 大幅增加对比度。与 overlay 类似,但使用强化版 multiplyscreen,对比效果明显
soft-light hard-light 类似,但使用 burn / dodge 来代替 multiply / screen
复合 hue 将顶层颜色的 色调 混合到底层颜色中
saturation 将顶层颜色的 饱和度 混合到底层颜色中
luminosity 将顶层颜色的 亮度 混合到底层颜色中
color 将顶层颜色的 色调饱和度 混合到底层颜色中
比较 difference 从亮色中减去暗色
exclusion 类似 difference,但对比度稍弱

有的模式在实际应用中可能更有用一点,需要反复试验才能选出最合适的混合模式。您可以在 https://garden.bradwoods.io/notes/css/blend-modes 以交互式的方式预览其中的大部分混合模式效果。

13.3.3 图片纹理的添加

Adding texture to an image

混合模式的另一个应用场景,是为图片添加纹理效果。比如有一张清晰的图片,但有时处于样式考虑,您像让图片与众不同。这时就可以混合第二张图片,实现手动添加胶片噪点或其他纹理效果。

仔细观察图 13.18 所示的图片。这时之前演示过的棕熊图片,但是混合一张纹理图片后,就渲染出了类似粗制帆布(rough-hewn canvas)的效果。实现该效果有如下三种混合模式可供选择:overlayhard-light 以及 soft-light。在本例中,我们不希望更改图片的色调,因此用的是一张灰度图片(grayscale image)来提供纹理,这样就保留了原始颜色。

图 13.18 混合了纹理的图片效果

【图 13.18 混合了纹理的图片效果】

实现纹理叠加效果的样式代码如代码清单 13.16 所示。纹理图片以重复平铺(tiled)的方式叠加在棕熊图片的上方。试根据下列代码同步更新样式表,并在浏览器中查看最终的渲染效果。

代码清单 13.16 使用混合模式 soft-light 为图片添加纹理

.blend {
  min-height: 70vmin;
  background-image: url("images/scratches.png"), 
    url("images/bear.jpg"); /* 让纹理图片覆盖在主图上 */
  background-size: 200px, cover; /* 每 200px 平铺一张纹理图片 */
  background-repeat: repeat, no-repeat; /* 每 200px 平铺一张纹理图片 */
  background-position: center;
  background-blend-mode: soft-light; /* 使用柔光混合模式 */
}

纹理图片(如图 13.19 所示)的背景大小设为 200px,同时启用重复背景。这样就可以让纹理图片平铺填满整个元素。同时,第二张图片的背景大小设为 cover,且不启用重复,因此该图片不会平铺。

图 13.19 灰度模式的帆布纹理图片

【图 13.19 灰度模式的帆布纹理图片】

我发现混合模式 soft-light 对于暗色系的纹理图片效果很好,而 hard-lightoverlay 模式则更适用于亮色的纹理图片(倘若纹理图片放在了主图下方,情况则恰好相反)。然而在实际应用中,效果可能千差万别,这取决于您具体的设计需求以及基础图片的明暗程度。

13.3.4 融合混合模式的用法

Mix blend modes

尽管 background-blend-mode 属性可以实现多张图片的混合,但它仅限于混合一个元素的背景色或图片。CSS 还有一个属性 mix-blend-mode 可以实现多个元素间的混合。这样一来不仅可以混合图片,还可以把元素的文本和边框与容器的背景图片混合在一起。使用融合混合模式(mix blend mode),可以将标题渲染到图片上方,但遮住的图片部分依旧可以显示出来,如图 13.20 所示。

图 13.20 混合标题及其下方图片后的最终效果图

【图 13.20 混合标题及其下方图片后的最终效果图】

融合后的最终效果很有意思,文字看上去是透明的,就像红色横幅被剪掉了一部分似的。这里利用了 hard-light 混合模式以及中灰色文字颜色。对比混合模式(contrast blend modes)通常在使用很亮或很暗的颜色时才会有更明显的效果,这里的文字采用了中灰色(#808080),背景图层显示出来后没有太大变化。

想在浏览器中看到这样的效果,需要将标题作为示例容器的子元素添加到 HTML 页面。将以下 HTML 标记同步更新到示例页:

<div class="blend">
  <h1>Ursa Major</h1>
</div>

<h1> 元素添加样式,最终渲染为一个红色的带纯色背景的横幅,且具有亮灰色的上下粗线条边框,文字颜色为灰色。接着应用融合混合模式,将整个元素视为一个图层,并与下面容器中的背景图片混合在一起。试根据代码清单 11.17 同步更新本地样式表,并查看页面效果。

代码清单 13.17 使用融合混合模式将多个元素混合到一起

.blend {
  background-image: url("images/bear.jpg");
  background-size: cover;
  background-position: center;
  padding: 50vmin 0 1em;
}

.blend > h1 {
  margin: 0;
  font-family: Helvetica, Arial, sans-serif;
  font-size: 6rem;
  text-align: center;
  mix-blend-mode: hard-light; /* 使用强光混合模式 */
  background-color: oklch(25% 0.2 25deg); /* 背景色为深红色 */
  color: #808080; /* 中灰色的文字颜色将变为透明 */
  border: 0.1em solid #ccc;
  border-width: 0.1em 0;
}

采用上述效果时,页面往往很难达到足够的对比度来让文字清晰可辨,因此在使用时需要特别当心。本例已采用大字号的粗字体来改善页面可读性。这样一来,在对比度较低的深色背景中,渲染效果也会相对更好一些。

混合模式在设计中有很多有趣的用法。将混合模式与渐变、阴影效果相结合,可以给页面带来很多有意思的视觉特效。但也请务必合理、审慎地利用好这些特效,当心过犹不及。

13.4 本章小结 Summary

  1. 译注:letterboxing 是一种屏幕宽高比渲染方式,是指如果要在宽高比为 4:3 的设备上渲染 16:9 的图像,为了保留画面完整性,图像将与设备同宽,并在上下添加黑边的显示模式。 ↩︎