先来看下我们做了什么~
这张图就是基于svg的一个效果。一个完成的图由25个碎片组成。用户每次获得一块新的碎片,就高亮显示,并和已有的其他合并成一块大的图像。直到全部收集齐后,才进行下一轮的收集。
其实看我们现在的界面,很多同学会感觉这个div+css也是可以做到类似效果的。但是选用了svg,也是基于以下几点考虑:
本文倒不会一一细数svg的各种属性。这些可能大部分文档都会讲的更专业。我们更像是通过一些案例的实践过程,帮助同学们在那些琳琅满目的的svg标签和属性里,整理出一些制作svg的认知框架。我的案例里主要会涉及到 :
本文后续还会同步到LOFTER技术团队公众号其其他平台~ 欢迎大家观看。
为什么又要开始讲基础了呢,因为视图里有个很有意思的小技巧,且容我多讲一点。
我们看下是 svg 的viewBox属性
参数:viewBox="x, y, width, height"
viewBox="x, y, width, height" 中的xy,表示视图的起始位置。xy可以是负数(width, height 不可以)。如果我们把xy设置成负数,整个画布向右下方移动,就可以做到类似padding的效果,而不用去修改里面已经完成的路径了!如果你有一些外发光效果会出现在 0,0 以外,那这个将会起到一个良好的保护作用。
参数:preserveAspectRatio="xMidYMid meet"
preserveAspectRatio顺带提一下,因为 svg会自动缩放,所以可以了解下,帮助我们做一些屏幕适配。
svg 绘制图形的标签有很多,line画直线、rect画方块、circle画圆。这些查文档即可了解全貌,基本不用特别的介绍。附文档地址 https://developer.mozilla.org/zh-CN/docs/Web/SVG
本文将重点提及path的路径的描述方法。因其自由度,同时也带来了复杂度。
就这个我们重点来分析d属性,也就是如何绘制路径
先来一个简单的例子
<path
d="M0,0 h100 v100 h-100 z"
/>
划重点:每个路径是由空格或者空白符来分割的,逗号表示的是 x,y 即坐标。不要被看到的长长的path迷惑了,逐条分析即可得到结果。
那我们看下上面代码中绘制的图像的步骤是如何的:
上述例子中已经使用 M,h,v,z, 那总共有哪些路径描述呢,如下:
可以看到有大写和小写之分。大写表示绝对坐标, 小写表示相对上一个点的坐标。比如我们从x=100,y=100的点开始画长度100的横线,用小写是 h100,大写是H200。
fill-rule也是我们绘制图形时需要关心的属性,特别是有交叉的路径的时候。镂空的关键点在于如何判定内容是内部还是外部,来看下这篇详细的介绍,可以帮助你理解:https://www.zhangxinxu.com/wordpress/2018/10/nonzero-evenodd-fill-mode-rule/
梳理完基本原理后,我们就来实际操作一下。先看下效果图:
那我们的题目就是这样了:
已知后端数据为
[
[1, 1, 1, 0, 1],
[0, 0, 1, 0, 1],
[0, 0, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 1, 1, 1]
]
的二维数组。1表示获得,需要绘制为高亮。0表示未获得,需要加上白色蒙层。求如何绘制这个边界路径。
解题开始:
按照上述步骤你就可以获得一个完整的路径了,后续我们将继续讲解如何通过fill填充蒙层和图像及 filter组合滤镜完成发光效果。
绘制好路径我们就要给他填充上内容。
fill="#ffffff"
颜色的填充就比较简单直观了,不做赘述了。
蒙层就可以用颜色加透明度来实现填充。
pattern可以让你把图片作为填充物,也可以把一些svg内容作为填充物,非常好用。高亮部分就可以使用这个。也可以直接透明,用这个是为了刚好的实现后续的内外发光滤镜。
<pattern id="puzzleImage" x="0" y="0" patternUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse" width="495" height="1237">
<image x="0" y="0" width="495" height="1237" xlink:href="https://lofter.lf127.net/1645514196505/puzzle.jpg?imageView&crop=0_0_495_1237#pattern" />
</pattern>
写法上也很方便,pattern里包裹image即可,其中patternUnits属性需要关注下 patternUnits 可选值为 userSpaceOnUse 和 objectBoundingBox。userSpaceOnUse表示用户空间也就是最外层svg上的viewBox。objectBoundingBox是相对与当前元素的。
我们这里是通过叠加多层图像来实现效果,所以选用 userSpaceOnUse,保证了填充的图片能完全一致。
<pattern id="puzzleImage" x="0" y="0" patternUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse" width="495" height="1237">
<image x="0" y="0" width="495" height="1237" xlink:href="https://lofter.lf127.net/1645514196505/puzzle.jpg?imageView&crop=0_0_495_1237#pattern" />
<line x1="99" y1="0" x2="99" y2="1237" stroke-width="1" stroke="#fff" />
<line x1="198" y1="0" x2="198" y2="1237" stroke-width="1" stroke="#fff" />
<line x1="297" y1="0" x2="297" y2="1237" stroke-width="1" stroke="#fff" />
</pattern>
pattern除了图片还能添加其他svg标签,比如上图的,我们白色的分割线也放到了pattern里,这样填充的图片就自动带上了白色的分割线。我们高亮部分就是通过这种方式填充内容的。
我来看下一个高亮效果,一个内外发光的svg滤镜效果。
其实现代码代码如下:
<filter id="puzzleNormalShadow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceAlpha" stdDeviation="13" />
<feMorphology operator="dilate" radius="6" result="offsetblur" />
<feFlood flood-color="#ffe4b1" flood-opacity="0.75" />
<feComposite result="outblur" in2="offsetblur" operator="in" />
<feFlood flood-color="#ffe4b1" flood-opacity="0.75" />
<feComposite in2="SourceGraphic" operator="out" />
<feGaussianBlur stdDeviation="13" />
<feComposite result="inblur" in2="SourceGraphic" operator="atop" />
<feMerge>
<feMergeNode in="outblur" />
<feMergeNode in="inblur" />
</feMerge>
</filter>
这是个比较典型的svg的多个滤镜组合后生成的效果,那它们又是怎么组合生效的呢?我们可以来看一下下方的gif,结合我们的分析来理解一下。现在我们逐条分析每一个滤镜的作用来加深理解:
<feGaussianBlur in="SourceAlpha" stdDeviation="13" />
feGaussianBlur的滤镜作用是模糊,in="SourceAlpha"表示作用在透明区域上,stdDeviation="13"表示模糊的程度<feMorphology operator="dilate" radius="6" result="offsetblur" />
feMorphology是用来扩大或者缩小内容的。operator="dilate"表示扩大。result="offsetblur" result表示记录产物为offsetblur 可以用在后面指定的地方。<feFlood flood-color="#ffe4b1" flood-opacity="0.75" />
feFlood 表示填充颜色<feComposite result="outblur" in2="offsetblur" operator="in" />
feComposite 来合并图像,有in和in22个属性。其中,in缺省则自动使用上一步的产物,in2="offsetblur"即使用之前第二步的产物。<feMerge>
把外发光内发光合并在一起,此时内外发光是可以用不同的颜色和效果的。虽然内外发光在我们看来是一个很常见的效果,但是真的去实现又需要多条滤镜相互结合才能完成。反复思考这个例子,相信可以帮助同学们理解滤镜是如何组合的,后面自己制作的时候也能梳理出思路,而不是只能搜索现成的。
feColorMatrix 是 svg的一个滤镜,主要作用是 通过一个计算方法,图像上的每个颜色转化成另一个颜色。比如红色 转 黄色,或者让红色变得更红,绿色更绿,都是可以做到的。可以看到feColorMatrix可能是我们最常用的图片处理滤镜了。
那我们先来看一下,一个 feColorMatrix 有哪些些属性:
<feColorMatrix
in="SourceGraphic"
type="matrix"
id="feColorMatrix"
values="0.2 0.2 0.2 0 0.3
0.2 0.2 0.2 0 0.3
0.17 0.17 0.17 0 0.49
0 0 0 1 0"
/>
其中最重要的是 values 也就是我们的转换矩阵。
计算公式如下:
我们看下每个部分:
最后,请大家多花费几分钟,再回归下我们的公式,理解一下。理解了才能进行我们实际的操作。
效果如下图,左边是处理完后的,右边是原图。运用feColorMatrix来处理吧!拿到换个效果的时候,我们一般也会先和视觉同学沟通下,了解他们如何用PS做出这个效果的。经过沟通我们知道他们做了2步操作:
变黑白也就是图片只有黑白2色,图上只有白、灰、黑。做法其实就是吧 RGB 3个通道的颜色相加再平分。比如原来是 rgb(30, 60, 90) 转化后就变成了 rgb(60,60,60)。每个点都这么处理,图片就变黑白了。此时的feColorMatrix值应该是:
0.333 0.333 0.333 0 0
0.333 0.333 0.333 0 0
0.333 0.333 0.333 0 0
0 0 0 1 0
即通过设置每个通道的值都为R * 1/3 + G * 1/3 + B * 1/3
实现黑白的变化。
渐变映射其实就是特殊的变黑白。用一个指定颜色到白色的过渡色来,填充 每个通道上0-255的变化。
比如我们这次的值为rgba(115,115,157,1)的紫色到白色到过渡。以R通道的计算为例,我们看下计算过程:
rR(参考上面公式)
= 目标过渡变化区间/原图过渡变化区间
= 140 / 255
。140 / (255 * 3) 140 / (255 * 3) 140 / (255 * 3) 0 X
。X见下一条。140 / (255 * 3) 140 / (255 * 3) 140 / (255 * 3) 0 115/255
0.1830065359477124 0.1830065359477124 0.1830065359477124 0 0.45098039215686275
G和B 2个通道的转换矩阵,可以使用同样的方法计算得出。
最终生成的结果如下:
<feColorMatrixi
in="SourceGraphic"
type="matrix"
values="
0.1830065359477124 0.1830065359477124 0.1830065359477124 0 0.45098039215686275
0.1830065359477124 0.1830065359477124 0.1830065359477124 0 0.45098039215686275
0.12810457516339868 0.12810457516339868 0.12810457516339868 0 0.615686274509804
0 0 0 1 0"
/>
同学们也可以把这个计算过程梳理成JS函数,方便下次使用。其计算过程大致如下:
var originMax = 255;
var total = originMax * 3;
var white = 255;
var r = rInput.value;
var g = gInput.value;
var b = bInput.value;
var rp = (white - r) / total;
var gp = (white - g) / total;
var bp = (white - b) / total;
var matrix = `
${rp} ${rp} ${rp} 0 ${r / originMax}
${gp} ${gp} ${gp} 0 ${g / originMax}
${bp} ${bp} ${bp} 0 ${b / originMax}
0 0 0 1 0
`;
这个是记录了下本次开发中几个兼容性问题,同学们可以方便查看~
A: xlink:href 有更好的兼容性,推荐使用。低版本ios不支持href
A: 为 filter标签 添加下如下属性x="-50%" y="-50%" width="200%" height="200%"
扩大下滤镜的作用范围,一般200%够用了,外发光不会有这么大
A: 为feFlood标签添加上 x="0" y="0" width="100%" height="100%"
属性,明确feFlood的范围
本次开发也是作者开发使用和熟悉了解svg的一个过程。中间使用到了不少svg的特性。svg本身有一些便利性。通过预置的滤镜可以快速处理图像。大部分情况下使用svg滤镜处理图像能比canvas更快的看到结果。canvas需要先加载再处理,一般流程较长。但是svg依赖浏览器本身的优化,ios上就略有不足,还是会考虑使用上canvas的方案。但是用来做一些图片的快速处理倒是完全够用了。最后也欢迎大家留言讨论,有问题可以一起交流。