SVG奇淫巧技(三):stroke的冷知识

1,431 阅读5分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第3篇文章。

对于SVG来讲,呈现方式无非只有两种,stroke(描边)fill(填充),无论是用颜色还是图案来描边或填充,都只能被这仅有的两种形式压的死死的,如果将它们全部置为none,即使<path>画的再花里胡哨也是白搭。

虽说strokefill如此重要,但也经常被大家所忽略,每次在面试时只要针对这两个属性问的稍微深一点,对方就立马哑火,今天我们先来看看stroke到底再搞什么名堂。

stroke的冷知识

如果需要实现一个梯形,要求是梯形的线宽为8、非填充并且需要充满整块viewBox的高度,试想一下应该如何实现呢?

<svg width="200" height="100" viewBox="0 0 200 100">
  <polygon points="50,0 150,0 200,100 0,100" fill="none" stroke="#cd0000" stroke-width="8">
  </polygon>
</svg>

看着代码颅内渲染了一下,貌似没什么问题,那让我们来看看浏览器中渲染的情况:

59.png

有点奇怪,为什么同样的stroke-width最终的渲染结果,上下的线宽和两个的线宽差别如此之大呢?

stroke的绘制位置

60.png

这是因为,SVGstroke是根据坐标点作为线宽的中间点来进行绘制的,如果紧贴着viewBox的尺寸来绘制边框的话,则stroke的线宽要比实际宽度减少一半,因为另一半超出了viewBox的区域。

所以,如果想要满足刚才的要求之外,还要线宽完美呈现,就需要在梯形的起始坐标为线宽留出一半的空间,例如:

61.png

<svg width="200" height="100" viewBox="0 0 200 100">
  <polygon points="50,4 150,4 194,96 6,96" fill="none" stroke="#cd0000" stroke-width="8">
  </polygon>
</svg>

这时不知道大家有没有注意到,stroke-width=8 那么一半的线宽就是4,现在viewBox的大小为200 * 100,但是后两个点194,966,96x轴的预留空间却是6,为什么呢?

这就涉及到stroke的另一个冷知识了,让我们慢慢道来。

stroke的线帽

我们先来了解一个作用于stroke的属性stroke-linecap 直译过来就是线帽 ,这个属性有3个可选值 butt(默认)roundsquare ,分别用来描述一条线的两端用什么方式来呈现:

62.png

有了大体印象之后,我们再来看看关于这3个属性在MDN上的定义:

butt: The buttvalue indicates that the stroke for each subpath does not extend beyond its two endpoints. On a zero length subpath, the path will not be rendered at all.

round: The round value indicates that at the end of each subpath the stroke will be extended by a half circle with a diameter equal to the stroke width. On a zero length subpath, the stroke consists of a full circle centered at the subpath's point.

square: The squarevalue indicates that at the end of each subpath the stroke will be extended by a rectangle with a width equal to half the width of the stroke and a height equal to the width of the stroke. On a zero length subpath, the stroke consists of a square with its width equal to the stroke width, centered at the subpath's point.

字太多看着费劲儿,总结一下:

butt:在线的末端不会进行任何绘制

round:在线的末端会以路径点为圆心画一个延长的半圆,其直径与线宽相等,即stroke-width

square: 在线的末端会扩展出一个矩形,矩形的宽度为线宽的一半,即stroke-width/2 ,矩形的高度与线宽相同即stroke-width

说白了,就是butt是个点,round是个以stroke-width为直径的square是以stroke-width为宽的正方形,只不过他们都是以线段中间点为中心,所以后两个被遮挡了一半而已。

现在一条线的尾巴如何呈现我们了解了,那么两条线交接处又是如何呈现的呢?

stroke的交汇

这就要讲到另一个属性 stroke-linejoin了, 它有5个可选值, miter(默认)roundbevelarcsmiter-clip,我们直接上图:

63.png

恕我直言,bevelround都很好理解,但是mitermiter-cliparcs 这三个值到底有啥区别我是实在没看出来。

MDN的解释也是云里雾里:

miter : 用尖角连接路径段。 通过在路径段的切线处延伸笔触的外边缘直到它们相交,来形成拐角;

round:使用圆角连接路径片段;

bevel:用斜角连接路径段;

arcs:值指示将使用圆弧拐角来连接路径线段。 通过用与连接点的外边缘具有相同曲率的圆弧在连接点处延伸笔触的外边缘来形成弧形;

miter-clip:用尖角连接路径段。 通过在路径段的切线处延伸笔触的外边缘直到它们相交,来形成拐角。如果超过了stroke-miterlimit,则斜切将被裁剪为等于stroke-miterlimit值乘以路径段相交处的笔划宽度的一半的距离。在非常尖锐的连接或动画的情况下,这提供了比 miter 更好的渲染效果。

没看懂没关系,只要记住最常用的只有3个值 miterroundbevel ,而其中miter作为默认值是最常用的,上面关于miter-clip的解释中出现了一个新属性stroke-miterlimit 而它才是解释上面提到梯形x轴为什么预留空间是6的关键。

首先,stroke-miterlimit 只对stroke-linejoinmitermiter-clip 时生效,其作用为限制两条线交汇在一起形成的尖角的斜接长度,该属性的默认值为4

好吧,斜接长度又是个啥?

64.png

其中 miterlen 就是斜接长度,widthlen 就是线宽,具体的计算公式就是上述的这样,不过还有个三角函数的公式:

65.png

根据这个公式就很容易推导出上面例子中的梯形相交斜接长度,之后就可以很精确的计算出需要预留多少空间啦。

作为一名学渣就不费那脑子计算了,各位大佬有兴趣的可以计算一下,反正我是试出来的预留值。

最后,让我们再来看下不同 stroke-miterlimit 值下的线段交汇的表现:

66.png

本来想把fill一并聊聊的,没想到stroke就说了这么多,那就放到下一篇吧。