开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情
起因是UI设计师给的稿子上有个小锁图标,没有切图,我们自己的图标库里也没有,我就想着自己画一个。我画之前也思考了一下通常的开关锁做法,肯定就是用两个图标来回切换,我觉得要是我自己画肯定一个图标就能搞定。
反正就是想用svg画一个,就是不想用antd的图标,我正好有点功夫
先分析
一把🔐主要就是锁舌和锁身,锁舌我原本打算用一个比半圆稍大的圆弧,然后绕着圆心旋转来实现开关。 但是,圆弧的终点坐标不好确定,除了3/4圆弧。 所以,我就直接在半圆的基础上往下竖线延长,然后只要在y轴方向上下移动就能实现锁的效果。 等我做出来之后,我发现这种锁本来就是这样的啊,仿真有时候反而容易。
再动手
我很轻易的用path和rect把锁画出来了,然后发现它那个图标中心有个孔。我暂时想不到什么好办法,于是就用描边来实现空心。稍微提及一下描边的特性,描边的线宽就是真实的线宽,默认整个宽度是被没有宽度的线从中间分开的。 如下图,红框是真正落笔的轨迹,是灰白区域的线宽的中线。
<svg onclick="lock(event)" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg" style="background-color: rgb(168, 163, 163);font-size: 60px;color: #ccc;">
<!-- 半圆加一竖 开关锁就直接在y方向上位移就好了 -->
<path fill="none" class="tongue" stroke="currentcolor" stroke-linejoin="round"
stroke-width="5" d="m15 12 a 9 9 0 0 1 18 0 v10 " stroke-linecap="round" > </path>
<rect y="28" x ="14" width="20" height="15" fill="none" stroke="currentColor" stroke-width="14"></rect>
<rect y="28" x ="14" width="20" height="15" fill="none" stroke="#f00" stroke-width="0.1"></rect>
<!-- 总宽是宽加线宽 居中的话 就应该是外宽减总宽/2 + 线宽/2 -->
</svg>
虽然不太像,但是毕竟是画出来了。然后,我是onclick的方式写的,我就发现这种方式默认就是冒泡的,我写的target 而不是currentTarget,所以就是下面的效果。
对了,在码上掘金写代码出了问题,解决之后千万要把devtool关掉。
优化
不用描边, 加个锁孔,描边显然只能加方形锁孔。
拼装组合
最简单的办法就是画一个锁孔,其填充色和背景色设为一样,就是上面demo的第二把锁,但是这我背景用的是渐变色,所以不太好弄成一样。
其次就是第三把锁,虽然我不能直接在图形里面挖孔,但是我可以把这个空心图形,劈成两个实心图形,然后组合在一起。左边紫色的是我直接没改之前的代码,先画半圆再移动画笔,画直线,结果就是左边看到那样。
而右边,没有画半圆了,但是这个圆和直线的交点不好求(就是懒),所以就随便取了个点,这肯定就导致圆心不在之前位置上了,结果就那样了。
终极解决办法就是mask。
mask
问了大佬怎么用svg画空心图形,如果不用描边。大佬给了个demo,说用mask,我又先去mdn上看了下mask的用法,结果跟没看一样。研究了一会儿demo,终于明白了。
mask和clip很像 ,所有不在mask区域内的图形都不会显示,也就是被裁剪掉了。
有一点要注意的是声明的遮罩元素一定得写在SVG标签内部。
首先mask并不是反向裁剪,只是现在遇到了问题,很容易潜意识里希望有反向裁剪这样一种东西。
mask是对目标区域做了透明度的处理,如果完全透明,那不就等于是把目标区域剪掉了。 先看一个简单的demo,画个圆,再来个方形遮罩
<svg
viewBox="0 0 50 50" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<mask id="square" maskUnits=""
x="" y="" width="" height="">
<rect x="20" y="10" width="10" height="10" />
</mask>
<circle cx="25" cy="25" r="25" fill="#ffff00" mask="url(#square)" />
</svg>
如果按照这个写,你会发现,啥也看不见了。 因为你还没给遮罩颜色,没给颜色,颜色默认是000,000代表着完全透明。
来我们改个颜色试试,比如999
fff
现在,知道了吧,遮罩的效果就是会改变遮罩区域内的透明度,透明度由它的颜色决定,从000-fff ,不透明度从0-1。 不在遮罩内的图形会被裁剪。
那么如何实现反向裁剪?
那就是,先来一个大的遮罩把目标图形全部遮住,颜色设为fff,这样就没有不在遮罩内的区域了。
然后再来一个小的遮罩,这个遮罩的形状就是你希望裁剪掉那块。
来给刚才的方形遮罩前面加一个大的,看效果。一个孔方兄是不是就出来了。
<mask id="square" maskUnits=""
x="" y="" width="" height="">
<rect width="50" height="50" fill="#fff" />
<rect x="20" y="10" width="10" height="10" />
</mask>
第四把锁就这样画出来的。
更新 绘制空心图形的另一种方法
前面就说了我是因为要绘制空心图形才研究了一下mask。 因为在书上看到了这么一句话nonzero 是当你从头到尾沿着一个方向画交叉的边线时企图得到“更多的内部空间”, 并且只有你在图形内部沿着相反的方向绘画来撤销它们时才会返回图形外部。
我主要是被后半句引发了思考, 试了一下,果然可行 。
只要内部图形的绘制方向和外部图形相反,内部图形就不会被填充
而且这个是和fillrule无关的,我暂时还没搞明白fillrule。交叉部分确实遵循了保证每一条线都隔开了内部和外部,但是把下面的evenodd更换成nonzero看上去没有区别。
这个内部就是被填充的区域,外部就是不用填充的区域。
<svg
viewBox="0 0 50 50" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="m25 0 a 20 20 0 1 1 0 50
a 20 20 0 1 1 0 -50
m0 8 a 12 12 0 0 0 0 24
a 12 12 0 0 0 0 -24
m 0 20 l-5 10 h10 l-5 -10
"
a 5 5 0 0 1 10 0
fill="#00ffff" fill-rulle="evenodd" stroke="#409eff" stroke-width="0.5" />
</svg>
更新 fill-rule
填充规则决定了如何填充图形,也就是哪些要填,哪些不填。 换言之,填充规则就是判断点是否在图形(填充区域)内的规则。
默认是nonzero,另一种是evenodd 。 那本书上的写法确实难懂,但是mdn上的解释就很好懂了。
evenodd
先看奇偶数法,因为这个简单。 判定方法就是把需要判断的点P 和无穷远(也就是肯定在图形外)处一点O相连,然后判定OP和图形路径的交点个数的奇偶, 奇数就是在图形内,偶数就是不在。
很容易理解,我们假设P点在图形内,那么p点要去到O点,它必然要走出图形区域。 如果是个简单的凸多边形,显然只要1次, 如果是复杂的图形,就是2n + 1次 。这个2n就是穿过图形,你在图形外要穿过图形,至少两次,一进一出, n次进进出出就是2n 。
nonzero
nonzore同样来这么一条线OP, 判断和绘图路径的交点,不过它的规则是计分制,如果交点处路径是顺时针绘制,则+1分,逆时针则 -1分,看最终结果是不是零分,如果是零分就认为是在图形外,否则在图形内。
这个规则为什么这么设置,我也还没理解。但是很明显如果满足nonzero肯定也满足evenodd ,反之 ,不一定成立。
大家可以自己动手画一画,就用上面的图形,用两种规则,刚好都满足图形外。
结语
本文讲述了如何使用SVG的mask绘制空心图形。 其实标题也可以改成《还不会给几何图形挖孔?很简单,用mask一招搞定》。
想必css的mask属性也是这样的,大家可以试一试。
理解了fillrule的规则后,也可以利用规则绘制空心图形。