1. 前言
- 最近在弄骨架屏的需求,所以在研究业内主流的几个方案。
- 本篇文章基于react-content-loader分析骨架屏svg动画的实现。
2. 效果
3. 代码
<!-- aria-labelledby属性标识了一个(或多个)元素,该元素标记了它所应用到的元素。这里指向内部title标签的id。 -->
<!-- viewBox属性的值是一个包含4个参数的列表 min-x, min-y, width and height, 以空格或者逗号分隔开, 在用户空间中指定一个矩形区域映射到给定的元素,查看属性preserveAspectRatio。 -->
<!-- min-x 视口横轴偏移量,min-y 视口纵轴偏移量 -->
<!-- width 视口宽度,height 视口高度 -->
<svg aria-labelledby="ml3udb-aria" viewBox="0 0 380 70">
<!-- title 描述性字符串,该描述只能是纯文本,hover时做提示作用。 -->
<title id="ml3udb-aria">Loading...</title>
<!-- x、y相对定位偏移量,width、height宽高,这里rect为外层 -->
<!-- clip-path CSS 属性使用裁剪方式创建元素的可显示区域。区域内的部分显示,区域外的隐藏。 -->
<!-- 用 <url> 表示剪切元素的路径 -->
<!-- style元素元素样式表直接在SVG内容中间嵌入。 -->
<!-- 这里fill: url指向了id一致的linearGradient渐变标签的id -->
<rect
x="0"
y="0"
width="100%"
height="100%"
clip-path="url(#ml3udb-diff)"
style="fill: url('#ml3udb-animated-diff')"
></rect>
<!-- 把所有需要再次使用的引用元素定义在defs元素里面。 -->
<!-- 这里主要是针对clip-path和style的引用 -->
<defs>
<clipPath id="ml3udb-diff">
<!-- 3个小块,1正方形+2长条 -->
<rect x="0" y="0" rx="5" ry="5" width="70" height="70"></rect>
<rect x="80" y="17" rx="4" ry="4" width="300" height="20"></rect>
<rect x="80" y="50" rx="3" ry="3" width="150" height="20"></rect>
</clipPath>
<!-- linearGradient元素用来定义线性渐变,用于图形元素的填充或描边。 -->
<linearGradient id="ml3udb-animated-diff">
<!-- 一个渐变上的颜色坡度,是用stop元素定义的。 -->
<!-- offset属性用于表示该颜色位于渐变的什么位置上。这里指的是offset为整个外层元素横轴0%的地方的基点颜色 -->
<!-- stop-color属性指示在渐变停止时使用的颜色。 -->
<!-- stop-opacity属性定义给定颜色渐变停止的不透明度。 -->
<stop offset="0%" stop-color="#f5f6f7" stop-opacity="1">
<!-- animate动画元素放在形状元素的内部,用来定义一个元素的某个属性如何踩着时点改变。 -->
<!-- 重点!这里values和keytimes是重点部分,先略过,第4点分析处会单独说明-->
<!-- dur,即duration,动画时长 -->
<!-- repeatCount属性表示动画将发生的次数。 -->
<animate
attributeName="offset"
values="-2; -2; 1"
keyTimes="0; 0.25; 1"
dur="4s"
repeatCount="indefinite"
></animate>
</stop>
<!-- 这里指的是offset为整个外层元素横轴50%的地方的基点颜色 -->
<stop offset="50%" stop-color="red" stop-opacity="1">
<animate
attributeName="offset"
values="-1; -1; 2"
keyTimes="0; 0.25; 1"
dur="4s"
repeatCount="indefinite"
></animate>
</stop>
<!-- 这里指的是offset为整个外层元素横轴100%的地方(即终点)的基点颜色 -->
<stop offset="100%" stop-color="#f5f6f7" stop-opacity="1">
<animate
attributeName="offset"
values="0; 0; 3"
keyTimes="0; 0.25; 1"
dur="4s"
repeatCount="indefinite"
></animate>
</stop>
</linearGradient>
</defs>
</svg>
4. 分析
这里,我们大致看下来,这个svg动画效果,由这几部分组成:svg元素+视口盒子、title声明/提示、rect父级元素(包含了defs定义的clip-path下的3个子级元素)、linearGradient渐变元素(包含3个stop坡度元素和内部的animate动画元素)。那么它是怎么实现动画效果的呢?
这里我们从基点角度出发,从时间的维度去对比:
- values内部值的单位是100%的外层元素宽度,即父级元素宽度。后续统一称为基点偏移相对值。
- keytimes内部值的单位是整个动画时长的百分比例,满值为1。后续统一称为基点时间相对值。
- values和keytimes彼此对应,比如第一个stop基点元素,在0s(时间系数ratio * 动画总时长duration)时,value为-2,指的是相对于svg viewBox起止点(左上角x = 0, y = 0)横轴向左偏移了-200%的父级宽度。
| 基点元素 | values | keytimes |
|---|---|---|
| stop1 | -2; -2; 1 | 0; 0.25; 1 |
| stop2 | -1; -1; 2 | 0; 0.25; 1 |
| stop3 | 0; 0; 3 | 0; 0.25; 1 |
- 基点时间相对值为0的情况下,stop1到stop2的距离是|-2 - 0| = 2,基点时间相对值为0.25的情况下,stop1到stop2的距离是|-2 - 0| = 2,基点时间占比为1的情况下,stop1到stop2的距离是|1 - 3| = 2。这就说明整个linearGradient渐变元素的宽度是2个父级元素的宽度。
- 基点时间相对值从0到0.25的过程,这个linearGradient渐变元素没有动,短暂的停止了,如果动画总时长是4s,那这个暂停时长就是1s,如果动画总时长是1s,那这个暂停时间就是0.25s。
- 基点时间相对值从0.25到1的过程中,linearGradient渐变元素从父级元素最左边向右边开始移动(你也可以理解它是当前的节点自己在模拟变化,具体的本篇文章不做扩展),最终的情况是stop3作为offset为100%,距离svg父级元素的起点向右偏移了3 * 100%的距离。
我画个图来形容一下这块的整个过程:
5. 扩展
正常情况下,设计师通过AE、Principle这类动画制作软件导出的json文件里面数值都是具体的,svg动画实现到web端的细节参数是很多的,一个完整的动画,如果存在外层宽高比例变化的情况,哪怕是一个像素的差异,都会导致很多svg参数的改变,所以因此svg提供 preserveAspectRatio 这么一个参数去让我们“兼容”。
那这个属性是干嘛用的呢?官方描述是很抽象的,简单来说,可以这么理解:
它是一个调整元素在二维画布中,位置、宽度、高度自适应缩放的一个自定义设置,可以让你的svg元素,在外层容器宽高比例出现变化的时候,做出定义的适配变化,也就是说,如果外层和最初的这个比例基准不对等的时候,我们让它怎么处理。
6. 结束语
如果觉得写的不错,有用的话,还请帮我点个赞 O(∩_∩)O
转发请注明出处,谢谢!