优雅的轮廓与 SVG paint-order
特别声明:本文转载《优雅的轮廓与 SVG
paint-order
》一文,如需转载,烦请注明原文出处:https://www.w3ctech.com/topic/1628,英文出自于:《Elegant Outlines with SVG paint-order》一文。
SVG 渲染使用 painter
的模型来描述图像如何渲染到屏幕。像墙上的油漆层,上层的内容遮盖下层的内容。SVG 规范定义了哪些内容会绘制在其他内容之上。每个形状的不同部分 —— stoke
,fill
,marker
—— 每个都创建绘制层。这些形状绘制在其他层之上,层的顺序就是他们在文档中被定义的顺序。
两个新的属性被引入 SVG2 规范,他们是 z-index
和 paint-order
,允许你改变渲染规则。
大多数网站设计者对 z-index
很熟悉,它被 CSS 布局支持很多年了。不幸的是,对于SVG 的z-index
,还没有主流浏览器支持。目前,唯一解决办法是通过排列你的标记(或脚本创建的 DOM),使元素按你想他们被绘制的顺序列出。
相反,paint-order
属性在一些浏览器上已经被实现了。如果你原意让你的设计根据浏览器支持水平做出调整,你可以在最新的浏览器用此来微调控制,而其他的浏览器用简单的效果替换。如果你需要在所有浏览器上有相同的展现效果,那么你可以用像 SVG1.1 代码控制的制绘制顺序来实现。这篇文章描述了为什么 paint-order
是有用的,如何在最新的浏览器上使用,以及如何在其他浏览器上模拟。
理解 SVG 绘制属性
你的 SVG 代码中的形状元素,是使用与分辨率无关的数学公式定义的精确的几何曲线,SVG <line>
就是线的概念,即连接两个无限小的点;它自身没有厚度。SVG 的 Text
也是定义成几何轮廓,它是基于字体文件的矢量曲线。
当你在 SVG 中引入了一个没有任何样式信息的形状或文本元素时,它会显示为一个跟你定义的大小一样的黑色的实心区域, 因为fill
的默认值是:solid black
。
fill
属性告诉 SVG 渲染程序如何渲染那个几何形状。对于屏幕上的每个像素 —— 或纸上的墨点 —— 该程序决定该点是在形状的里面还是外面。如果在里面,该程序指向 fill
值并找出下一步做什么。
在简单的场景里(默认的黑色),fill
值是一个颜色,在形状里面的所有点都被替换成了该颜色。在其他情况下,fill
值是一个指令用来查找其他复杂的绘图代码。通过引用一个带有表示指令的 SVG 元素的 ID
的 URL
来指示在哪里找。
除了fill
之外,你可以通过 stroke
来绘制形状。在计算机图形里,stroke
一个形状意味着沿着它的边界画一条线。不同的程序对 srtoke
的意义有不同的解读。
在 SVG 中,stroke
实现为一个在主形状的边界上向内或向外延伸的两种形状。stroke
属性默认值是 none
,但它可以设置成一个颜色值或一个 Paint Server 来创建一个可见的 stroke
。stroke
的厚度(通过 stroke-width
属性设置)集中在形状的边缘,一半与 fill
区域重叠,另一半在边界外面。其他 stroke
相关的属性控制着形状产生的细节,例如它如何包裹转角,或者切断形状形成虚线。
如果点在内部,程序使用来自stroke
属性的绘画指令设置颜色。stroke
的区域的绘画与 fill
主轮廓的方式相同:SVG 渲染程序扫描整个区域,然后决定某个点是在 stroke
内部还是外部。
操作的顺序
当一个形状同时有 fill
和 stroke
绘制时,有些点被同时包含在 fill
区域和 stroke
区域,因此有两种不同的颜色指定。如同所有的 SVG,绘制模型采用:如果两个颜色是不透明的,在上层的颜色替换下层的颜色。
但哪一层是“上面的”?
默认情况下,stroke
绘制在 fill
的上面。这意味着你总是可以看到完整的 stroke
宽度。这也意味着如果 stroke
是半透明的,会出现双色调,fill
绘制的颜色在 stroke
区域的内半部分下面可见,外半部分不可见。
在 SVG1.1 里,将 stroke
绘制在 fill
下面的唯一方式是将其分成两个形状:一个只绘制 stroke
,另一个相同的形状复制在同一个地方(用一个 <use>
元素),fill
但不 stroke
:
<g stroke="blue" fill="red">
<g fill="none">
<path id="shape" d="..." />
</g>
<use xlink:href="#shape" stroke="none" />
</g>
上面的代码片段使用了大量继承的样式。 <path>
本身没有直接设置 fill
或 stroke
值;而是继承自他的包含块。所有的 stroke
和 fill
值都设置在包含元素<g>
上;在嵌套组和 <use>
元素上,fill
或者 stroke
属性会消失。
SVG2 引进 paint-order
属性让这种效果更容易得到。它的值是由空白隔开的关键字(fill
、 stroke
和 markers
)组成的列表,它指明了形状各部分应该按照什么顺序绘制。因此,相同的效果可以由一个元素创建:
<path id="shape" d="..." stroke="blue" fill="red" paint-order="stroke fill" />
一些绘制层不指定 paint-order
顺序,会晚一些被绘制(markers
就是这样的情况),相同顺序的会被正常绘制。这意味着交换 fill
和 stroke
的绘制顺序, 你只需要声明为 stroke
:
<path id="shape" d="..." stroke="blue" fill="red" paint-order="stroke" />
stroke
会先绘制, 然后是 fill
, 最后是其他任何 marker
。整个 fill
区域总是可见的,即使它重叠了 stroke
。
paint-order
的默认值(等效于 fill
、 stroke
、 marker
)可以显示地设置成普通的关键字。
警告: 在写作本文的时候,
paint-order
已被最新版 Firefox (从版本31开始),Blink (从 Chromium 版本35开始), WebKit (从 2014.3 开始)浏览器支持。IE 和 Edge,以及其他老版浏览器使用默认的绘制顺序。
控制绘制顺序的能力对 text
尤其重要。SVG 的 Text
可以像形状一样被 stroke
,来创建轮廓效果。但是,最细的stroke
除外,其余都会遮挡文字的细节。
为了绘制 fill
区域高出 stroke
的 —— 用一个对比颜色 —— 你可以加强文字的形状来恢复可读性。下例使用 paint-order
,用一个粗的 stroke
围绕这标题文字来创建一个清晰的轮廓。下例结果图 显示了在支持的浏览器上的结果。
stroke
没有遮挡文字更精细的细节:
<svg viewBox="0 0 400 80" width="4in" height="0.8in">
<title>Outlined text, using paint-order</title>
<rect fill="navy" height="100%" width="100%" />
<text x="50%" y="70"
text-anchor="middle"
font-size="80"
font-family="sans-serif"
fill="mediumBlue"
stroke="gold"
stroke-width="7"
paint-order="stroke"
>Outlined</text>
</svg>
将 stroke
绘制在 fill
下面来给文字添加轮廓。结果如下:
优雅降级
如果你完全凭借 paint-order
来达到这个效果,在不支持的浏览器上你的文本会变成一个杂乱的块,就像下面显示的。此时一些回退的策略是必须的。
使用默认顺序 stroke
文本:
一种解决办法是使用 CSS 的 @supports
条件规则,仅当支持 paint-order
时才用轮廓效果。另一种方法是,使用一个不同的样式,如果不是预期效果提供清晰的文本。
下面的例子是前面示例的修改版本。样式从图像的属性上移除,放在 <style>
里,这样可以使用条件 CSS 。基本的样式包含了一个较窄的 stroke
,当绘制顺序不被控制时会生效;@support
块里用粗的 stroke
替换了较窄的并且 paint-order
生效。
在支持 paint-order
(目前所有这些浏览器也支持 @support
规则) 的浏览器中,结果看起来跟上面的一样。图2展示了修改后的代码在其他浏览器看起来的样子。
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 400 80" width="4in" height="0.8in"
xml:lang="en">
<title>Using @supports to adjust paint-order effects</title>
<style type="text/css">
.outlined {
text-anchor: middle;
font-size: 80px;
font-family: sans-serif;
fill: mediumBlue;
stroke: gold;
/* fallback */
stroke-width: 3;
}
@supports (paint-order: stroke) {
.outlined {
stroke-width: 7;
paint-order: stroke;
}
}
</style>
<rect fill="navy" height="100%" width="100%" />
<text x="50%" y="70" class="outlined"
>Outlined</text>
</svg>
当不支持 paint-order
时,文本会拥有较窄的轮廓: