CSS 形状使用入门

将内容封装在自定义路径中

Razvan Caliman
Razvan Caliman

长久以来,Web 设计师一直被迫在矩形的约束下进行创作。由于大多数尝试采用非矩形布局的广告素材最终都以失败告终,因此网络上的大多数内容仍局限于简单的框架。随着 CSS Shapes 的引入,这种情况即将改变。CSS Shapes 从 Chrome 37 开始提供。 借助 CSS 形状,Web 设计师可以将内容换行到自定义路径(例如圆形、椭圆形和多边形)中,从而摆脱矩形的限制。

形状可以手动定义,也可以从图片中推断出来。

我们来看一个非常简单的示例。

也许您和我一样,在首次浮动包含透明部分的图片时,希望内容能够换行并填充空白,但最终却发现元素周围仍然存在矩形换行形状,这让您感到失望。CSS 形状可用于解决此问题。

从图片中提取形状
<img class="element" src="image.png" />
<p>Lorem ipsum…</p>

<style>
.element{
  shape-outside: url(image.png);
  shape-image-threshold: 0.5;
  float: left;
}
</style>

shape-outside: url(image.png) CSS 声明会告知浏览器从图片中提取形状。

shape-image-threshold 属性定义将用于创建形状的像素的最低不透明度。其值必须介于 0.0(完全透明)和 1.0(完全不透明)之间。因此,shape-image-threshold: 0.5 表示仅使用不透明度为 50% 或更高的像素来创建形状。

float 属性是关键。虽然 shape-outside 属性定义了内容将围绕的区域的形状,但如果没有浮点,您将看不到形状的效果。

元素的 float 值的另一侧有一个浮动区域。例如,如果带有咖啡杯图片的元素悬浮左侧,则系统会在咖啡杯右侧创建浮动区域。尽管您可以设计两边都有间隙的图片,但内容只会在 float 属性指定的另一侧(即左侧或右侧)的形状周围环绕,而不是围绕着形状。

将来,可以通过引入的 CSS 排除对象对非浮动元素使用 shape-outside

手动创建形状

除了从图片中提取形状之外,您还可以手动对它们进行编码。您可以从以下几个函数值中进行选择来创建形状:circle()ellipse()inset()polygon()。每个形状函数都接受一组坐标,并与用于建立坐标系的参考框搭配使用。稍后详细了解参考框。

circle() 函数

circle() 形状值的插图

圆形形状值的完整表示法为 circle(r at cx cy),其中 r 是圆形的半径,而 cxcy 是圆形中心在 X 轴和 Y 轴上的坐标。圆心坐标是可选的。如果您省略这些值,系统会将元素的中心(其对角线的交点)用作默认值。

.element{
  shape-outside: circle(50%);
  width: 300px;
  height: 300px;
  float: left;
}

在上面的示例中,内容将环绕圆形路径的外侧。单个参数 50% 用于指定圆形的半径,在本例中,该半径等于元素宽度或高度的一半。更改元素的尺寸会影响圆形的半径。下面是一个 CSS 形状如何响应的基本示例。

在继续之前,先说一下:请务必注意,CSS 形状仅影响元素周围浮动区域的形状。如果元素具有背景,则该背景不会被形状剪裁。要达到这种效果,您必须使用 CSS 遮罩中的属性,即 clip-pathmask-imageclip-path 属性非常实用,因为它遵循与 CSS 形状相同的表示法,因此您可以重复使用值。

`circle()` 形状 + clip-path 的插图

本文档中的插图使用剪裁来突出显示形状,以帮助您了解相关效果。

返回圆形。

使用百分比作为圆形半径时,系统实际上会使用稍微复杂一些的公式 sqrt(width^2 + height^2) / sqrt(2) 来计算该值。理解这一点会很有帮助,因为它有助于您想象当元素的尺寸不相等时生成的圆形会是什么样子。

所有 CSS 单位类型都可以在形状函数坐标中使用,例如 px、em、rem、vw、vh 等。您可以根据自己的需求选择灵活或严格的方案。

您可以通过为圆形中心的坐标设置显式值来调整圆形的位置。

.element{
  shape-outside: circle(50% at 0 0);
}

这会将圆心置于坐标系的原点。什么是坐标系?这就是我们引入参考框的地方。

CSS 形状的参考框

参考框是元素周围的虚拟框,可以建立用于绘制和定位形状的坐标系。坐标系的原点位于左上角,X 轴指向右侧,Y 轴指向下方。

CSS 形状的坐标系

请注意,shape-outside 会更改内容将围绕的浮动区域的形状。浮动区域会延伸到由 margin 属性定义的框的边缘。这称为 margin-box,如果未明确提及任何参考框,则它是形状的默认参考框。

以下两个 CSS 声明的结果完全相同:

.element{
  shape-outside: circle(50% at 0 0);
  /* identical to: */
  shape-outside: circle(50% at 0 0) margin-box;
}

我们尚未为该元素设置边距。此时,可以放心地假设坐标系的原点和圆心位于元素内容区域的左上角。设置边距后,情况会有所不同:

.element{
  shape-outside: circle(50% at 0 0) margin-box;
  margin: 100px;
}

现在,坐标系的原点位于元素的内容区域(向上 100 像素,向左 100 像素)之外,圆形中心也是如此。计算出的圆形半径值也会随之增大,以反映 margin-box 参照框建立的坐标系的表面增大。

带有和不带边距的边距盒坐标系
您可以选择以下几个参考框选项:`margin-box`、`border-box`、`padding-box` 和 `content-box`。这些名称暗示了它们的边界。我们之前介绍过 `margin-box`。`bound-box` 受元素边框外边缘的约束,`padding-box` 受元素的内边距约束,而 `content-box` 与元素内内容所使用的实际表面积相同。
所有参考框的插图

在给定时间内,只能使用一个包含 shape-outside 声明的引用框。每个参考框都会以不同的方式(有时是细微的方式)影响形状。我们还提供了另一篇文章,可帮助您更深入地了解 CSS 形状的参考框

ellipse() 函数

ellipse() 形状值的图示

椭圆形看起来像被压扁的圆形。它们定义为 ellipse(rx ry at cx cy),其中 rxry 是椭圆在 X 轴和 Y 轴上的半径,而 cxcy 是椭圆中心的坐标。

.element{
  shape-outside: ellipse(150px 300px at 50% 50%);
  width: 300px;
  height: 600px;
}

百分比值将根据坐标系的尺寸计算得出。无需进行复杂的计算。您可以省略椭圆形中心的坐标,系统会根据坐标系的中心推断出这些坐标。

X 轴和 Y 轴上的半径也可以使用关键字进行定义:farthest-side 会产生一个半径,该半径等于椭圆形中心与最远处的参考框边之间的距离,而 closest-side 则表示相反的情况,即使用中心与边之间的最短距离。

.element{
  shape-outside: ellipse(closest-side farthest-side at 50% 50%);
  /* identical to: */
  shape-outside: ellipse(150px 300px at 50% 50%);
  width: 300px;
  height: 600px;
}

当元素的尺寸(或参考框)可能会以不可预知的方式发生变化,但您希望椭圆形能够相应调整时,这可能会很有用。

farthest-sideclosest-side 关键字同样适用于 circle() 形状函数中的半径。

polygon() 函数

polygon() 形状值示意图

如果圆形和椭圆形限制太多,多边形形状函数可以为您提供无限的选择。格式为 polygon(x1 y1, x2 y2, ...),您可以在其中为多边形的每个顶点(点)指定成对的 x y 坐标。用于指定多边形的最少对数为 3(三角形)。

.element{
  shape-outside: polygon(0 0, 0 300px, 300px 600px);
  width: 300px;
  height: 600px;
}

顶点放置在坐标系上。对于响应式多边形,您可以为部分或全部坐标使用百分比值。

.element{
  /* polygon responsive to font-size*/
  shape-outside: polygon(0 0, 0 100%, 100% 100%);
  width: 20em;
  height: 40em;
}

有一个可选的 fill-rule 参数从 SVG 导入,该参数会指示浏览器在遇到自相交路径或封闭形状时如何考虑多边形的“内侧”。Joni Trythall 非常详细地介绍了 SVG 中的 fill-rule 属性的运作方式。如果未定义,则 fill-rule 默认为 nonzero

.element{
  shape-outside: polygon(0 0, 0 100%, 100% 100%);
  /* identical to: */
  shape-outside: polygon(nonzero, 0 0, 0 100%, 100% 100%);
}

inset() 函数

借助 inset() 形状函数,您可以创建矩形形状,并在其周围换行内容。考虑到 CSS Shapes 的初始前提是让 Web 内容摆脱简单的框架,这可能听起来不合常理。很有可能。我还没有找到 inset() 的用例,它无法通过浮点和边距或 polygon() 实现。不过,与 polygon() 相比,inset() 确实为矩形形状提供了更易读的表达式。

内嵌形状函数的完整表示法为 inset(top right bottom left border-radius)。前四个位置参数是相对于元素边缘的内侧偏移量。最后一个参数是矩形形状的边框半径。此字段是可选字段,因此您可以将其省略。它遵循您在 CSS 中使用的 border-radius 简写表示法。

.element{
  shape-outside: inset(100px 100px 100px 100px);
  /* yields a rectangular shape which is 100px inset on all sides */
  float: left;
}

从参考框创建形状

如果您没有为 shape-outside 属性指定形状函数,则可以允许浏览器从元素的参考框派生形状。默认的参考框是 margin-box。目前还没有什么奇特的地方,浮点数已经实现。不过,通过应用此技术,您可以重复使用元素的几何图形。我们来看看 border-radius 属性。

如果您使用它来使浮动元素的角变圆,则会获得剪裁效果,但浮动区域仍会保持矩形。添加了 shape-outside: border-box,以封装 border-radius 创建的轮廓。

使用边框参考框从元素的边框半径中提取形状
.element{
  border-radius: 50%;
  shape-outside: border-box;
  float: left;
}

当然,您可以以这种方式使用所有参考框。派生形状的另一种用途是偏移的引文。

使用 content-box 引用框创建偏移引号

通过仅使用 float 和 margin 属性可以实现偏移拉引号效果。不过,这需要您在 HTML 树中将引号元素放置在您希望其呈现的位置。

以下是如何实现同样的偏移引号效果,同时提高灵活性:

.pull-quote{
  shape-outside: content-box;
  margin-top: 200px;
  float: left;
}

我们为形状的坐标系明确设置了 content-box 参考框。在这种情况下,引号内容的量决定了外部内容将围绕的形状。此处使用 margin-top 属性来定位(偏移)引号,无论其在 HTML 树中的位置如何。

形状边距

您会发现,将内容包裹在形状周围可能会使其与元素过紧地摩擦。您可以使用 shape-margin 属性在形状周围添加间距。

.element{
  shape-outside: circle(40%);
  shape-margin: 1em;
  float: left;
}

具体效果与您习惯使用常规 margin 属性的情况类似,但 shape-margin 只会影响 shape-outside 值周围的空间。仅当坐标系中有足够的空间时,才会在形状周围添加间距。因此,在上述示例中,圆形半径设置为 40%,而不是 50%。如果将半径设置为 50%,则圆形会占据坐标系中的所有空间,因而无法对 shape-margin 的效果产生任何空间。请注意,形状最终会受元素的 margin-box(元素及其周围的 margin)的约束。如果形状较大且超出边界,系统会将其剪裁到 margin-box,最终得到矩形。

请务必注意,shape-margin 仅接受一个正值。它没有长手记号。那么,圆形的上外边距指的是什么?

为形状添加动画效果

您可以将 CSS 形状与许多其他 CSS 功能(例如转换和动画)搭配使用。不过,我必须强调,如果文本布局在阅读过程中发生变化,用户会感到非常厌烦。如果您决定为形状添加动画效果,请密切关注用户体验。

您可以为 circle()ellipse() 形状的半径和中心添加动画效果,前提是它们在浏览器可插值的值中定义。可以从 circle(30%) 改为 circle(50%)。不过,在 circle(closest-side)circle(farthest-side) 之间添加动画会导致浏览器卡顿。

.element{
  shape-outside: circle(30%);
  transition: shape-outside 1s;
  float: left;
}

.element:hover{
  shape-outside: circle(50%);
}
动画圆圈的 GIF

polygon() 形状添加动画时,可以实现更有趣的效果,但请务必注意,多边形在两个动画状态之间必须具有相同数量的顶点。如果您添加或删除顶点,浏览器将无法进行插值。

一种技巧是添加所需的最大顶点数量,并将它们在您希望形状的边缘感知度较低的动画状态中聚集在一起。

.element{
  /* four vertices (looks like rectangle) */
  shape-outside: polygon(0 0, 100% 0, 100% 100%, 0 100%);
  transition: shape-outside 1s;
}

.element:hover{
  /* four vertices, but second and third overlap (looks like triangle) */
  shape-outside: polygon(0 0, 100% 50%, 100% 50%, 0 100%);
}
动画三角形的 GIF

将内容换行到形状内

使用 CSS 形状来换行内容的《爱丽丝梦游仙境》演示的屏幕截图

CSS Shapes 规范的初始草稿包含 shape-inside 属性,可让您将内容换行到形状内。在 Chrome 和 WebKit 中,甚至有一段时间也实现了该功能。但是,将任意放置的内容封装到自定义路径中需要付出更多努力和研究,以覆盖所有可能的场景并避免 bug。因此,shape-inside 属性已推迟到 CSS Shapes Level 2,并且已撤消其实现。

不过,只要您付出一些努力并做出一些妥协,仍然可以实现将内容换行到自定义形状中的效果。技巧是使用两个带 shape-outside 的悬浮元素,这两个元素位于容器的相对侧。折衷之处在于,您必须使用一个或两个没有语义意义的空元素,但它们可用作支柱,营造内部有形状的错觉。

<div>
  <div class='left-shape'></div>
  <div class='right-shape'></div>

  Lorem ipsum...
</div>

容器顶部的 .left-shape.right-shape 支撑元素的位置非常重要,因为它们会浮动到左侧和右侧,以便将内容夹在中间。

.left-shape{
  shape-outside: polygon(0 0, ...);
  float: left;
  width: 50%;
  height: 100%;
}

.right-shape{
  shape-outside: polygon(50% 0, ...);
  float: right;
  width: 50%;
  height: 100%;
}
Alice 演示版 Shape-inside 解决方法图示

这种样式会使两个浮动支柱占据元素内的所有空间,但 shape-outside 属性会为其余内容留出空间。

如果浏览器不支持 CSS 形状,则会将所有内容向下推,从而产生难看的效果。因此,以渐进增强的方式使用该功能非常重要。

在前面的形状动画示例中,您会发现文本移动很麻烦。并非所有用例都需要动画形状。不过,您可以为与 CSS 形状互动的其他属性添加动画,以便在适当的情况下添加效果。

在 CSS 形状的 Alice in Wonderland 演示中,我们使用滚动位置更改了内容的上边距。文本被挤在两个浮动元素之间。当它向下移动时,必须根据两个悬浮元素的 shape-outside 重新布局。这给人的印象是,文字在深入失败,为叙事体验锦上添花。是否接近无意义?或许可以。但它看起来很酷。

由于文本布局是由浏览器以原生方式完成的,因此其性能比使用 JavaScript 解决方案要好。但是,滚动时更改 margin-top 确实会触发大量重新布局和绘制事件,这可能会明显降低性能。请谨慎使用!不过,在不为 CSS 形状添加动画的情况下使用它们不会明显降低性能。

采用渐进增强的方式

首先假设浏览器不支持 CSS 形状,并在您检测该功能时在此基础上构建。Modernizr 是执行功能检测的理想解决方案,“非核心检测”部分中提供了 CSS 形状的测试。

某些浏览器通过 @supports 规则在 CSS 中提供功能检测,而无需外部库。Google Chrome(也支持 CSS 形状)可以识别 @supports 规则。您可以通过以下方式使用它来逐步增强:

.element{
  /* styles for all browsers */
}

@supports (shape-outside: circle(50%)){
  /* styles only for browsers which support CSS Shapes */
  .element{
    shape-outside: circle(50%);
  }
}

Lea Verou 撰写了一篇文章,详细介绍了如何使用 CSS @supports 规则

与 CSS 排除对象区分

我们今天所说的 CSS 形状在规范的早期被称为 CSS 排除项和形状。命名方式的转变可能看起来只是一个细微之处,但实际上非常重要。CSS 排除对象现已成为一项单独的规范,可让您围绕任意定位的元素换行内容,而无需使用浮动属性。假设您要将内容封装在绝对定位的元素周围;这就是 CSS 排除项的一个用例。CSS 形状仅定义内容将环绕的路径。

因此,形状和排除对象并不相同,但它们确实是相辅相成的。CSS 形状目前适用于浏览器,而 CSS 排除功能尚未针对形状交互实现。

用于处理 CSS 形状的工具

您可以在传统图片创作工具中创建路径,但在撰写本文时,这些工具都无法导出 CSS 形状值所需的语法。即使可以,这样做也不太实用。

CSS 形状旨在在浏览器中使用,在浏览器中,它们会对网页上的其他元素做出响应。要直观呈现修改形状在周围内容上的效果,这非常有用。以下工具可帮助您完成此工作流程:

括号适用于括号的 CSS Shapes Editor 扩展程序使用代码编辑器的实时预览模式叠加用于修改形状值的交互式编辑器。

Google Chrome适用于 Google Chrome 的 CSS Shapes Editor 扩展程序可为浏览器的开发者工具添加控件,以便创建和修改形状。它会在所选元素上方放置一个交互式编辑器。

Google Chrome 中的检查器内置了对突出显示形状的支持。将鼠标悬停在具有 shape-outside 属性的元素上,该元素会亮起以说明其形状。

根据图片创建形状:如果您希望生成图片并让浏览器从图片中提取形状,可以查看 Rebecca Hauck 的 Photoshop 教程,该教程非常实用。

Polyfill:Google Chrome 是首个发布 CSS Shapes 的主要浏览器。我们即将支持在 Apple 的 iOS 8 和 Safari 8 上使用此功能。其他浏览器供应商将来可能会考虑这样做。在此之前,可以使用 CSS 形状 polyfill 提供基本支持。

总结

在 Web 中,内容大多被困在简单的框中,CSS Shapes 提供了一种创建富有表现力的布局的方法,缩小了 Web 设计和印刷设计之间的保真度差距。当然,形状可能会被滥用,造成干扰。不过,如果运用得当,形状可以增强内容呈现效果,并以独特的方式吸引用户的注意力。

我会给您分享一些其他人的作品集,其中大部分来自平面媒体,展示了非长方形布局的有趣用途。希望这能激励您尝试 CSS 形状并尝试新的设计理念。

非常感谢 Pearl Chen、Alan Stearns 和 Zoltan Horvath 审核本文并提供宝贵的见解。