CSS 形状使用入门

将内容封装在自定义路径周围

Razvan Caliman
Razvan Caliman

长期以来,网页设计人员一直被迫在矩形的约束范围内进行创作。网络上的大多数内容仍被困在简单的方框中,因为大多数创意采用非矩形布局最终会令人沮丧。随着 CSS 形状(从 Chrome 37 开始提供)的引入,这种情况即将发生变化。借助 CSS 形状,网页设计人员可以将内容封装在自定义路径(例如圆形、椭圆形和多边形)周围,从而摆脱矩形的约束条件。

形状可以手动定义,也可以根据图像推断得出。

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

也许您和我一样,像我一样天真地浮现了带有透明部分的图片,期望内容能够包围并填补间隙,结果只是因为元素周围的矩形包围形状而令您感到失望。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 值在另一侧有一个浮动区域。例如,如果某个元素带有咖啡杯图片,让系统向左悬浮,则会在杯子的右侧创建浮动区域。尽管您可以设计出两侧有间隙的图像,但内容只会环绕在浮动属性指定的对面的形状上,即左侧或右侧,而绝不能两者并用。

将来,可以使用 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()”形状 + 裁剪路径的图示

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

回到圆形。

使用百分比形式的圆半径时,实际会使用稍微复杂一些的公式计算该值: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`。它们的名称暗示了它们的边界。我们之前对“边距框”进行了介绍。“边框框”受元素边框的外边缘的约束,“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;
}

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

对于 circle() 形状函数中的半径,也可以使用相同的 farthest-sideclosest-side 关键字。

Polygon() 函数

Polygon() 形状值的图示

如果圆形和椭圆形的限制过多,那么多边形形状函数将打开一系列选项。格式为 polygon(x1 y1, x2 y2, ...),其中您可以为多边形的每个顶点(点)指定 x y 坐标对。指定多边形所需的最少对数为三个(三角形)。

.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 Trithall 非常详细地介绍了填充规则属性在 SVG 中的运作方式。如果未指定,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 从简单的框中释放网络内容,这可能听起来不合常理。情况很有可能。我还没有发现 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;
}

当然,您也可以通过这种方式使用所有参考框。这是派生形状的另一个用途 - 偏移拉引号。

使用内容框参考框创建偏移量拉取引用

只需使用浮点数和外边距属性即可实现偏移引用效果。不过,这需要您将引号元素放置在 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 形状规范的初始草案中包含 shape-inside 属性,您可以使用该属性将内容封装在形状内。而且在 Chrome 和 Webkit 中还曾有过一段时间的实现。但是,将任意位置的内容封装在自定义路径中需要执行更多的精力和研究,以涵盖所有可能的场景并避免 bug。因此,shape-inside 属性已被推迟为 CSS 形状级别 2,并且其实现已被撤销。

不过,您只要付出一些努力并做出一些妥协,便能实现将内容封装在自定义形状中的效果。技巧就是使用 shape-outside 的两个浮动元素,它们位于容器两侧。折衷方法是,您需要使用一两个没有语义含义的空元素,但它们充当支撑杆,在内部制造形状的错觉。

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

  Lorem ipsum...
</div>

.left-shape.right-shape Strut 元素在容器顶部的位置非常重要,因为它们会悬浮左右两侧,以便边缘内容。

.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 解决方案。但是,更改滚动时的顶部外边距确实会触发大量重新布局和绘制事件,而且可能会显著降低性能。使用时要多加注意!但是,使用 CSS 形状而不为其添加动画效果并不会对性能造成明显影响。

渐进增强

首先假设浏览器不支持 CSS 形状,并在检测到 CSS 形状时基于此进行构建。Modernizr 是执行功能检测的理想解决方案,并且 “Non-core detected”部分提供了针对 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 Shapes 值所需的语法。就算这样,这样工作也不会太实际。

CSS 形状是为了让浏览器对页面上的其他元素做出反应。直观了解修改形状对其周围内容的影响非常有用。有一些工具可以帮助您完成此工作流程:

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

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

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

图片形状:如果你更喜欢生成图片并让浏览器从中提取形状,Rebecca Hauck 编写了一个很好的 Photoshop 教程

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

总结

在内容主要被困在简单框中的网络环境中,CSS 形状提供了一种创建富有表现力的布局的方法,弥合了网页设计与平面设计之间的保真度差距。当然,形状可能会被滥用并造成干扰。但是,如果在运用品味和善意判断的情况下使用形状,则可以增强内容呈现效果,并以独特的方式吸引用户的注意力。

我为你留下了其他人的一系列作品,其中大部分都是印刷作品,这些作品展示了非直排布局的有趣用途。希望以上内容对您试用 CSS 形状并尝试新的设计理念有所启发。

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