SVG 动画精髓

DemBDKS 2年前
   <h2>TL;DR</h2>    <p>本文主要是讲解关于 SVG 的一些高级动画特效,比如 SVG 动画标签,图形渐变,路径动画,线条动画,SVG 裁剪等。</p>    <p>例如:路径动画</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/80851b30567570a3aa4d7ee0d011b900.gif"></p>    <p>图形渐变:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/37d4acb5a33d206778629618bbb30192.gif"></p>    <p>线条动画:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c2193db0ad56ab15ca74c04c46767bc8.gif"></p>    <p>以及,相关的动画的矩阵知识,这个也是现在 CSS 动画里面最重要,同时也是最为欠缺的知识点:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/fc2837a380b97da47f5d4d042b39051e.png"></p>    <p>文章会先从基本语法入手,然后,慢慢深入。介绍一些动画基本原理和对应的数学原理知识点。并且文章后面,还附有相关语法的介绍,当你在遇到不熟悉语法的时候可以参考参考。</p>    <p>前面一篇文章,主要介绍了一些 SVG 的基本概念和基本图形。接下来我们需要了解一下,SVG 处理矢量这个特性之外,还有啥内容吸引我们,能让 SVG 现在普及度这么高?</p>    <h2>SVG Animation</h2>    <p>在 SVG 中,如果我们想实现一个动画效果,可以使用 CSS,JS,或者直接使用 SVG 中自带的 animate 元素添加动画。</p>    <p>使用 CSS 的话,有两种选择一种是通过 style 直接内联在里面,另外是直接使用相关的动画属性-- transform 。</p>    <pre>  <code class="language-css"><use id="star" class="starStyle" xlink:href="#starDef"         transform="translate(100, 100)"         style="fill: #008000; stroke: #008000"/></code></pre>    <p>而使用 SVG 中自定的 animate 主要还是 SVG 自己的东西,比较好用。如果想用 CSS 的动画,这都无所谓。</p>    <p>先看一个 SVG animate DEMO:</p>    <pre>  <code class="language-css"><rect x="10" y="10" width="200" height="20" stroke="black" fill="none">    <animate      attributeName="width"      attributeType="XML"      from="200" to="20"      begin="0s" dur="5s"      fill="freeze" />  </rect>  </code></pre>    <p>通过将 animate 标签嵌套在指定的图形里面,即可实现变换的效果。另外,还有 animateTransform,它主要是用来做变形动画的。</p>    <pre>  <code class="language-css"><rect x="-10" y="-10" width="20" height="20"      style="fill: #ff9; stroke: black;">      <animateTransform attributeType="XML"        attributeName="transform" type="scale"        from="1" to="4 2"        begin="0s" dur="4s" fill="freeze"/>  </rect>  </code></pre>    <p>简单来说:</p>    <ul>     <li>animate: 相当于 CSS 中的 transition</li>     <li>animateTransform: 相当于 CSS 中的 transform</li>    </ul>    <p>里面一些技术细节我们这里就不过多讲解了。这里,主要想介绍一下 animate 中的 morph 的效果。</p>    <h3>animate morph</h3>    <p>该效果主要做的就是图形内部的渐变。如图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/37d4acb5a33d206778629618bbb30192.gif" alt="SVG 动画精髓" width="112" height="108"></p>    <p>这种动画是怎么实现呢?</p>    <p>直接看代码吧:</p>    <pre>  <code class="language-css"><path fill="#1EB287">      <animate                attributeName="d"                dur="1440ms"                repeatCount="indefinite"               keyTimes="0;                         .0625;                         .208333333;                         .3125;                         .395833333;                         .645833333;                         .833333333;                         1"               calcMode="spline"                keySplines="0,0,1,1;                           .42,0,.58,1;                           .42,0,1,1;                           0,0,.58,1;                           .42,0,.58,1;                           .42,0,.58,1;                           .42,0,.58,1"               values="M 0,0                        C 50,0 50,0 100,0                       100,50 100,50 100,100                       50,100 50,100 0,100                       0,50 0,50 0,0                       Z;                         M 0,0                        C 50,0 50,0 100,0                       100,50 100,50 100,100                       50,100 50,100 0,100                       0,50 0,50 0,0                       Z;                         M 50,0                        C 75,25 75,25 100,50                        75,75 75,75 50,100                       25,75 25,75 0,50                       25,25 25,25 50,0                       Z;                         M 25,50                        C 37.5,25 37.5,25 50,0                        75,50 75,50 100,100                       50,100 50,100 0,100                       12.5,75 12.5,75 25,50                       Z;                         M 25,50                        C 37.5,25 37.5,25 50,0                        75,50 75,50 100,100                       50,100 50,100 0,100                       12.5,75 12.5,75 25,50                       Z;                         M 50,0                       C 77.6,0 100,22.4 100,50                        100,77.6 77.6,100 50,100                       22.4,100, 0,77.6, 0,50                       0,22.4, 22.4,0, 50,0                       Z;                                              M 50,0                       C 77.6,0 100,22.4 100,50                        100,77.6 77.6,100 50,100                       22.4,100, 0,77.6, 0,50                       0,22.4, 22.4,0, 50,0                       Z;                                              M 100,0                        C 100,50 100,50 100,100                       50,100 50,100 0,100                       0,50 0,50 0,0                       50,0 50,0 100,0                       Z;"/>    </path>  </code></pre>    <p>这么多,是不是感觉有点懵逼。不过,我们细分来看一下其实很简单。里面主要是利用 animate 中的 keyTimes , calcMode , keySplines ,以及 values 这几个属性。不急,我们一个一个来解释一下。</p>    <ul>     <li>keyTimes: 这其实和 CSS 中定义的 @keyframes 一样。通过 0-1 之间的值,定义每段动画完成的时间。格式为: value;value... 。例如 0;.0625;.208333333;.3125;.395833333;.645833333;.833333333;1 。从第一个动画,到第二个动画经历的时间比例为 6.25%。并且,keyTimes 需要和 values 里面定义的帧数一致。</li>     <li>calcMode: 用来定义动画具体的插值模型。取值有: discrete | linear[default] | paced | spline 。具体可以参考 MDN 。这里我们主要介绍一下 spline。该值表示每个动画间使用自定的贝塞尔变换曲线。如果没有特殊要求,使用 linear 其实已经足够了,这样就不用麻烦去定义下面的 keySplines 属性。</li>     <li>keySplines:该值用来具体定义动画执行时的 贝塞尔曲线 。使用格式是通过 ; 来分隔每一个值。即, cubic-bezier(.31,.57,.93,.46) 为一组。使用 keySplines 表达,则为: keySplines = ".31,.57,.93,.46;" 。当然,里面的贝塞尔曲线组数为 整个动画帧数 - 1 。</li>    </ul>    <p>而 values 就很简单了。它是直接结合 attributeName 属性,来设置具体的值,每个值之间使用 ; 进行分隔。</p>    <p>像上面那样,可以在指定元素里面嵌套多个 animate,既实现了形状的改变,又实现了颜色的改变。Morph 比较常用于数字的更迭,比如, 倒数 10s 的相关动画 。到这里,Morpah 相关的知识点就结束了。</p>    <p>接着,让我们来看一下 SVG 中,另外一非常重要的标签 – animateMotion 。</p>    <p>该标签可以让指定的元素,绕着指定的路径进行运动。所以这对于复杂的路径来说非常有用,因为我们很难使用 transform 去模拟复杂的变换路径。看一个 DEMO</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/80851b30567570a3aa4d7ee0d011b900.gif" alt="SVG 动画精髓" width="600" height="377"></p>    <h3>animateMotion</h3>    <p>animateMotion 大致的属性和 animate 差不多,不过,它还拥有自己特有的属性,比如 keyPoints 、 rotate 、 path 等。不过,calcMode 在 AM(animateMotion) 中的默认属性由, linear 变为 paced 。</p>    <p>这些属性,我们慢慢介绍,先从最简单的开始吧。首先,我们来看一个 DEMO:</p>    <pre>  <code class="language-css"><g>    <rect x="0" y="0" width="30" height="30" style="fill: #ccc;"/>    <circle cx="30" cy="30" r="15" style="fill: #cfc; stroke: green;"/>    <animateMotion from="0,0" to="60,30" dur="4s" fill="freeze"/>  </g>  </code></pre>    <ul>     <li>from,to:指定两点的位置,位置参数是以元素的坐标为原点的。</li>     <li>dur:执行渲染时间</li>     <li>fill:指定动画结束后停留的装填。有 freeze 和 remove 效果。remove 表示回到动画开始的位置,freeze 表示停留在动画结束的位置。</li>    </ul>    <p>如果,你想要更复杂的路径,可以直接使用 path 属性来指定路径。用法和 path 标签中 d 属性是一样的。</p>    <pre>  <code class="language-css"><rect x="0" y="0" width="30" height="30" style="fill: #ccc;">      <animateMotion      path="M50,125 C 100,25 150,225, 200, 125"      dur="6s" fill="freeze"/>  </rect>  </code></pre>    <p>或者使用 mpath 标签,引用外部的 path 。</p>    <pre>  <code class="language-css"><path d="M10,110 A120,120 -45 0,1 110 10 A120,120 -45 0,1 10,110"        stroke="lightgrey" stroke-width="2"         fill="none" id="theMotionPath"/>    <circle cx="10" cy="110" r="3" fill="lightgrey"  />    <circle cx="110" cy="10" r="3" fill="lightgrey"  />      <!-- Red circle which will be moved along the motion path. -->    <circle cx="" cy="" r="5" fill="red">        <!-- Define the motion path animation -->      <animateMotion dur="6s" repeatCount="indefinite">        <mpath xlink:href="#theMotionPath"/>      </animateMotion>    </circle>  </code></pre>    <p>动画效果为:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d2e28a0fc96460b85a87643dc8ce6ed6.gif"></p>    <p>所以,一般而言我们在定义 AM 的路径的时候,只用一种方式定义即可,否则会发生相应的覆盖: mpath > path > values > from/to 。</p>    <p>在 AM 运动中,还有一个很重要的概念就是旋转角。默认情况下,运动物体的角度是按照它和坐标轴的初始角度确定的。例如:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0b64d72ee769dd47194512fbb77af285.gif"></p>    <p>这样看起来确实有些别扭,那能不能让物体垂直于路径进行运动呢?</p>    <p>有的,根据 rotate 属性值,一共有 3 个值可供选择。</p>    <ul>     <li>auto:让物体垂直于路径的切线方向运动。不过,如果你的路径是闭合曲线的话,需要注意起始点的位置。</li>    </ul>    <p>例如:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/27093ecaa90ff6562917dbf8f6dbc600.gif"></p>    <ul>     <li>auto-reverse:让物体垂直于路径的切线方向并 + 180°。也就是和 auto 运动关于切线对称。</li>    </ul>    <p style="text-align:center"><img src="https://simg.open-open.com/show/8923005de3ae27c33b50a67c4f409cb2.gif"></p>    <ul>     <li>Number:让物体以固定的旋转角度运动。这个就相当于使用 transform:rotate(deg) 进行控制。</li>    </ul>    <p>在动画设置标签中,还有一个更简单的– set 。</p>    <h3>set</h3>    <p>该标签也是用来模拟 transition 效果的。它和 animate 的主要区别是,它仅仅需要 to 的指定属性,而不需要其他的参考属性,比如 from , by 等。那它有啥特别的存在意义吗?</p>    <p>有的,因为 set 针对于所有属性,甚至包括 style 里面的相关 CSS 属性。所以,可以靠它来很好描述一些非 number 的属性值。</p>    <pre>  <code class="language-css"><text text-anchor="middle" x="60" y="60" style="visibility: hidden;">    <set attributeName="visibility" attributeType="CSS"      to="visible" begin="4.5s" dur="1s" fill="freeze"/>    All gone!  </text>  </code></pre>    <h2>矩阵动画</h2>    <p>上面差不多简单阐述了关于 SVG 一些比较有特点的动画。当然,还有比较重要的线条动画,这个我们放到后面进行讲解。这里先来看一下所有动画中,非常重要的矩阵原理。线性代数应该是大学里面来说,最容易学的一门科目,MD。。。还记得,大学线代期末考试的时候,100 分的同学应该说是如韭菜地般,一抓一大片(对不起,我没能和他们同流合污。)</p>    <p>那矩阵是如何在动画中使用的呢?</p>    <p>简单的说,矩阵中的每个元素其实可以等价代换为每个因式里面的系数:</p>    <p><img src="https://simg.open-open.com/show/4da980b32ec1b43d718108148ab20690.png"></p>    <p>上面也叫作 三维矩阵。即,它涉及到 x,y,z 轴的计算。那对于我们平面 2D 变换来说,那么此时矩阵又是哪种形式呢?</p>    <p>很简单,只要将 z 轴永远置为一个常数就 OK。这里,惯例上是直接取 0 0 1 来设置。</p>    <p><img src="https://simg.open-open.com/show/0ea48b003db8ed206159cce0238a99a3.png"></p>    <p>不信的话,大家只要代进去乘以乘,应该就可以得到结果了。所以,在二维中,具体变换方式为:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/1a194941e76ce6a2afba32e72af1a4eb.png" alt="SVG 动画精髓" width="454" height="105"></p>    <p>后面,我们也会依据这个公式进行相关的变形操作。那矩阵变换是怎么运用到 CSS/SVG 当中呢?</p>    <p>在 CSS 中,是直接使用 transform 中的属性:</p>    <pre>  <code class="language-css">transform: matrix(a,b,c,d,e,f);</code></pre>    <p>当然,在 SVG 中也是一样的:</p>    <pre>  <code class="language-css"><g transform="matrix(1,2,3,4,5,6)">      <line x1="10" y1="20" x2="30" y2="40" style="stroke-width: 10px; stroke: blue;"/>    </g>  </code></pre>    <p>所以,我们主要的重点就是讲解一下 matrix 这个属性。它的格式为:</p>    <pre>  <code class="language-css">matrix(a,b,c,d,e,f);</code></pre>    <p>对应于我们上面的公式有:</p>    <p><img src="https://simg.open-open.com/show/dad9d00de37ab6c25e10e62b149bd8a3.png"></p>    <p>在接触 transform 的时候,大家应该了解到 transform 里面有很多固定的动画属性:</p>    <ul>     <li>translate()</li>     <li>rotate()</li>     <li>scale()</li>     <li>skew()</li>    </ul>    <p>实际上,在底层还是使用 matrix 实现的变换。就拿 translate 举个例子吧。</p>    <p>translate 的格式为:</p>    <pre>  <code class="language-css">translate(dx,dy)</code></pre>    <p>相当于参考当前原点,在 x/y 轴上移动 dx/dy 的距离。那么映射到矩阵,应该如何表示呢?</p>    <p>很简单,它等同于:</p>    <pre>  <code class="language-css">matrix(1 0 0 1 dx dy);</code></pre>    <p>使用代数证明一下:</p>    <p>假设有 matrix(1 0 0 1 20 30)</p>    <p>变为矩阵为:</p>    <p><img src="https://simg.open-open.com/show/6f99ee231967d478ab1091ed0e84f01f.png"></p>    <p>根据,上面的表达式有:</p>    <pre>  <code class="language-css">X = x'*1 + y'*0 + 20 = x' + 20  Y = x'*0 + y'*0 + 30 = y' + 30</code></pre>    <p>所以,就是 X 在原有 X 轴坐标上向右移动 20 的距离,Y 相对于原有移动 30 的距离。</p>    <p>那么其他几个属性呢?也是怎么变化的吗?</p>    <p>恩,类似。只是里面取值不一样:</p>    <ul>     <li>scale(x,y): 放大 X/Y 轴,矩阵的表达为 matrix(x 0 0 y 0 0)。</li>     <li>rotate(θ): 坐标旋转,矩阵的表达为 matrix(cosθ sinθ -sinθ cosθ 0 0)。</li>     <li>skew(θx,θy): X/Y 轴拉伸,矩阵的表达为 matrix(1 tanθx tanθy 1 0 0)。</li>    </ul>    <p>注意,上面三个都会改变原有物体的坐标系!!! 这点很重要,换句话说,后面每次变换都是基于前面一个的变换结果的。</p>    <p>详情看图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c47aa3d61e07d5728ac490b7dc61276e.png"></p>    <p>不过,这并不是我们使用 matrix 的重点,也不是它的优势。它的优势在于可计算,即,能够将复杂的动画集合到一个表达式中,并且,后续的变换可以直接基于当前的 matrix。</p>    <p>我们先来了解一下,如果多个变换动画一起使用,matrix 应该如何表达呢?</p>    <p>**只需要找到我们变换动画对应的矩阵,然后相乘即可。**例如,先旋转 45°,然后放大 1.5 倍,则有变换动画为:</p>    <pre>  <code class="language-css">transform: rotate(45deg) scale(1.5,1.5);</code></pre>    <p>注意,虽然,你定义动画是分开的,但此时的动画是同时进行的。为啥?因为,这两个动画实际上可以整合成为一个变换矩阵:</p>    <p><img src="https://simg.open-open.com/show/72cffda6f97031c00c6782f5208dbaac.png"></p>    <p>并且,位置是不可以调换的。比如, transform: scale(2,2) translate(20px,30px) 。即,你先放大两倍,然后移动 20,30 的距离。注意,这里移动的 20,30 相对的是已经放大过后的坐标,相对于原坐标而言就是 40,60 了。 如果,你调换位置,即 transform: translate(20px,30px) scale(2,2) 。就变成现在原坐标移动 20,30,然后再放大两倍。</p>    <p>而上面强调的顺序关系,实际上就可以理解为 <strong>矩阵不满足交换律的原则</strong> 。因为一旦交换,结果很可能不一样。</p>    <h3>矩阵高级用法</h3>    <p>上面的内容只是简单的描述了关于矩阵的概念。在实际中,矩阵可以说是真正利器。</p>    <p>假设现在有一个动画,要求你将一个物体从一个点通过抛物线的方式移动到另外一个点,那么此时要求 JS/CSS 随你挑。此时,你会不会感觉,呼吸急促,头脑发热呢?</p>    <p>恩,matrix 可以治,而且包治百病。不过,matrix 有一个限制点,它只能用于一次线性动画表达式。即,针对于抛物线,椭圆曲线这类复杂曲线来说,不太合适。那么有什么办法吗?</p>    <p>有的,微分思想。每一段动画其实都可以通过一定范围内的直线拼接而成,那么这样,我们就可以将一段抛物线拆分为由几段线段构成的曲线。当然,如果你分的越细,拟合度就越高。这里我们不打算过度你和,我们简单的将一段抛物线分为 5段。</p>    <p>如图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/4a68cb869192bc4ecfd1dd015967ecab.png"></p>    <p>那么接下来就是抠细节。这里,依次取倾角为 45°,30°,0°,-45°,-30° 这 5 段直线。每段分配的时间比例为 20%、25%、10%、25%、20% 这主要是用于 keyframe 的设定。现在,用数学来分析一下,这个动画到底该怎么弄。</p>    <p>现在,已知两点之间的距离为 100px。那么我们同样根据上述比例分,则有 20px, 25px, 10px, 25px, 20px。</p>    <p>这里我们以 45° 倾角为参考点,则终点坐标为 (20,20); 。那么,该段的矩阵为:</p>    <pre>  <code class="language-css">// 注意 Y 轴需要取负值!     1 0 20   0 1 -20   0 0 1</code></pre>    <p>CSS 中的变形动画为:</p>    <pre>  <code class="language-css">transform: matrix(1,0,0,1,20,-20);</code></pre>    <p>然后,第二段就为:</p>    <pre>  <code class="language-css">1 0 25  0 1 -14.4  0 0 1</code></pre>    <p>使用矩阵的乘法法,则有:</p>    <pre>  <code class="language-css">1 0 45   0 1 -34.4   0 0 1</code></pre>    <p>变形动画为:</p>    <pre>  <code class="language-css">transform: matrix(1,0,0,1,45,-34.4);</code></pre>    <p>剩余几段也是这样的做法。最终,整个 keyframe 就应该表示为:</p>    <pre>  <code class="language-css">@keyframe Parabola{      20%{          transform: matrix(1,0,0,1,20,-20);      }      45%{          transform: matrix(1,0,0,1,45,-34.4);      }      ...  }</code></pre>    <p>整个动画过程差不多都是这样。当然,矩阵也不仅仅局限于这几个动画,凭借着高度定制化和灵活性的特点,这它还常常用于进行回弹,弹跳等动画中。如果大家有兴趣,后期也可以对这类动画进行简单的讲解。</p>    <p>后面,我们最后来了解一下 SVG 中很重要的线条动画。</p>    <h2>线条动画</h2>    <p>SVG 中的线条动画常常用作过渡屏(splash screen)中。例如:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/eceee609f5df2af7bc00cfdd69871c05.gif"></p>    <p>或者,一些比较炫酷的 LOGO 中,比如 AllowTeam 的:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/dbb1fc4f84323b785ee69450b1491854.gif"></p>    <p>看到这些炫酷的效果,大家有没有动心想学一学,看看自己到底能否做的这么好呢?</p>    <p>OK,我们现在来正式介绍一下线条动画。在 SVG 中,最长用到的线条标签就是 Path 。这里我前面一篇文章已经做了介绍,我这里就不赘述了。</p>    <p>而在具体变化当中用到的是关于 stroke 的相关属性:(下面的属性都可以直接用在 CSS 当中!)</p>    <ul>     <li>stroke*:定义笔触的颜色。例如: stroke="green"</li>     <li>stroke-dasharray*:定义 dash 和 gap 的长度。它主要是通过使用 , 来分隔 <strong>实线</strong> 和 <strong>间隔</strong> 的值。例如: stroke-dasharray="5, 5" 表示,按照 实线为 5,间隔为 5 的排布重复下去。如下图:</li>    </ul>    <p><img src="https://simg.open-open.com/show/1345c755daba9c92296266f3a517d8cf.png"></p>    <p>放大看有:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0d3d80b3e7d3861b9c8a78e7e630e2dc.png"></p>    <p>另外,stroke-dasharray 并不局限于只能设置两个值,要知道,它本身的含义是设置最小重复单元,即, dash,gap,dash,gap... 。比如,我定义 stroke-dasharray="15, 10, 5" 则相当于,[15,10,5] 为一段。则有:</p>    <p><img src="https://simg.open-open.com/show/ad4d027123454d41801d67bf6c6eafa1.png"></p>    <p>放大看则有:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/89c03c90c559417d063c61c762b65926.png"></p>    <ul>     <li>stroke-dashoffset*: 用来设置 dasharray 定义其实 dash 线条开始的位置。值可以为 number || percentage 。百分数是相对于 SVG 的 viewport。通常结合 dasharray 可以实现线条的运动。</li>     <li>stroke-linecap: 线条的端点样式。</li>     <li>stroke-linejoin: 线条连接的样式</li>     <li>stroke-miterlimit: 一个比较复杂的概念,如果我们只是画一些一般的线段,使用上面 linejoin 即可。如果涉及对边角要求比较高的,则可以使用该属性进行定义。它的值,其实就是角长度比上线宽:</li>    </ul>    <p style="text-align:center"><img src="https://simg.open-open.com/show/3df1b1428e8af4c86af6f4119876969c.png"></p>    <p>而实际理解的话,就是假设当 width 为 1。此时比例为 2。那么 miter = 2。那么超过 2 的 miter 部分则会被 cut 掉。可以参照:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/fc3999765c76e534aa1debd339aebd03.png"></p>    <p>他主要是配合 linejoin 一起使用。因为 linejoin 默认取值就是 miter 。所以,默认情况下就可以使用该标签属性。它默认值为 4。其余的大家下去实践一下即可。详细可以参考: <a href="/misc/goto?guid=4959748249602964790" rel="nofollow,noindex">miter</a></p>    <ul>     <li>stroke-opacity:线段的透明度</li>     <li>stroke-width:线的粗细。</li>    </ul>    <p>OK,介绍完关于 path 的所有 stroke 属性之后,我们就要开始动手写一下让线条动起来的代码。简单来说,就是通过 stroke-dashoffset 和 stroke-dasharray 来做。整个动画可以分为两个过程:</p>    <ul>     <li>通过 dasharray 将实线部分隐藏,空余为全线段长。然后,将实线部分增加至全长。比如: dasharray: 0,1000 变为 dasharray: 1000,1000 。</li>     <li>同时,通过 dashoffset 来移动新增的实线部分,造成线段移动的效果。有: dashoffset:0 ,变为 dashoffset:1000 。</li>    </ul>    <p>不过,这里我们不打算使用 Path 来做啥复杂的动画,这主要考虑到手头没有一些 SVG 生成工具。所以,这里我们就以 Text 来做吧(因为做起来真的简单)。</p>    <p>这里,先以 IV-WEB 这段文字来做动画。</p>    <p>先给大家看一下最终结果:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/4d1aa8446145a49945c576de2086118c.gif"></p>    <p>那么这种动画是怎么做的呢?</p>    <p>这里,我主要介绍一下关于 CSS 相关,SVG 就一个 Text 我直接贴代码了:</p>    <pre>  <code class="language-css"><svg viewBox="0 0 1320 300">      <!-- Symbol -->    <symbol id="s-text">      <text text-anchor="middle"            x="50%" y="50%" dy=".35em">        IV-WEB      </text>    </symbol>        <!-- Duplicate symbols -->    <use xlink:href="#s-text" class="text"         ></use>    <use xlink:href="#s-text" class="text"         ></use>    <use xlink:href="#s-text" class="text"         ></use>       </svg>  </code></pre>    <p>上面是通过创建一个居中定位的字体,然后使用 3 个 text 重叠。具体 CSS 我们下面来说一下。首先,我们营造的效果是从无到有,就需要使用 dasharray 将 gap 设置的足够大。这里我取 300 即可。</p>    <pre>  <code class="language-css">stroke-dasharray: 0 300;</code></pre>    <p>然后,通过 nth-child 选择器,给每一个文字使用不同的颜色值:</p>    <pre>  <code class="language-css">.text:nth-child(3n + 1) {    stroke: #F60A0A;  }  .text:nth-child(3n + 2) {    stroke: #F2FF14;  }    .text:nth-child(3n + 3) {    stroke: #FB9505;  }</code></pre>    <p>下面才是重点内容。此时,这 3 个 text 的起始点重合。我现在既要他们在运行时不完全重合,又要他们的线条能进行滚动。不啰嗦了,直接看代码吧:</p>    <pre>  <code class="language-css">@keyframes stroke {    100% {      stroke-dashoffset: 1000;      stroke-dasharray: 80 160;    }  }    @keyframes stroke1 {    100% {      stroke-dashoffset: 1080;      stroke-dasharray: 80 160;    }  }      @keyframes stroke2 {    100% {      stroke-dashoffset: 1160;      stroke-dasharray: 80 160;    }  }</code></pre>    <p>这就是上面 3 个不同的 text 运用的动画。 dashoffet 由 0 到 1000。这完成了滚动的目的。同时,为了让字体不重合,我还需要在对应字体的 dashoffset 上,加上不同的间隔距离。比如,第一个字体 offset 为 1000。那么第二个字体,我需要加上前一个字体 dash 的长度,即,80。所以,第二个字体就变为 1080 。那么第三个就是加上前两个的 dash 长度,即 1160 。</p>    <p>这里再给大家布置一个练习作业,如何实现无线连续的分段动画呢?</p>    <p>具体效果如图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c2193db0ad56ab15ca74c04c46767bc8.gif" alt="SVG 动画精髓" width="583" height="187"></p>    <p>给点提示:</p>    <p>将多个文字重叠,取不同的 offset 和 array 即可。动画的终止位置一般取一个 gap + dash 的周期长即可。</p>    <p>后面看看这篇文章反响如何,到时候再决定是否再写一篇续集,介绍该作业的原理。</p>    <h2>SVG 文字</h2>    <p>在 SVG 中定义文字直接使用 text 标签即可。关于文字来说,一般而言需要注意的点就那么即可,文字的排列,间距等等。这些都可以直接使用 CSS 进行控制。不过,有几个属性比较特殊,这里需要额外提一下。</p>    <h3>text-anchor</h3>    <p>用来定义参考点和实际字符之间的定位关系。格式为:</p>    <ul>     <li>text-anchor: start | middle | end | inherit</li>    </ul>    <p>直接看代码解释吧:</p>    <pre>  <code class="language-css"><!-- Anchors in action -->      <text text-anchor="start"            x="60" y="40">A</text>        <text text-anchor="middle"            x="60" y="75">A</text>        <text text-anchor="end"            x="60" y="110">A</text>  </code></pre>    <p>第一个 A,参考的是 (60,40) 的点,定义为 start ,那么参考点应该在字符的前面。</p>    <p><img src="https://simg.open-open.com/show/93746b152ac8f015db1be7394d88759b.png"></p>    <p>而剩下两个也是同样的道理:</p>    <p><img src="https://simg.open-open.com/show/d83db4a4a41c1e94091dd05270036bb3.png"></p>    <h3>tspan</h3>    <p>现在,假如我们想在 text 里面添加一些特殊的字符效果,比如斜体,加粗等。由于,text 标签不能实现嵌套,所以,为了解决这个痛点,提出了 tspan 的标签。它其实就是一个可以嵌套的 text 标签。</p>    <pre>  <code class="language-css"><text x="10" y="30" style="font-size:12pt;">    Switch among    <tspan style="font-style:italic">italic</tspan>, normal,    and <tspan style="font-weight:bold">bold</tspan> text.  </text>  </code></pre>    <p>tspan 里面同样可以自定义相关的自身属性。详细的可以参考 <a href="/misc/goto?guid=4959748249695933882" rel="nofollow,noindex">tspan</a> 我这里就不详述了。</p>    <h3>在 Path 展示 text</h3>    <p>Text 一般可以横放,竖放。那有没有啥办法让文字可以按照一定的路径任意排放呢?</p>    <p>有的,这里可以使用 textPath 标签,来定义具体参考路径。</p>    <pre>  <code class="language-css"><path id="sharp-corner"      d="M 30 110 100 110 100 160"      style="stroke: gray; fill: none;"/>    <text>      <textPath xlink:href="#sharp-corner">      Making a quick turn      </textPath>  </text>  </code></pre>    <p>如图:</p>    <p><img src="https://simg.open-open.com/show/ffe1a9892c3d8d2bca65367fb611e382.png"></p>    <p>具体细节我这里就不多说了。</p>    <h2>Clip</h2>    <p>在 DOM 中如果想展示一个图片的部分,或者以某种形状展示图片的部分,一般是通过一个 cover div 来实现的。不过,如果涉及到不规则图形的话,那么 DOM 就有天生缺陷了(当然使用 CSS 里的 clip-path 可以完成,不过兼容性不太好)。而在 SVG 中,提供了 clipPath 标签,能够让我们自定义裁剪图片的范围和形状。</p>    <p>clipPath 里面可以接任何图形,比如,path,rect 甚至是 text。使用的时候,直接在 style 中,指定 clip-path 即可,或者直接使用 clip-path 属性指定。</p>    <pre>  <code class="language-css"><defs>    <clipPath id="textClip">      <text id="text1" x="20" y="20" transform="rotate(60)"        style="font-family: 'Liberation Sans';          font-size: 48pt; stroke: black; fill: none;">  CLIP      </text>    </clipPath>   </defs>      <use transform="translate(100, 0)"    xlink:href="#shapes" style="clip-path: url(#textClip);"/>         <use transform="translate(100, 0)"    xlink:href="#shapes" clip-path="url(#textClip);"/>  </code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c7e86714154348337e90234ce53f603c.png"></p>    <p>或者说,如果我们想画一个圆的裁剪区域的话:</p>    <pre>  <code class="language-css"><defs>       <clipPath id="circularPath" clipPathUnits="objectBoundingBox">       <circle cx="0.5" cy="0.5" r="0.5"/>      </clipPath>  </defs>    <use xlink:href="#shapes" style="clip-path: url(#circularPath);" />  </code></pre>    <h2>Appendix 参考标签</h2>    <h3>g</h3>    <p>分组标签应该毫无意外排第一,因为其实作为绘制图形中最常和最基本的标签。前面一篇文章也主要介绍过了,这里做点补充。</p>    <p>每一个分组标签都带有 id 属性,唯一标识该分组,为什么呢?</p>    <p>因为,后面我们可以使用该 id 标签添加动画,重用该分组等。</p>    <pre>  <code class="language-css"><g id="demo" stroke="green" fill="white" stroke-width="5">       <circle cx="25" cy="25" r="15"/>       <circle cx="40" cy="25" r="15"/>       <circle cx="55" cy="25" r="15"/>       <circle cx="70" cy="25" r="15"/>     </g>  </code></pre>    <p>每个分组里面可以含有一些描述标签,比如 desc 。 这些描述内容是不会被渲染的。</p>    <pre>  <code class="language-css"><g id="demo" stroke="green" fill="white" stroke-width="5">      <desc>Just Demo</desc>       <circle cx="25" cy="25" r="15"/>     </g>  </code></pre>    <h3>use</h3>    <p>该标签就是结合 g 标签一起使用,作用是可以复用 g 分组的样式。</p>    <pre>  <code class="language-css"><g id="Port">        <circle style="fill: inherit;" r="10"/>  </g>  <use x="50" y="30" xlink:href="#Port" class="classA"/>  </code></pre>    <p>里面使用 xlink:href 加上指定 group 的 id,然后通过 x , y 属性指定副本放置的位置。不过,有一个限制,use 标签的 style 属性,并不能覆盖点原始的 group style 样式。而且,有时候,我们只是想使用一些模板,即,图形并未被解析,只有代码存在。这时候,就需要使用 defs 来包裹了。</p>    <h3>defs</h3>    <p>用来保存一些代码,使其不会被浏览器解析。并且里面的分组可以被 use 属性的 style 样式所覆盖。</p>    <pre>  <code class="language-css"><defs>      <g id="Port">        <circle style="fill: inherit;" r="10"/>      </g>    </defs>     <use x="50" y="50" xlink:href="#Port" style="fill: blue;"/>  </code></pre>    <h3>symbol</h3>    <p>该标签和 g 标签类似,也是用来进行分组。不过,它有个特点,即,不会被浏览器所渲染。那它不和 defs 差不多吗?</p>    <p>恩,确实。不过,defs 是官方推荐,用来包裹一些模板 svg 代码而创造出来,用来增加可读性的标签。而 symbol 是存粹的作为一个模板。它可以独立于 svg 的 viewbox 来自定义子 viewbox 和 preserveAspectRatio。</p>    <pre>  <code class="language-css"><symbol id="sym01" viewBox="0 0 150 110">    <circle cx="50" cy="50" r="40" stroke-width="8"        stroke="red" fill="red"/>    <circle cx="90" cy="60" r="40" stroke-width="8"        stroke="green" fill="white"/>  </symbol>    <use href="#sym01"       x="0" y="0" width="100" height="50"/>  </code></pre>    <p>同样使用该模板,也是使用 use 标签来完成。</p>    <h3>image</h3>    <p>既然 use 可以重用 SVG 代码,那么 SVG 里面能不能重用已经画好的 png/jpg 的图片呢?</p>    <p>这时候,就需要用到 image 标签。其可以用来加载外部的 PNG, JPEG 图片,注意,官方规定是前两种,其它图片支持不支持官方没做答复。即,如果你使用 GIF 图片,并不能保证所有的浏览器都能正常显示。</p>    <pre>  <code class="language-css"><image xlink:href="kwanghwamun.jpg"    x="72" y="92"    width="160" height="120"/>  </svg>  </code></pre>    <p>同样,该 image 标签也具有自定义 preserveAspectRatio 的效果。</p>    <ul>     <li>x: 定义水平位置</li>     <li>y: 定义垂直位置</li>     <li>width: 图片渲染的宽度,必须有。</li>     <li>height: 图片渲染的高度,必须有。</li>     <li>preserveAspectRatio: 控制图片的缩放</li>    </ul>    <h3>marker</h3>    <p>marker 一般是用来画箭头或者线段始末的标识图形。</p>    <pre>  <code class="language-css"><defs>      <marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5"          markerWidth="6" markerHeight="6" orient="auto">        <path d="M 0 0 L 10 5 L 0 10 z" />      </marker>    </defs>      <polyline points="10,90 50,80 90,20" fill="none" stroke="black"         stroke-width="2" marker-end="url(#Triangle)" />  </code></pre>    <p>如图:</p>    <p><img src="https://simg.open-open.com/show/ade07ac65b0b13d1818ecb0773d607c0.png"></p>    <p>这里我们只需要里了解即可,因为在实际画的时候,直接使用相关工具生成更加方便。</p>    <h3>a</h3>    <p>这里的 a 标签和我们直接在 HTML 使用的超链接 a 标签类似。也是用来定义一个外链的。</p>    <pre>  <code class="language-css"><a xlink:href="https://developer.mozilla.org/en-US/docs/SVG"        target="_blank">      <rect height="30" width="120" y="0" x="0" rx="15"/>      <text fill="white" text-anchor="middle"             y="21" x="60">SVG on MDN</text>    </a>  </code></pre>    <p> </p>    <p> </p>    <p>来自:https://www.villainhr.com/page/2017/05/01/SVG 动画精髓</p>    <p> </p>