使用stroke-dashoffset 快速实现SVG描边动画

3,037 阅读4分钟

一、前言

公司或者个人logo如何描边呢?我因为这个好奇地去调查了一番,发现了svg用几行代码就能搞定,于是我写下这篇文章,希望能快速帮助大家理解实现描边动画

二、api

我们先来说一下本篇文章会用到的api

  • stroke-dasharray:控制用来描边的点划线的图案范式。

    这里可以传入以空格代表分隔的数组:可以传入任意数量的数字,代表了分割的规律。比如:

    • '1 ' : dash和间隔大小都为1
    • '1 2':dash大小为1 ,间隔大小为2
    • '1 2 3':dash大小为1,间隔大小为2,然后dash大小为3,间隔大小为1...不断循环下去

    例子来自MDN:

    <svg viewBox="0 0 30 10" xmlns="http://www.w3.org/2000/svg">
      <!-- No dashes nor gaps 没设置stroke-dasharray-->
      <line x1="0" y1="1" x2="30" y2="1" stroke="black" />
    
      <!-- Dashes and gaps of the same size stroke-dasharray只设置1个 -->
      <line x1="0" y1="3" x2="30" y2="3" stroke="black"
              stroke-dasharray="4" />
    
      <!-- Dashes and gaps of different sizes  stroke-dasharray设置2个-->
      <line x1="0" y1="5" x2="30" y2="5" stroke="black"
              stroke-dasharray="4 1" />
    
      <!-- Dashes and gaps of various sizes with an odd number of values   stroke-dasharray设置3个-->
      <line x1="0" y1="7" x2="30" y2="7" stroke="black"
              stroke-dasharray="4 1 2" />
    
      <!-- Dashes and gaps of various sizes with an even number of values stroke-dasharray设置4个-->
      <line x1="0" y1="9" x2="30" y2="9" stroke="black"
              stroke-dasharray="4 1 2 3" />
    </svg>
    

  • stroke-dashoffset:用于指定 stroke-dasharray 开始的偏移量,这也是动画的原理的关键

    我画个图帮助理解:

通过控制stroke-dashoffset 可以使得原本的图形发生偏移,让用户只能看到在框框中的图形。这时候再配合上css3的animation,设置偏移动画的时间和效果,就可以实现描边动画的效果

  • styleSheets:可以获取网页上引入的link样式表和style样式表。

    如果我们想要动态设置@keyframe,可以采用styleSheets的insertRule方法:

    • insertRule作用是来给当前样式表插入新的样式规则

    • insertRule(rule, index) 中第一个参数是必需的,代表要插入的新规则

    • 而index:可选,新规则插入的位置,样式表起始位置

三、描边动画实现

先举个简单的例子,如下图:

步骤:

  1. 我们先找设计师或者各大网站(比如这个例子我就是去iconfont里搜索的)拿到svg标签。
    <svg
      t="1610851432472"
      class="icon"
      viewBox="0 0 1024 1024"
      version="1.1"
      xmlns="http://www.w3.org/2000/svg"
      p-id="7792"
      width="200"
      height="200"
    >
      <path
        class="icon-path"
        stroke="#000"
        d="M177.152 655.36v-24.576c58.368 0 88.064-25.6 88.064-75.776V177.152c0-20.48-13.312-30.72-39.936-30.72-35.84 0-65.536 11.264-88.064 34.816C114.688 204.8 97.28 240.64 87.04 286.72L61.44 280.576l22.528-174.08c12.288 2.048 24.576 3.072 35.84 4.096 11.264 1.024 21.504 2.048 32.768 2.048h315.392c11.264 0 23.552-1.024 34.816-2.048 12.288-1.024 24.576-2.048 36.864-4.096v171.008l-22.528 3.072c-6.144-87.04-50.176-131.072-130.048-131.072-27.648 0-40.96 9.216-40.96 27.648v377.856c0 50.176 29.696 75.776 88.064 75.776V655.36H177.152z"
        fill="#90C9C4"
        p-id="7793"
      ></path>
      <path
        class="icon-path"
        stroke="#000"
        d="M600.064 917.504v-24.576c58.368 0 88.064-25.6 88.064-75.776V440.32c0-20.48-13.312-30.72-39.936-30.72-35.84 0-65.536 11.264-88.064 34.816-22.528 23.552-38.912 58.368-50.176 105.472l-25.6-6.144 22.528-174.08c12.288 2.048 24.576 3.072 35.84 4.096 11.264 1.024 21.504 2.048 32.768 2.048H890.88c11.264 0 23.552-1.024 34.816-2.048 12.288-1.024 24.576-2.048 36.864-4.096v171.008l-22.528 3.072c-6.144-87.04-50.176-131.072-130.048-131.072-27.648 0-40.96 9.216-40.96 27.648v377.856c0 50.176 29.696 75.776 88.064 75.776v24.576H600.064z"
        fill="#4FA39A"
        p-id="7794"
      ></path>
    </svg>
  1. 一般拿到svg的图片可能大小不合适,可以为它设置宽高

  2. 接着要设置stroke-dasharray的大小。

    重点

    • svg元素上有一个方法getTotalLength方法去获取到当前path的长度,是给stroke-dasharray赋值
    • 如果这时候有多个path元素,就去它们分别计算取最大值
    • 这里通过getTotalLength去取值,并且取最大值的原因是:
      1. 如果随便设置一个很大值,这时候如果业务场景需要不断地重复进行描边动画,这时候因为stroke-dasharray设置过大,会导致动画停留过久
      2. 而设置过小则因为stroke-dasharray方法是对图形切割的,会导致图形出现断层
        <script>
          document.addEventListener("DOMContentLoaded", () => {
            const logo = document.getElementsByClassName("icon-path")[0];
            console.log(logo.getTotalLength());
          });
        </script>
    
  3. 接着就是写css动画了

        <style>
          .icon {
            width: 300px;
            height: 300px;
          }
          .icon-path {
            fill: none;
            animation: animation 5s linear  forwards;
          }
          @keyframes animation {
            0% {
              fill: white;
              stroke: #333;
              stroke-dasharray: 2718;
              stroke-dashoffset: 2718;
            }
            50% {
              fill: white;
              stroke: #333;
              stroke-dasharray: 2718;
              stroke-dashoffset: 0;
            }
            75% {
              fill: #e5e7e7;
              stroke: white;
            }
            100% {
              fill: #90c9c4;
              stroke: white;
            }
          }
        </style>
    

    分析:

    • animation 是实现动画的关键,我们来介绍其中的几个比较容易忽略的属性
      1. linear这代表动画效果,可以参考animation-timing-function
      2. forwards 代表最后一帧是保持住当前图形而不用回到初始的时候。可以参考animation-fill-mode
      3. 这里animation 还可以设置数字或者infinite 代表重复次数或者无穷次,不设置则不重复,可以参考animation-iteration-count
      4. 2718是我经过getTotalLength去计算出的最大值。
      5. 0% 到 50% 是实现图形从无到有的形成过程, stroke-dashoffset设置为0代表展示图形
      6. 75%到100%的过程代表为图形上色的过程,这里的fill代表填充的颜色,这一般在拿到svg元素的就已经写在svg标签里了
  4. 到此整个流程结束,我们用一张图来总结吧:

四、进阶 | 通用设置

通过上一章节我们已经知道了整个svg实现描边动画的步骤了,但是有一个新的问题产生了:如果我们svg图形色彩比较丰富,比较复杂了。我们还是手动一个一个为每个svg中的path元素去计算path长度,然后找到最大值吗?手动为每个path添加animation吗?这显然很不通用,也不是我们程序员该做的事。所以本篇将讲解更通用的设置:

改善想法

  1. 当我们svg图形比较复杂的时候由多个path组合而成,我们需要计算所有path的路径的长度,所以我们需要通过循环获取path元素得到全部路径长度,取其中的最大值。

具体代码如下:

        let pathElements = document.getElementsByTagName("path");
        let maxPath = 0;
        for (let i = 0; i < pathElements.length; i++) {
          const pathElement = pathElements[i];
          maxPath =
            maxPath < pathElement.getTotalLength()
              ? pathElement.getTotalLength()
              : maxPath;
        }
  1. 而要为每个path动态设置animation的关键在于如何动态设置@keyframes,这里就要用到styleSheets中的insertRule(相关概念已经在api中讲解)
        for (let j = 0; j < pathElements.length; j++) {
          const pathElement = pathElements[j];
          const fill = pathElement.getAttribute("fill");
          const path = {
            fill: "none",
            animation: `animation${j} 5s linear 3 forwards`,
          };
          setStyle(pathElement, path);
          document.styleSheets[0].insertRule(
            `
            @keyframes animation${j} {
            0% {
              fill: white;
              stroke: #333;
              stroke-dasharray: ${maxPath};
              stroke-dashoffset: ${maxPath};
            }
            50% {
              fill: white;
              stroke: #333;
              stroke-dasharray: ${maxPath};
              stroke-dashoffset: 0;
            }
            100% {
              fill: ${fill};
              stroke: ${fill};
            }
            }
          `
          );
        }

分析:

  • 这里面的内容可以做成很通用,比如传入动画时间等等,这里提供一个思路,就没写得很具体了

最后整体代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
    </style>
    <svg
      t="1610851432472"
      class="icon"
      viewBox="0 0 1024 1024"
      version="1.1"
      xmlns="http://www.w3.org/2000/svg"
      p-id="7792"
      width="200"
      height="200"
    >
      <path
        class="icon-path"
        stroke="#000"
        d="M177.152 655.36v-24.576c58.368 0 88.064-25.6 88.064-75.776V177.152c0-20.48-13.312-30.72-39.936-30.72-35.84 0-65.536 11.264-88.064 34.816C114.688 204.8 97.28 240.64 87.04 286.72L61.44 280.576l22.528-174.08c12.288 2.048 24.576 3.072 35.84 4.096 11.264 1.024 21.504 2.048 32.768 2.048h315.392c11.264 0 23.552-1.024 34.816-2.048 12.288-1.024 24.576-2.048 36.864-4.096v171.008l-22.528 3.072c-6.144-87.04-50.176-131.072-130.048-131.072-27.648 0-40.96 9.216-40.96 27.648v377.856c0 50.176 29.696 75.776 88.064 75.776V655.36H177.152z"
        fill="#90C9C4"
        p-id="7793"
      ></path>
      <path
        class="icon-path"
        stroke="#000"
        d="M600.064 917.504v-24.576c58.368 0 88.064-25.6 88.064-75.776V440.32c0-20.48-13.312-30.72-39.936-30.72-35.84 0-65.536 11.264-88.064 34.816-22.528 23.552-38.912 58.368-50.176 105.472l-25.6-6.144 22.528-174.08c12.288 2.048 24.576 3.072 35.84 4.096 11.264 1.024 21.504 2.048 32.768 2.048H890.88c11.264 0 23.552-1.024 34.816-2.048 12.288-1.024 24.576-2.048 36.864-4.096v171.008l-22.528 3.072c-6.144-87.04-50.176-131.072-130.048-131.072-27.648 0-40.96 9.216-40.96 27.648v377.856c0 50.176 29.696 75.776 88.064 75.776v24.576H600.064z"
        fill="#4FA39A"
        p-id="7794"
      ></path>
    </svg>
  </head>
  <body>
    <script>
      function setStyle(target, styles) {
        for (const k in styles) {
          target.style[k] = styles[k];
        }
      }
      document.addEventListener("DOMContentLoaded", () => {
        let pathElements = document.getElementsByTagName("path");
        let maxPath = 0;
        for (let i = 0; i < pathElements.length; i++) {
          const pathElement = pathElements[i];
          maxPath =
            maxPath < pathElement.getTotalLength()
              ? pathElement.getTotalLength()
              : maxPath;
        }
        for (let j = 0; j < pathElements.length; j++) {
          const pathElement = pathElements[j];
          const fill = pathElement.getAttribute("fill");
          const path = {
            fill: "none",
            animation: `animation${j} 5s linear 3 forwards`,
          };
          setStyle(pathElement, path);
          document.styleSheets[0].insertRule(
            `
            @keyframes animation${j} {
            0% {
              fill: white;
              stroke: #333;
              stroke-dasharray: ${maxPath};
              stroke-dashoffset: ${maxPath};
            }
            50% {
              fill: white;
              stroke: #333;
              stroke-dasharray: ${maxPath};
              stroke-dashoffset: 0;
            }
            100% {
              fill: ${fill};
              stroke: ${fill};
            }
            }
          `
          );
        }
      });
    </script>
  </body>
</html>

五、更多效果展示

我们之后想要换其他svg图形,就只要去替换html中的svg元素就可以了(记得设置宽高)。

但是不足的地方是如果一些svg过于复杂,可能形成的效果会不尽人意了。(太复杂的动画建议还是让ui生成gif动图,也节省空间)


下面展示的svg都是去iconfont的插件库找的

(1) 跑车

(2) 404

(3) 福

(4)呐喊