蒙版、形状与剪切

本章概要

  • 利用滤镜(filters)来控制元素及其背景的外观
  • 蒙版图片在遮盖元素局部区域中的用法
  • 使用剪切路径(clip paths)重塑元素
  • 设置元素左右浮动的方法
  • 让文字沿图形边缘对齐

上一章介绍了一些有助于增强页面视觉趣味性、激发创意灵感的实用技巧。本章将继续探讨这个话题,首先利用 CSS 提供的多种内置函数(例如模糊设置、颜色去饱和等)来演示几个视觉效果的实现过程;然后介绍蒙版(masks)和剪切路径(clip paths)的用法,并利用它们能够选择性隐藏元素局部区域的强大功能,创建几个有趣的形状;最后是浮动和属性 shape-outside 相关的知识,了解它们在文字环绕中的具体应用,看看文字是怎样紧密围绕某个形状来排列的。

鉴于这些页面特效仅适用于某些特定场景,若要让它们在一个页面内同时生效可能会有些凌乱,因此本章将通过彼此独立的小型案例来逐一演示说明。

14.1 滤镜

Filters

CSS 中的 filter 滤镜属性可以实现元素的模糊设置、色彩偏移或者去饱和等页面特效,其中好几种特效都是现成的;但为了演示 filter 的工作原理,本节先从元素的模糊设置开始介绍。

通过属性 filter 设置的滤镜将对整个元素生效,包括元素内的文字、边框及背景样式等。图 14.1 为某内容板块(类似第 12 章中演示的版本)在设置模糊滤镜前后的效果对比图。其中左侧未设置滤镜,而右侧声明了 filter: blur(3px),即对该板块设置了 3px 的模糊滤镜。

Figure 14.1 A filter applies to an entire element, including its border, children elements, and background.
图 14.1 滤镜将对包括其边框、子元素以及背景样式在内的整个元素生效

尽管图 14.1 形象地展示了滤镜的效果,但由于它让文字难以阅读,显然并不能算作滤镜的一个特别理想的应用场景。为此,我将为您演示滤镜在图片中的实际应用。

先新建一个 HTML 文档与样式表,用于存放本章将要介绍的各种示例代码。然后将代码清单 14.1 中的 HTML 标记添加到示例页面。与前面的章节一样,关联的小鸟图片可从示例代码库中获取,详见:https://github.com/cssindepth/css-in-depth-2。

代码清单 14.1 带小鸟图片的页面 HTML 标记

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <img src="images/bird.jpg"
      alt="Blue and orange bird perched on a branch"
      width="568" height="379">
  </body>
</html>

应用模糊滤镜后,图片效果将如图 14.2 所示。

【图 14.2 模糊滤镜生效后的图片效果】

函数 blur() 接受一个参数,即用于描述模糊程度的一个长度值。它表示屏幕上会有多少个像素混合到一起(除像素单位 px 外,em、rem 及其他长度单位也是有效的)。若长度值为 0px,则表示没有模糊;只有当参数值大于 0 时才会看到模糊特效,且值越大,模糊效果越显著。

请根据代码清单 14.2 所示的 filter 声明同步更新本地样式表,为图片添加 5px 的模糊特效。

img {
  filter: blur(5px);
}

利用浏览器开发者工具 DevTools 对 5px 的像素值做上下调整,看看模糊设置的强弱对图片效果的影响究竟如何。当参数值较小时,图片内容尚且可以读懂;但参数值越高,图片就越模糊,并逐渐变为一团模糊的色斑(a vague smudge of color)。这对于只需给出图片的大致印象而不必过分关注细节的场合也许会比较有用,稍后我会举例说明。在此之前,先来看看 CSS 都提供了哪些现成的滤镜功能。

14.1.1 滤镜的类型 Types of filters
目前可用的 CSS 滤镜功能共有 10 种,它们中的大多数都是以某种方式操纵颜色:要么通过调整对比度或色彩饱和度,要么通过添加阴影或变更元素的不透明度。

所有现成的滤镜功能描述如表 14.1 所示。与 blur() 类似,表中的滤镜函数都会通过传入的参数调整其特定行为,其中大部分的值均为百分数;当然也支持百分比形式(如 50%)或者小数形式(0.5)。表中所有的百分比参数其实都是可选项,如果不设置该参数,则默认为 100%。建议对照下表在您的示例页上试验每一个滤镜函数,看看都有哪些具体的特效。

表 14.1 滤镜函数用法一览表

函数示例 描述 参数说明
blur(10px) 应用高斯模糊特效 长度值越大,模糊效果越显著。
brightness(150%) 增加或减少亮度 参数值低于 100% 降低亮度;高于 100% 则增加亮度。
contrast(150%) 增加或减少对比度 参数值低于 100% 则降低对比度(使图像变淡);高于 100% 则增加对比度。
drop-shadow(10px 10px 15px black) 新增一个阴影特效,用法与 drop-shadow 属性类似 前两个参数分别表示 x 和 y 偏移量;第三个为模糊程度(可选);第四个为颜色值(可选)。与 drop-shadow 属性不同,该函数不支持扩展半径值和关键字 inset
grayscale(50%) 降低色彩饱和度 参数值介于 0% 到 100% 之间,可生成一个全灰度图像。
hue-rotate(30deg) 偏移每个像素的色调值 参数可以是色轮上代表颜色偏移量的任意角度值。
invert(100%) 用于反转颜色 参数值为 100% 时颜色完全反转;介于 0% 到 100% 之间则以指定强度设置反转效果。
opacity(50%) 设置元素透明效果,用法与 opacity 属性类似 参数值为 0% 时元素完全透明;值为 100% 时则完全不透明。
saturate(150%) 增加或减少色彩饱和度 参数值高于 100% 则增加图片色彩饱和度;低于 100% 则降低饱和度。saturate(25%) 等效于 grayscale(75%)
sepia(100%) 将当前色彩替换为深褐色色调效果 参数值介于 0%100% 之间,用于调节深褐色色调的强度。
此外,还可以同时设置多个滤镜,滤镜函数间用空格进行分隔。例如:filter: blur(5px) sepia(20%),此时各滤镜将按从左至右的顺序依次生效。

当需要针对性地设置一些细节特效时,filter 属性尤为适用。比如说,当鼠标悬停在某个图片上时,想要对图库中的其他所有图片设置一个略带模糊的、或者适当降低色彩饱和度的页面特效,则可以通过代码清单 14.3 给出的样式代码,在 :hover 状态下添加相关滤镜来实现。至于该样式对应的 HTML 页面,如果您愿意尝试的话,我就把它留作练手项目交给您来完成了。

代码清单 14.3:对图库中的图片设置些许滤镜效果

.gallery:hover img {
  filter: blur(2px) grayscale(50%); /* 鼠标悬停到图库上时,给其中的图片添加滤镜特效 */
}

.gallery img:hover {
  filter: none; /* 移除当前悬停图片上的滤镜效果 */
}

.gallery img {
  transition: filter 1s; /* 在 1 秒内逐步开启或关闭滤镜特效 */
}

根据上述代码,只要鼠标悬停到图库上方,滤镜特效就会对图库中的所有图片生效,但当前悬停的那张图片除外。这些滤镜只是出于演示目的才这样设定,在实际的生产应用环境下,可能会做进一步微调:模糊特效可能仅为 1px、灰度滤镜的值也会更低,不一而足。

提示

上述特效还有另一个比较巧妙的实现方案:利用 :has() 选择器在鼠标悬停到某张图片时,选中该图库内不在悬停状态下的所有图片,比如写作:.gallery z:has(img:hover) img:not(:hover)。但是 Firefox 浏览器直到 2023 年底才添加对 :has() 特性的支持,在没有考虑浏览器最新的相关兼容性的前提下,我可能不会采用这种写法。

本例中还用到了一个 transition 属性,以实现滤镜特效的延时开启或关闭,否则特效的切换会在一瞬间触发。因为我发现如果不设置过渡,突如其来的样式变化会令我分心,无法专注于当前图片——这正是我竭力避免的情况。过渡相关的知识我们还没学,下一章会重点介绍。

14.1.2 背景滤镜

Backdrop filter
偶尔也需要在某些内容后设置滤镜,而不是直接将其作用于内容本身。要实现这样的效果,可以使用另一个属性:backdrop-filter。该属性的值可以是前面介绍过的所有滤镜函数。

当需要将文字放到某个背景图片前方时,背景滤镜(backdrop filter) 会非常有用。选用恰当的滤镜可以在增强文字可读性的同时,又不致于完全遮挡住后面的图片内容。背景滤镜的一个典型应用如图 14.3 所示。

【图 14.3 背景滤镜可用于遮挡半透明元素后面的内容】

在本例中,背景图片位于某元素内。该元素又包含一个添加了模糊特效 blur() 的背景滤镜、且背景色为半透明白色的 div 元素。与前面讲的 filter 属性不同的是,本例中的模糊特效并没有对文字内容生效,即便 div 容器带有边框,该特效也不会对容器边框生效;相反,它只会影响到容器背后的内容,让图片可以透过 div 元素渲染出来。

该页面的 HTML 标记如代码清单 14.4 所示。请根据提供的示例代码同步更新本地示例页面。

代码清单 14.4:背景滤镜效果的示例页 HTML 标记

<div class="box">
  <div class="box__content">
    <h1>Common Kingfisher</h1>
    <p>
      The Common Kingfisher is a bird native found in Europe, Asia, and
      north Africa. It has bright blue plumage with an orange belly.
    </p>
  </div>
</div>

样式方面,背景图片将设置在 box 元素上,而背景滤镜样式 backdrop-filter 则在容器 box__content 上声明。此外还需要一些内边距、外边距来排列元素。具体样式代码详见代码清单 14.5。请将它们同步更新到本地样式表中。

代码清单 14.5 背景滤镜示例样式代码

.box {
  padding: 30px;
  background-image: url(images/bird.jpg);
  background-size: cover;
  background-position: center 30%;
}
.box__content {
  max-inline-size: 500px;
  margin-inline: auto;
  padding: 50px 30px 70px;
  border-radius: 10px;
  background-color: oklch(100% none none / 0.3); /* 即半透明的白色 */
  -webkit-backdrop-filter: blur(10px); /* 供应商前缀(用于 Safari 浏览器) */
  backdrop-filter: blur(10px); /* 启用背景滤镜的模糊特效 */
}

注意,上述代码声明了两次背景滤镜:第一次添加了 -webkit 浏览器前缀(vendor prefix),接着不带该前缀又声明了一次。鉴于 Safari 浏览器只支持 backdrop-filter 带前缀的写法,这里也只能包含两种写法。

关于浏览器前缀 Vendor prefixes

浏览器前缀是浏览器过去在实现新的或实验性的 CSS 特性时采用的一种技术。现在浏览器已经不再通过该技术来添加新的功能特性了,取而代之的是启用 Web 实验性平台特性标记(即 Experimental Web Platform Features flags,具体操作详见第 1 章内容)。该标记可以有效防止开发者在开发过程中依赖一些或将引入重大变更的、尚不稳定的新特性。

浏览器前缀现在很少使用了。不过一些浏览器尚未提供个别样式属性非前缀版本的支持,比如本章涉及的这几个属性:backdrop-filter 和某些 mask-* 开头的属性。它们在 Safari 或 Chrome 浏览器中解析时都需要添加 -webkit 前缀。您可能还会遇到其他类似的浏览器前缀,例如 -moz(用于 Firefox 浏览器)、-o(用于旧版的 Opera 浏览器)以及 -ms(用于 Internet Explorer 以及旧版的 Edge 浏览器)等。

如果确实需要引入浏览器前缀的写法,可以考虑通过一些自动化工具来实现,例如 Autoprefixer,可从 https://autoprefixer.github.io/ 获取。此外,CSS 预处理器 Lightning CSS(详见:https://lightningcss.dev/)也提供了类似功能。我现在都不怎么用这些工具了,毕竟当前需要添加浏览器前缀屈指可数。但如果必须考虑前缀、却又不想死记硬背这些属性对应的特定浏览器前缀,使用这些工具就会大有帮助。

背景滤镜在小型界面元素的开发中可能会非常有用,例如通知类消息组件或者应用级控制按钮等。

以上示例用的都是静态定位(static positioning),此时父元素的背景内容可以透过滤镜渲染出来。同样的背景特效也可以应用在设置了固定定位或绝对定位、彼此间相互堆叠的页面元素场景中。

14.2 蒙版

Masks

页面上的每个可见元素通常都是按一个矩形来渲染的。从页面设计的角度来看,有时会感觉束手束脚。圆角边框虽然可以让元素的四个角变成圆弧,甚至可以通过声明 border-radius: 50% 得到一个椭圆(oval),但也仅此而已。

所幸,CSS 提供了一些技术手段来调整元素的外形,而 蒙版(masking 技术便是其中之一。利用蒙版技术,即应用某个蒙版,人们可以将某个图像作为参照物,并让浏览器有选择地隐藏或显示元素的某些部分。

图 14.4 为一个典型的带蒙版效果的元素示例。该元素的背景图片是之前演示过的翠鸟图片,同时该元素还设置了一个星形蒙版;蒙版图片本身的效果如图 14.5 所示。

图 14.4 一个设置了蒙版效果的示例页面元素

【图 14.4 一个设置了蒙版效果的示例页面元素】

图 14.5 星形蒙版本身的图片效果

【图 14.5 星形蒙版本身的图片效果】

蒙版图片通常都非常简单,如本例所示。其中星形是完全不透明的,而星形周围的区域则是完全透明的。浏览器其实是将蒙版与页面元素叠加,并且只允许元素中与蒙版可见区域重叠的部分渲染出来。至于蒙版是什么颜色其实并不重要,但用黑色来表示蒙版也是有好处的,这样就能让蒙版的形状一目了然。

警告

出于安全考虑,浏览器往往会将蒙版图片的加载限制到同一域(same domain)内,除非请求头专门设置了 CORS 跨域。此外,蒙版图片也不支持在 file:// 协议下加载。更多关于 CORS 跨域的介绍,详见 MDN 在线文档:https://mng.bz/JZaV

想要在示例页面中实现一个带蒙版效果的元素,可以用一个简单的 div 元素开始:

<div class="mask"></div>

接着,将代码清单 14.6 中的示例样式添加到本地样式表中。该代码为元素指定了一些尺寸大小,添加了背景图片,还对蒙版进行了定义。

mask-image 属性(以及带浏览器前缀的 -webkit-mask-image 备用版本)从概念上看与 background-image 属性类似;同理,mask-size 也与 background-size 类似,并且也支持关键字 covercontain

代码清单 14.6 蒙版图片的应用

.mask {
  width: 300px;
  min-height: 300px;
  background-image: url(images/bird.jpg);
  background-size: cover;
  background-position: 100%;
  -webkit-mask-image: url(images/star-mask.png); /* 带浏览器前缀的回退方案(适用于 Chrome) */
  -webkit-mask-size: 100% 100%; /* 带浏览器前缀的回退方案(适用于 Chrome) */
  mask-image: url(images/star-mask.png); /* 指定蒙版图片的 URL */
  mask-size: 100% 100%; /* 调整蒙版图片的尺寸大小 */
}

想要预览上述样式代码的页面效果,直接用浏览器打开 HTML 文件是行不通的;因为浏览器的安全限制,蒙版无法在 file:// 协议下加载,只能通过某个 HTTP 服务器环境来访问。

为此,Node.js 的第三方工具包 http-server 提供了一种简单快捷的解决方案,可以基于您当前的文件目录来搭建本地服务器环境,进而实现静态资源的访问。如果本地还没有安装 Node.js,那么就现装一个(具体操作说明,详见:https://nodejs.org)。

Node 安装完毕后,进入示例 HTML 及 CSS 文件所在目录,并运行命令 npx http-server .,然后访问 http://localhost:8080/index.html(根据您的具体情况调整文件路径),应该就能看到上述示例效果了。如果更熟悉其他 HTTP 服务器,也可以切换到对应的 HTTP 服务器来操作。

在定义蒙版样式时,请务必确保所有的蒙版属性都有带 -webkit 前缀的样式回退方案。因为 Chrome 以及其他基于 Blink 引擎的浏览器(如 EdgeOpera 浏览器)直到前不久都还只支持带前缀的蒙版属性写法;而对不用写前缀的蒙版特性的支持,也不过是从 2023 年底才开始的。

14.2.1 带渐变效果的蒙版特效

Masking with a gradient

mask-image 属性也可以像 background-image 那样通过渐变来定义蒙版特效。由此生成的渐变效果将被视为蒙版图片,因此蒙版中任何透明的部分都将遮住元素相应位置的内容。(与 url() 中设置的路径不同,此时示例页若在 file:// 协议下打开,蒙版样式仍然有效。)

图 14.6 将渐变作为蒙版特效的示例效果

【图 14.6 将渐变作为蒙版特效的示例效果】

图 14.6 用的还是之前的页面,只是把渐变用作了蒙版图片。该渐变是从顶部的透明色渐变到底部的黑色。

上述蒙版对应的 CSS 代码如代码清单 14.7 所示。试根据代码中的最后两个声明同步更新本地样式表。注意,之前的 mask-size 属性在本例中就用不上了,因为渐变会自动填充页面元素。

代码清单 14.7 将渐变作为蒙版特效的示例样式代码

.mask {
  width: 300px;
  min-height: 300px;
  background-image: url(images/bird.jpg);
  background-size: cover;
  background-position: 100%;
  -webkit-mask-image: linear-gradient(to bottom, transparent, black);
  mask-image: linear-gradient(to bottom, transparent, black);
}

您还可以尝试不同的渐变样式来实现各种蒙版特效,例如通过声明 radial-gradient(black 40%, transparent 70%) 来实现一个 渐晕效果(vignette effect 1;或者利用重复渐变来实现条纹特效,例如 repeating-linear-gradient(45deg, transparent 0px 30px, black 30px 60px)

使用蒙版可能会遇到困难,因为从本质上讲它们是不可见的。如果设置蒙版后,页面元素完全可见或者完全不可见,此时要找出蒙版存在的问题就会比较棘手。如果蒙版效果达不到预期,有时我会临时将 mask-image 改为 background-image(而属性 mask-size 则改为 background-size),因为它们的语法都是一样的;之后我便能清楚地看到蒙版是如何在元素上生效的,以便做进一步更正。

14.2.2 基于亮度来定义蒙版

Masking using luminance

默认情况下,蒙版图片是通过其 alpha 通道生效的;蒙版透明及半透明的部分会对目标元素的不透明度带来直接影响。您也可以基于亮度(luminance)来设置蒙版——利用黑色与白色来调节蒙版效果,而非蒙版的透明情况。该特性尤其适用于没有现成可用的透明蒙版图片的应用场景中。

要让蒙版切换到基于亮度来设置,需要用到 mask-mode 属性。注意,该属性直到 2023 年年底才刚刚添加到 Chrome 浏览器,目前上不支持带 -webkit 前缀的写法。若要启用该特性并作为页面核心功能,请务必提前查看该特性最新的浏览器支持情况(详见:https://mng.bz/wxVP)。

mask-mode 的初始值为 alpha。改为 luminance 后,蒙版特效的行为模式随即改变,此时蒙版图片的黑色部分会对应透明的像素点,而白色部分则对应不透明的像素点。白色与黑色之间的阴影效果则对应半透明的蒙版特效。

代码清单 14.8 给出了该模式下的一个示例 CSS 版本。在不使用元素的不透明度的情况下,通过渐变中的亮度来定义的蒙版特效,其最终效果与之前代码清单中的演示效果完全相同。

代码清单 14.8 基于图片亮度来定义蒙版的示例样式代码

.mask {
  width: 300px;
  min-height: 300px;
  background-image: url(images/bird.jpg);
  background-size: cover;
  background-position: 100%;
  -webkit-mask-image: linear-gradient(to bottom, black, white); /* 用灰度阴影来定义蒙版 */
  mask-image: linear-gradient(to bottom, black, white); /* 用灰度阴影来定义蒙版 */
  mask-mode: luminance; /* 启用亮度模式 */
}

注意,在前面的示例(即代码清单 14.7 对应版本)中,黑色是作为不透明色来描述蒙版图片的可见部分的,至于具体是什么颜色并不重要,只要完全不透明即可;而本例中黑色所代表的含义与之前明显不同了。启用亮度模式后,黑色表示透明区域,而白色反倒表示可见区域了。

14.2.3 其他蒙版属性

Other mask properties

CSS 提供了多种蒙版属性来调节蒙版特效的具体行为、尺寸及生效位置。其中很多属性在概念上都与对应的背景属性 background-* 类似:

Chrome 浏览器于 2023 年 12 月发布了一次大型更新来支持 CSS 的蒙版特效。在此之前,像 mask-clipmask-modemask-composite 这样的属性并未获得完全支持,而对于其他蒙版属性还必须加注 -webkit 前缀方能使用。

  1. vignette effect,即渐晕效果,是一种图像处理效果,通常用于摄影和图像编辑中。其特点是图像的中心部分较亮,而四周的边缘逐渐变暗或模糊。

14.3 剪切路径

Clipping paths

剪切路径(clipping path) 是另一种有选择地隐藏元素局部区域的方法。剪切路径在概念上与蒙版类似,但它不是用图片来遮挡元素,而是通过数学方法来定义形状,即通过 clip-path 属性进行设置。

本节先为您演示剪切路径的一个典型案例,然后在逐步过渡到更复杂的知识点。本节第一个示例的最终效果如图 14.7 所示。其中,剪切形状被定义为一个圆。页面元素所有不在该形状内的部分就会被隐藏。此时,圆的尺寸大于图片高度,从而令元素可见部分的上下边缘呈直线、左右两边则呈曲线效果。

图 14.7 矩形示例图片在设置剪切路径为圆形后的最终效果
图 14.7 矩形示例图片在设置剪切路径为圆形后的最终效果

要实现上述效果,我们先根据代码清单 14.9 给出的 HTML 标记,给示例页面添加一个图片元素。操作时,既可以替换到前面演示的 HTML 元素,也可以将新的图片元素追加到页面末尾。

代码清单 14.9 演示剪切路径特效对应的图片 HTML 标记

<img
  class="clipped" src="images/eagle.jpg"
  alt="Golden Eagle"
  width="796" height="529"
/>

接着利用类名 clipped 选中该元素,并通过 clip-path 属性设置一个剪切路径;设置该属性最简单的一种方式就是使用 circle() 函数。该函数需要一个表示圆半径的长度值或百分数作参数。请根据代码清单 14.10 同步更新本地样式表。

代码清单 14.10 设置圆形剪切路径的示例样式代码

.clipped {
  clip-path: circle(398px); /* 剪切出一个半径为 398px 的圆 */
}

由于图片宽度设为了 796px,上述代码中的半径 398px 刚好是它的一半。此外还可以使用一个特殊的关键字实现相同的效果:circle(farthest-side)。同理,指定 circle(farthest-side) 将调整圆的大小并覆盖元素的上下边缘,只因它们是距离圆心最近的两条边。

注意
将属性 clip-path 的值设为除了初始值 none 以外的任意合法值后,页面会同步创建一个新的堆叠上下文(详见第 6 章相关内容)。

在本例中,剪切出的圆位于元素正中;但您也可以使用 at 关键字以及 xy 坐标对剪切出的形状重新定位。这样,圆心就会移动到新的坐标位置。请根据下列示例代码更新样式表并查看页面效果:

.clipped {
  clip-path: circle(229px at 337px 293px);
}

样式更新后,剪切出的圆就跑到了鹰头的正中位置,同时圆的大小也进行了调整,使得图片大部分区域都不可见,仅渲染出了鹰的面部与喙。这些 xy 坐标的值都是从元素边框盒(border box)的左上角开始测量的。圆的尺寸和方位可以设置为百分数。

此外,还可以使用 ellipse() 函数来定义一个椭圆形的剪切路径。使用方法与 circle() 函数类似,区别在与椭圆函数需要两个尺寸参数,即垂直方向与水平方向上的半径。根据椭圆剪切出的图片效果如图 14.8 所示。

图 14.8 剪切路径为椭圆时的图片最终效果
图 14.8 剪切路径为椭圆时的图片最终效果

上述效果对应的 CSS 样式如代码清单 14.11 所示。请将其同步更新到本地样式表。

代码清单 14.11:剪切路径为椭圆时的示例样式代码

.clipped {
  clip-path: ellipse(300px 170px at 360px 290px); /* 定义一个水平半径 300px、垂直半径 170px 的椭圆 */
}

值得注意的是,与蒙版特效一样,剪切路径的设置并不会改变目标元素的实际大小;特效只是隐藏了元素的某些区域。此时元素仍然占据着页面原始大小,浏览器只是将裁剪遮挡的部分变为了空白。

在绝大多数实际项目开发中,往往需要让剪切图形的尺寸大小贴近目标元素的完整宽高。因此在定义剪辑形状时,通常建议先在图片编辑工具中将图片裁剪到合适的尺寸,然后再设置剪切路径,以便按人们期望的方式来调整形状。

14.3.1 多边形的裁剪路径

Polygon clipping paths

利用多边形函数 polygon() 还可以定义出更复杂的剪切形状。该函数的参数为任意数量的 xy 坐标组,各组之间用逗号分隔;每一组坐标都对应一个多边形的顶点位置。例如,polygon(50% 0%, 100% 100%, 0% 100%) 就定义了一个三角形,对应的三个顶点坐标分别为顶部正中的(50% 0%)、右下角的(100% 100%)以及左下角的(0% 100%)。

有了这个函数,我们就能根据想要的效果定义出具有任意顶点的多边形。代码清单 14.12 给出了剪切多边形的另一个示例。试根据以下样式代表同步更新本地样式表。

代码清单 14.12 剪切图形为多边形的示例样式代码

.clipped {
  clip-path: polygon(380px 50px, 650px 210px, 520px 500px, 20px 360px); /* 定义包含四个顶点的多边形 */
}

上述代码的页面效果如图 14.9 所示。该裁剪路径形似一个风筝,并且是横着放置的风筝:

图 14.9 多边形的图片剪切效果
图 14.9 多边形的图片剪切效果

如本例所示,利用多边形剪切,我们既可以围绕图片的关键部分重塑图片的可见区域,也可以在指定部位附近雕刻出有趣的边框效果。为此,知名前端大牛 Temani Afif 还专门写了一篇文章演示了不少生动案例,深度探讨了渐变蒙版和剪切路径在构建趣味边框特效中的应用,详见:https://mng.bz/7d0v

提示

推荐一个非常实用剪切路径在线制作工具:CSS clip-path maker。该工具预设了大量美观实用的剪切图形效果供人们选用。

14.3.2 Firefox 内置的剪切路径工具

Firefox clip-path tools

编写剪切路径的代码,尤其是多边形的剪切路径往往会十分繁琐,需要不断调整坐标值才能达到预期效果。为此,Firefox 浏览器的开发者工具 DevTools 提供了一个我认为对与构建或微调剪切路径特效非常有价值的可视化编辑工具。略为遗憾的是,目前其他主流浏览器暂未内置该工具。

Firefox 中,右键单击页面上设有剪切路径的图片元素并选择 “检查(Inspect)” 即可打开 DevTools 工具。找到目标元素的 clip-path 属性,会发现其函数旁边有一个多边形图标(如图 14.10 所示)。只要是通过内置函数 circle()ellipse()polygon() 以及 inset()(具体用法稍后介绍)定义的任意剪切路径,都会出现该图标。

图 14.10 点击多边形图标来编辑剪切路径
图 14.10 点击多边形图标来编辑剪切路径

点击该多边形图标,将启用主浏览器窗口中的 clip-path 交互式编辑模式。剪切路径的轮廓用蓝色细线勾勒,每一个可以编辑的顶点都会出现一个小圆圈,如图 14.11 所示。点击并拖动其中某个圆圈就能实时编辑图形,并在 DevTools 的 “规则” 面板(Rules pane)中看到当前的变更情况。

图 14.11 Firefox 浏览器提供的 clip-path 交互式编辑模式效果
图 14.11 Firefox 浏览器提供的 clip-path 交互式编辑模式效果

编辑多边形时,每个顶点处都设计了一个控制手柄(control handle);双击多边形的某条边可以新增一个控制点。编辑圆形时,我们会看到两个控制手柄:一个用于移动圆心位置,另一个则在圆的右侧用于调整半径大小。椭圆的操作与圆类似,只是在底部会多出一个控制点,用于控制椭圆垂直方向上的半径大小。

14.3.3 其他剪切路径类型

Other clip-path types

CSS 还提供了几种其他类型的剪切路径。这些特效的试验工作就交给各位了:

以上剪切路径设置中,最为通用的反倒是 path() 函数,因为该函数在定义复杂图形与曲线方面具备了极为强大的灵活性,远超 polygon() 函数的能力范围。然而,path() 函数的灵活应用离不开矢量编辑软件的相关操作经验。关于 SVG 路径语法的快速入门介绍,可以参考 Chris Coyier 发表的这篇文章:The SVG path Syntax: An Illustrated Guide

14.4 浮动和形状

Floats and shapes

使用蒙版和剪切路径来重塑元素外观看着很有意思,但从页面布局的角度看,该元素仍然是一个普通的矩形框。想让基础布局拥有更复杂的形状,就需要用到 shape-outside 属性。这就会让文本这样的行内内容(inline content)以另外一种方式环绕在该元素周围,不同于以往沿着元素的直角边进行排列。

shape-outside 属性非常适合与遵循相同轮廓的蒙版、剪切路径或圆角半径相结合来实现一些页面特效。此时,页面元素看似有了不同的形状,文字也紧密环绕在相同的形状周围。图 14.12 就给出了这样一个典型案例:猎鹰图片被剪切成圆形,一旁的文字也围绕在由 shape-outside 属性定义的同心圆周围。

图 14.12 定义形状会让行内内容围绕该形状的轮廓进行排列
图 14.12 定义形状会让行内内容围绕该形状的轮廓进行排列

要使用 shape-outside 属性,元素就必须设为 浮动(floated 元素。设为浮动元素后,该元素将向页面左右两侧的某一侧靠拢,并让行内内容围绕其外边距盒(margin box)排布。鉴于浮动本身就是个复杂的话题,本节将先行阐述它的基本概念与工作原理,然后再介绍与形状(shapes)相关的知识。

14.4.1 浮动

Floating

浮动设置会将一个元素(通常为图片)拉到所在容器的左侧或右侧,让文档流环绕在该元素周围。某浮动元素在一段文字内的渲染效果如图 14.13 所示。由于这样的布局在报纸和杂志排版中非常常见,CSS 也为此添加了浮动这一特效。

图 14.13 多行文本环绕在浮动元素周围的效果示意图
图 14.13 多行文本环绕在浮动元素周围的效果示意图

浮动元素会从常规文档流中移除,并被带往所在容器的边缘位置。随后,文档流重新排布,但行内内容会围绕浮动元素当前占据的空间进行排列。浮动元素并没有从文档流中的初始位置向上或向下移动;换句话说,其最终位置取决于该元素在 HTML 文本中的初始方位。

通过声明 float: left 或者 float: right 可以将一个元素浮动到指定的方向。如果将多个元素浮动到同一个方向,这些元素就会并排排列,如图 14.14 所示。

图 14.14 并排排列的两个浮动元素效果图
图 14.14 并排排列的两个浮动元素效果图

下面我们将在示例页中添加一些内容,实现图片浮动到一旁的效果。然后再设置一些剪切特效,让文字排列呈现出某个形状。请根据代码清单 14.13 同步更新本地 HTML 示例页面。您既可以删除 <body> 元素中的内容,也可以将示例代码添加到现有内容的下面。

代码清单 14.13 带浮动图片的示例页 HTML 标记

<div>
  <div class="poem-image"><!-- 待浮动的元素 -->
    <img src="images/eagle.jpg" alt="Golden Eagle">
  </div>
  <h1>Hope is the thing with feathers</h1>
  <p><cite>Emily Dickinson</cite></p>
  <p>
    “Hope” is the thing with feathers<br>
    That perches in the soul,<br>
    And sings the tune without the words,<br>
    And never stops at all,
  </p>
  <p>
    And sweetest in the Gale is heard;<br>
    And sore must be the storm<br>
    That could abash the little bird<br>
    That kept so many warm.
  </p>
  <p>
    I’ve heard it in the chillest land,<br>
    And on the strangest sea;<br>
    Yet, never, in extremity,<br>
    It asked a crumb of me.
  </p>
</div>

提示
在本例中,<img> 元素被放在了一个 div 容器中。这并非浮动样式的专属写法。但我发现在某些情况下,在 “普通”(“normal”) 元素上处理形状比在图片元素上更简单。从专业技术的角度看,图片元素在 HTML 中是一个 可替换元素(replaced element;在某些情况下,该元素的尺寸大小及方位可能与普通元素的行为模式略有不同。具体来说,在 Chrome 浏览器中使用 object-position 属性会出现一个 Bug,可能会对环绕在该元素附近的文本位置产生不良影响。

接着,我们将设置一些 CSS 样式来适当调整图片的尺寸,并将其浮动到页面左侧。完成设置后的页面最终效果将如图 14.15 所示。

图 14.15 将图片浮动到文字左侧的页面效果图
图 14.15 将图片浮动到文字左侧的页面效果图

请将代码清单 14.14 中的 CSS 样式添加到本地样式表,并在浏览器中查看渲染效果。我们在图片上声明了 display: block,这样行高就不会给父级 div 元素增加额外的高度 —— 尽管在重置样式(reset styles)中我也常常这样声明(详见第 8 章)。

代码清单 14.14 将图片元素浮动到页面左侧的示例样式代码

.poem-image {
  float: left;
  margin-right: 15px;
}

.poem-image > img {
  display: block;
  height: 350px;
  width: 350px;
  object-fit: cover;
}

在 HTML 中,浮动元素在其余行内内容中的位置,对于确定哪些文本行将会环绕该元素而言至关重要。图片上方的文字仍旧在图片上方不变,但与图片同行或者在该行下方渲染的文字将会环绕该浮动元素排列。在本例中,我们希望整首诗歌都围绕在图片的右侧,因此需要将图片放到 HTML 文档的最前面。

如果对浮动的基本特性不太熟悉,建议您挪一挪该浮动元素来切身感受它的特点。比如将其放到段落中间,看看渲染出的最终位置。如果浮动元素是一个行内元素(inline element),还可以将其放到某个句子中。

浮动元素的另一个有趣、偶尔也会让人猝不及防的行为特征,是它们不会给所在的父元素贡献任何高度。如果一个浮动元素是某个 <div> 元素的唯一子元素,则该 div 的高度将渲染为 0。这么做是为了让您可以在某个段落(<p>)内浮动一张图片时,让后续段落的内容也能继续围绕该图片排列,如图 14.16 所示。

图 14.16 某容器内的浮动样式会扩展到下一个容器,让两个容器内的文字均围绕该浮动元素排列
图 14.16 某容器内的浮动样式会扩展到下一个容器,让两个容器内的文字均围绕该浮动元素排列

浮动的这一行为特征,不仅在两个容器均为段落元素时是有意义的,且当它们是两个 div 元素、或者其他任意块级元素(block-level element)时也同样适用。若偏要让另一个元素出现在浮动元素的下方,则需要使用 clear: both;当然除了 both 外,也可以使用 leftright 来清除指定方向上的元素浮动。

此外,还可以在容器的 ::after 伪元素上设置 clear 属性来让容器强行包含其浮动的子元素。这样一来容器元素的高度便可以扩展到浮动子元素的底部:

.contain-floats::after {
  display: block;
  content: "";
  clear: both;
}

在 CSS 引入 Flexbox 布局或网格布局之前,浮动元素(floats)是构建页面复杂布局的唯一选择。由于这并非浮动样式的设计初衷,因此在实现页面布局的过程中往往存在一定的困难,实现手法也不太优雅;但随着全新布局技术的出现,这样的局面已然成为了历史。人们可以仅仅出于浮动设计的本意来使用浮动,即:当确实需要让文字环绕在某元素周围时,再考虑启用浮动。

14.4.2 形状的定义

Defining a shape

让示例页中的图片浮动起来后,就可以利用 shape-outside 属性为一旁的文字定义形状了。该属性的合法值与 clip-path 属性大同小异,都支持:margin-boxborder-boxpadding-boxcontent-boxinset()circle()ellipse() 以及 polygon();最显著的区别在于处理 SVG 路径时的 path() 函数。尽管这个值已经纳入 CSS 规范,但目前在任何浏览器中 shape-outside 均不支持属性值为 path()。没准今后就可以用了。

这就意味着,人们通常可以给元素的剪切路径和形状指定完全相同的属性值。一个值得推荐的做法是:将这个值赋给某个自定义属性,然后在设置这两个属性时直接引用这个值。该做法尤其适用于需要定义某个复杂多边形的应用场景中。请根据代码清单 14.15 同步更新本地样式表,按照上述思路将这个属性值设为圆形。

代码清单 14.15 剪切路径和形状皆为圆形时的示例样式代码

.poem-image {
  float: left;
  margin-right: 15px;
  --shape: circle(50%);
  clip-path: var(--shape);
  shape-outside: var(--shape);
}

.poem-image > img {
  display: block;
  height: 350px;
  width: 350px;
  object-fit: cover;
}

这样一来,猎鹰图片按圆形剪切,并且旁边的文字会整机环绕在它周围,最终效果如图 14.17 所示(与前面展示的图 14.12 相同)。

图 14.17 文字同样按圆形环绕排列的效果图

【图 14.17 文字同样按圆形环绕排列的效果图】

您也可以将自定义属性 --shape 改为其他值,例如 polygon()ellipse() 或者其他有效取值,然后观察文字对应的环绕情况。如果想让文字紧密环绕浮动元素的圆角半径 border-radius 时,也可以考虑以下属性值:margin-boxborder-boxpadding-box 以及 content-box

另外,鉴于圆形也可以通过设置圆角半径为 50% 来实现,因此也可以不通过剪切路径、而改用圆角半径来达到同样的效果,如代码清单 14.16 所示。注意,此时需要同时在图片元素(为了剪切图片)和图片所在的 div 容器上(为了影响 shape-outside 属性)设置圆角半径。

代码清单 14.16 围绕边框圆角定义的形状样式代码

.poem-image {
  float: left;
  margin-right: 15px;
  shape-outside: margin-box; /* 围绕圆角边框轮廓定义的形状 */
  border-radius: 50%;
}

.poem-image > img {
  display: block;
  height: 350px;
  width: 350px;
  object-fit: cover;
  border-radius: 50%;
}

在本例中,我们用了一个右外边距来将图片和环绕文字隔开一小段距离。也可以使用 shape-margin 属性,例如声明 shape-margin: 15px 将在 shape-outside 指定的轮廓周围再增加 15px 的外边距。

但是设置的 shape-margin 外边距并不会延伸到浮动元素的外边距之外。此时,剪切路径只能到达元素的边缘而非外边距边缘,这并非我们想要的效果。尽管上下两端的文字依然会环绕圆形轮廓排列,但中间的文本环绕的却是由元素外边距盒(margin box)构成的直角边。并且在使用 shape-margin 前,最好能确保环绕形状的边缘与元素边缘留有足够的空间才敢放心使用。关于这一点稍后我会用一个案例来演示说明。

14.4.2.1 让形状贴合渐变蒙版

Matching a shape to a gradient mask

shape-outside 属性也可以像蒙版那样对某个图片元素生效。这对渐变特效来说尤为适用,因为这样一来就能将元素沿某个斜边进行剪切并让文字环绕斜边排列,如图 14.18 所示。

图 14.18 利用线性渐变定义的对角线形状
图 14.18 利用线性渐变定义的对角线形状

在本例中,我们在图片蒙版以及环绕形状上同时设置了相同的线性渐变特效,倾斜角均为 65 度。试根据代码清单 14.17 提供的渐变样式代码同步修改本地样式表。注意,示例代码中并未限制图片的宽度,这样可以给斜边提供更多空间。

代码清单 14.17 利用渐变特效定义的环绕形状的样式代码

.poem-image {

  float: left;
  --gradient: linear-gradient( /* 对角线型渐变样式 */
    65deg,
    black 0 375px,
    transparent 375px);
  mask: var(--gradient);
  shape-outside: var(--gradient);
  shape-margin: 15px;
}

.poem-image > img {
  display: block;
  height: 350px;
  object-position: -100px 0; /* 将猎鹰调整到画面中心 */
}

将颜色节点设置在 375px 的位置,可以实现从黑色到透明的突变效果。该节点的位置坐标、连同 65 度的倾斜角,都是在浏览器的开发者工具中微调得到的。接着我又设置了 object-position 属性,将猎鹰重新调至画面正中位置,否则会被右侧的环绕形状切掉一部分。建议您在浏览器中多改改这些数值,看看渐变效果会怎样变化。

由于浮动元素向右延伸并超出了环绕形状的边缘,此时改用 shape-margin 也是没问题的。此外,也可以使用径向渐变,例如:将自定义属性 --gradient 改为 radial-gradient(circle at 175px, black 175px, transparent 175px) 将得到一个圆形的剪切路径特效,几乎与代码清单 14.15 中演示的 circle(50%) 的效果完全相同。

在本例的线性渐变特效中,我们定义了一个从黑色突变到透明色的颜色节点,但这并不是什么固定操作。如果定义的是一个由黑色平滑过渡到透明的渐变特效,则环绕形状的边缘将由不透明度变为 0% 时的位置来界定。

您也可以通过 shape-image-threshold 属性来动手调整渐变效果对应的边缘。例如,指定 shape-image-threshold: 0.3 会让文字紧密环绕在图片不透明度渐变到 30% 时所形成的形状周围。您也可以利用该方法实现文字与图片的重叠渲染,让文字环绕在图片仍然依稀可见的位置,效果如图 14.19 所示。

图 14.19 设置容器的 shape-image-threshold 属性可以定义形状边缘对应的不透明度
图 14.19 设置容器的 shape-image-threshold 属性可以定义形状边缘对应的不透明度

本章演示的页面特效能够让您的页面从网上大量的常见样式设计中脱颖而出。这些功能特性往往更接近人们在报刊杂志或者其他类型的印刷设计中见到的样式效果。随着 Flexbox 弹性布局和 Grid 网格布局的出现,利用浮动元素来实现页面布局的做法几乎销声匿迹了,但浮动样式仍然有它的用武之地,尤其是当您想实现某些独具风格且引入注目的页面特效时,情况更是如此。

14.5 本章小结