醉美水芙蓉 发表于 2026-6-10 21:50:04

马黑黑老师《一文讲清 SVG 动态绘图 》

<style>
    .artBox { margin: 20px auto; font: normal 18px/26px sans-serif; }
    .artBox mark { padding: 2px 6px; background: #ccc; }
    .artBox blockquote { padding: 4px 16px; border-left: 4px solid teal; border-radius: 8px; font-size: 16px; }
    .artBox > p { margin: 10px 0; }
    #msvg { display: block; margin: 10px auto 0; width: 200px; height: 200px; border: 1px solid gray; }
    #msvg circle { fill: transparent; stroke: green; stroke-width: 4; stroke-dasharray: 565; stroke-dashoffset: 565; }
    @keyframes stroking { to { stroke-dashoffset: 0; } }
    @keyframes filling { to { fill: cyan; } }

    .tMid { text-align: center; }
</style>

<div class="artBox">
    <p>这里所谓的“动态绘图”指“一笔一画”将图案陆续绘制出来,换言之就是展示SVG图形的绘制过程。例如下面的效果:</p>
    <svg id="msvg" viewBox="-100 -100 200 200" xmlns="http://www.w3.org/2000/svg" onclick="showDrawing()">
      <title>SVG绘图区域(点击演示)</title>
      <circle x="0" y="0" r="90"></circle>
    </svg>
    <p class="tMid"><button onclick="showDrawing()">点我开始演示</button></p>
    <p>请点击SVG绘图区域或其下按钮以了解“动态绘图”全程,然后如果想真正理解实现原理和实现过程可以继续往下看。</p>
    <h2>一、原理解释</h2>
    <p>就像变魔术一样,你所看到的未必是真的。就是说,SVG展示的绘图过程实际上是假的:图形早已绘好,你所看到的只不过是通过一些手段“复原”绘制流程。具体措施是:结合 stroke-dasharray 属性和 stroke-dashoffset 属性将图形的描边线隐藏起来、同时用透明色(transparent)填充图形,动画开始后不断更改 stroke-dashoffset 属性值让图形的描边路径持续呈现直至到达路径的总长度。</p>
    <blockquote>stroke-dasharray 是 SVG 和 CSS 中的表现属性,用于控制描边(stroke)如何以虚线模式渲染。它的值是一个由数字、长度或百分比组成的列表,表示交替的虚线段和间隙长度。</blockquote>
    <p>我们可以让图形对象的虚线模式(stroke-dasharray)为描边路径的总长度,这样理论上图形的虚线就一个单位,呈现出来的就是完整的描边实线(虚线中不包含间隙的线段),然后再将描边线整体偏移,这需要 stroke-dashoffset 属性加持——</p>
    <blockquote>stroke-dashoffset 属性控制虚线图案的起始位置。通过设置这个属性,可以实现虚线的偏移效果。偏移量可以是正值或负值,正值表示向右或顺时针偏移,负值表示向左或逆时针偏移。</blockquote>
    <p>stroke-dashoffset 属性将虚线百分百偏移就会看不到,这是障眼法核心。</p>
    <p>然后,通过 @keyframes 动画设置虚线偏移量从完整描边路径长度到 0 进行演变,描边线就能从无到有“绘制”出来,实际上是复原描边线唯一一根虚线线段中的实线部分。</p>
    <p>动画的最后一步是给图形填充颜色,这个只需要更换一下图形对象的 fill 属性值,没有什么技术含量。</p>
    <h2>二、实现步骤</h2>
    <p>首先设置CSS核心样式:</p>
    <div class="hEdiv"><pre class="hEpre">
&lt;style&gt;
    /* id="msvg" 的 svg 标签内的圆 */
    #msvg circle {
      fill: transparent;      /* 透明填充 */
      stroke: green;          /* 描边颜色 */
      stroke-width: 4;      /* 描边宽度 */
      stroke-dasharray: 565;/* 虚线全线段(描边路径总长度≈2×π×r) */
      stroke-dashoffset: 565; /* 虚线完整偏移 */
      /* 运行动画 */
      animation: stroking 4s linear forwards, filling 2s 4s linear forwards;
    }
   
    /* 描边动画 */
    @keyframes stroking {
      to { stroke-dashoffset: 0; }
    }
   
    /* 填充颜色动画 */
    @keyframes filling {
      to { fill: cyan; }
    }
&lt;/style&gt;
    </pre></div>
    <p>接着是 SVG 标签结构性代码:</p>
    <div class="hEdiv"><pre class="hEpre">
&lt;svg id="msvg" width="200" height="200" viewBox="-100 -100 200 200" xmlns="http://www.w3.org/2000/svg"&gt;
    &lt;circle x="0" y="0" r="90"&gt;&lt;/circle&gt;
&lt;/svg&gt;
    </pre></div>
    <p>OK,两步走完“绘制”过程。</p>
    <p>手动计算描边路径总长度可能会很麻烦,尤其是当图形路径复杂的时候。解决计算痛点可以走第三步:用 JS 辅助计算 SVG 内部图形元素的路径长度,使用 svgelement.getTotalLength() 方法:</p>
    <div class="hEdiv"><pre class="hEpre">
// 获取circle元素
const circle = document.querySelector('#msvg circle');
// 获取circle描边路径总长度
let len = circle.getTotalLength();

// 设置对应的属性值(对应的CSS属性设置要删掉)
circle.style.cssText += `
    stroke-dasharray: ${len};
    stroke-dashoffset: ${len};
`;
    </pre></div>
    <p>需要处理绘制流程的图形元素不论有多少个,均可参照上述原理加以批量设置,无需手动在CSS中设置描边虚线相关属性值。</p>
    <p>上述总体实现方法通过 CSS 达成,也可以基于 SVG 自有的动画方式去完成。从提升效率和简化手段考虑,CSS方法更为划算。</p>
    <h2>应用实例</h2>
    <p>最后给出一个使用 svgdr 插件绘制的旋转复合路径图案,N多个图案依次描边、着色,完成后整体运行旋转动画(可点击代码框右上角预览按钮查看运行效果):</p>
    <div class="hEdiv" data-prev="1"><pre class="hEpre">
&lt;style&gt;
    #msvg { display: block; margin: 20px auto; }
    #msvg path {
      stroke-dasharray: var(--len);
      stroke-dashoffset: var(--len);
      animation: draw 2s var(--delay) forwards, fill 1s calc(var(--delay) + 2s) forwards;
    }
    .rotate { animation: rotate 8s 0.5s linear infinite; }
    @keyframes rotate { to { transform: rotate(360deg); } }
    @keyframes draw { to { stroke-dashoffset: 0; } }
    @keyframes fill { to { fill: rebeccapurple; } }
&lt;/style&gt;

&lt;svg id="msvg" width="400" height="400" xmlns="http://www.w3.org/2000/svg" viewBox="-200 -200 400 400"&gt;
    &lt;g id="g1"&gt;&lt;/g1&gt;
&lt;/svg&gt;

&lt;script src="https://638183.freep.cn/638183/svgdr/svgdr.min.js"&gt;&lt;/script&gt;
&lt;script&gt;
    (function tzRun() {
      var dr = _dr(msvg);
      dr.path('M0 0 L-40 -190Q0 -100,40 -190 L0 0', 'none', 'purple', 3).addTo(g1).rotates(9);

      setProperties();

      function setProperties() {
            const paths = msvg.querySelectorAll('path');
            paths.forEach((path, idx) =&gt; {
                path.style.cssText += `
                  --len: ${path.getTotalLength()};
                  --delay: ${idx * 2}s;
                `;
            });
            paths.onanimationend = () =&gt; {
                g1.classList.add('rotate');
            };
      }
    })();
&lt;/script&gt;
    </pre></div>
</div>

<script type="module">
    import hl from 'https://638183.freep.cn/638183/web/js/hl.js';
   
    var divs = document.querySelectorAll('.hEdiv');
    var pres = document.querySelectorAll('.hEpre');
    divs.forEach((div,idx) => hl.hl(div, pres));
   
    function showDrawing() {
      var circle = document.querySelector('#msvg circle');
      circle.style.animation = 'none';
      setTimeout(() => {
            circle.style.animation = 'stroking 4s linear forwards, filling 2s 4s linear forwards';
      }, 100);
    }
   
    window.showDrawing = showDrawing;
</script>
页: [1]
查看完整版本: 马黑黑老师《一文讲清 SVG 动态绘图 》