两个属性,一个想法

一条 SVG 描边可以画成一段段虚线,而不必是实线。stroke-dasharray 描述这个图案——每段实线的长度、以及它们之间间隙的长度;stroke-dashoffset 则说明图案从路径的哪个位置开始。

模型就这么简单。数组决定图案的形状,偏移决定它的位置。而线除了自身点序之外没有天生的方向,所以让图案沿它滑动,看起来就完全等于运动。

dasharray 设定图案,dashoffset 把它整体滑动 间隙 虚线段 dasharray: 12 9  ·  图案沿路径重复 移动 dashoffset 会带动所有虚线段一起滑动,所以流动读起来是一股同向的水流。
图 1 —— 虚线段与间隙拼出图案,偏移量把它移动。路径几何本身毫无改变。

流动线效果

要让管道流动,把图案保持适中,并让偏移量每个循环后退整整一个周期。周期就是虚线段长度加间隙长度。虚线段 12、间隙 9,周期即 21;把偏移量动画到 -42,意味着走过了两个完整周期,循环因此无缝。

.flow {
  stroke-dasharray: 12 9;   /* 虚线段 + 间隙 = 21 周期 */
  stroke-dashoffset: 0;
  animation: travel 1.15s linear infinite;
}

@keyframes travel {
  to { stroke-dashoffset: -42; }  /* 2 × 21 */
}

负数为什么重要

递减的偏移量把图案往路径起点拉,眼睛读到的就是向前运动。把偏移改成正值,方向就翻转。除非系统确实会反向,否则让同一张图里所有箭头朝同一个方向走,否则读者会稀释掉这个信号。

自绘路径效果

同样两个属性,也能让路径"自己画出来"而不是被流动冲刷。设一段比整条路径更长的虚线、再加一段比路径更长的间隙,然后把偏移量从整条路径长度动画到零。线就会像从起点开始一笔笔画出自己。

.draw {
  stroke-dasharray: 600;   /* ≥ 路径长度 */
  stroke-dashoffset: 600;  /* 起点时完全隐藏 */
  animation: draw 1.6s ease-out forwards;
}

@keyframes draw {
  to { stroke-dashoffset: 0; }
}
自绘路径:一段虚线,偏移量动画到零 65% 已绘出 dashoffset: 415 → 0 轮廓不断生长,直到圆环闭合 初值取不小于路径长度的数值,整条描边才会在起点时完全隐藏。
图 2 —— 一段与路径等长的虚线被偏移量隐藏,随着偏移量归零而逐渐画出。

值得注意的坑

  • 周期对不齐。若行程距离不是周期的整数倍,循环会明显跳动。乘上周期倍数即可。
  • 路径长度未知。手写的路径,可在控制台用 getTotalLength() 一行求出精确值,做到完美自绘。
  • 一次动太多。把偏移量跟缩放、模糊、旋转混在一起会埋没重点。一个会动的属性就够了。
  • 忘了减弱动效。把每段偏移关键帧都包起来,让它能被关闭;静止时的路径必须始终可读。
要点:流动与自绘不是两种技法——它们是同样两个属性配上不同数值。把周期学会一次,你就能给任何系统图解加上动画。