svg之animate - bobby169/Snap.svgDoc GitHub Wiki

[TOC]

svg 动画

动画基础

示例:收缩矩形

http://oreillymedia.github.io/svg-essentials-examples/ch12/simple_animation.html

<rect x="10" y="10" width="200" height="20" stroke="black" fill="none">
  <animate id="animation"
    attributeName="width"
    attributeType="XML"
    from="200" to="20"
    begin="0s" dur="5s"
    fill="freeze" />
</rect>

<animate> 元素指定了下列信息。

  • attributeName,动画中应该持续改变的值;在这里就是 width。
  • attributeTypewidth 属 性 是 一 个 XML 属 性。 另 一 个 常 用 的 attributeType 值 是 CSS,表示我们想要改变的属性是一个 CSS 属性。如果忽略这一属性,它的默认值是 auto;它首先会搜索 CSS 属性,然后才是 XML 属性。
  • 属性的起始(from)和结束(to)值。在这个例子中,起始值是 200,结束值是 20。from 值是可选的;如果不指定,则会使用父元素的值。此外,还有一个 by 属性,可以代替 to,它是一个从 from值开始的偏移量;动画结束时属性的值为结束值。
  • 动画的开始时间和持续时间。在这个例子中,时间以秒为单位,通过在数字后面使用 s 指定。
  • 动画结束时做什么。在这个例子中,持续 5 秒之后,属性会“冻结”(freeze)为 to 值。也就是 SMIL fill 属性,它会告诉动画引擎如何填补剩下的时间。不要把它跟 SVGfill 属性混淆了,该属性用于告诉 SVG 如何描绘对象。如果我们移除这一行,会使用 默认值(remove),5 秒的动画完成之后 width 属性会返回它的原始值 200。

示例:单个对象上的多重动画

http://oreillymedia.github.io/svg-essentials-examples/ch12/multiple_animation.html

<svg width="275" height="275">
  <rect x="10" y="10" width="20" height="20"
    style="stroke: black; fill: green; style: fill-opacity: 0.25;">
  <animate attributeName="width" attributeType="XML"
    from="20" to="200" begin="0s" dur="8s" fill="freeze"/>
  <animate attributeName="height" attributeType="XML"
    from="20" to="150" begin="0s" dur="8s" fill="freeze"/>
  <animate attributeName="fill-opacity" attributeType="CSS"
    from="0.25" to="1" begin="0s" dur="3s" fill="freeze"/>
  <animate attributeName="fill-opacity" attributeType="CSS"
    from="1" to="0.25" begin="3s" dur="3s" fill="freeze"/>
</rect>
</svg

上面示例更复杂一些。它从一个 20 乘 20 的绿色方块开始,将在 8 秒的时间里增长为 250 乘 200。前 3 秒,绿色的透明度会增加,接下来 3 秒会减小。注意 fill-opacity 是使用 attributeType="CSS" 的,因为它设置在 style 属性中。

示例:多个对象的简单动画

http://oreillymedia.github.io/svg-essentials-examples/ch12/multiple_animation2.html

<svg width="200" height="200">
  <rect x="10" y="10" width="20" height="20"
    style="stroke: black; fill: #cfc;">
    <animate attributeName="width" attributeType="XML"
      begin="0s" dur="8s" from="20" to="120" fill="freeze"/>
    <animate attributeName="height" attributeType="XML"
      begin="0s" dur="8s" from="20" to="120" fill="freeze"/>
  </rect>

  <circle cx="70" cy="70" r="20"
    style="fill: #ccf; stroke: black;">
    <animate attributeName="r" attributeType="XML"
      begin="2s" dur="4s" from="20" to="50" fill="freeze"/>
  </circle>
</svg>

上面示例是:一个正方形和圆形动画。正方形尺寸会在 8 秒的时间里 从 20 乘 20 扩大到 120 乘 120。动画开始 2 秒之后,圆形的半径在 4 秒的时间里从 20 扩大 到 50

动画时间详解

SVG 动画用到的动画时钟在 SVG 加载完成时开始启动计时,当用户离开页面时停止计时。 我们可以以下列任意一种方式指定特定动画的开始和持续时间为一个数值。

  • 时、分、秒形式(1:20:23)的完整时间值。
  • 分、秒形式(02:15)的局部时间值。
  • 以 h(时)、min(分)、s(秒)或者 ms(毫秒)缩写结尾的时间值,比如 dur="3.5s" begin="1min"。不可以在值和单位之间插入任何空白。

如果不指定单位,默认为秒。

同步动画

我们可以绑定动画的开始时间为另一个动画的开始或者结束,而不是在文档加载时定义每个动画的开始时间。

示例:同步动画

<svg width="200" height="200">
    <circle cx="60" cy="60" r="30" style="fill: #f9f; stroke: gray;"> 
        <animate id="c1" attributeName="r" attributeType="XML"
begin="0s" dur="4s" from="30" to="10" fill="freeze"/> 
    </circle>
    <circle cx="120" cy="60" r="10" style="fill: #9f9; stroke: gray;">
        <animate attributeName="r" attributeType="XML"
begin="c1.end" dur="4s" from="10" to="30" fill="freeze"/> 
    </circle>
</svg>

示例:带有偏移量的同步动画

http://oreillymedia.github.io/svg-essentials-examples/ch12/sync_with_offset.html

<svg width="200" height="200">
  <circle cx="60" cy="60" r="30" style="fill: #f9f; stroke: gray;">
    <animate id="c1" attributeName="r" attributeType="XML"
      begin="0s" dur="4s" from="30" to="10" fill="freeze"/>
  </circle>

  <circle cx="120" cy="60" r="10" style="fill: #9f9; stroke: gray;">
    <animate attributeName="r" attributeType="XML"
      begin="c1.begin+1.25s" dur="4s" from="10" to="30" fill="freeze"/>
  </circle>
</svg>

下面这个动画会在页面载入 6 秒之后开始。它会持续 12 秒或者直到名为 otherAnim 的动画结束,这取决于是动画先结束还是 12 秒先到:

<animate attributeName="width" attributeType="XML" begin="6s" dur="12s" end="otherAnim.end" from="10" to="100" fill="freeze"/>

当然,我们也可以设置 end 的值为一个指定的时间;这可以用于在中途拦截动画,这样我 们就可以看看是否一切都在正确的位置。下面这个动画在 5 秒时开始,并且应该持续 10 秒,但是会在文档载入后 9 秒(动画开始 4 秒后)暂停。这 个动画会停止在 40% 处,因此宽度会冻结在 140(从 100 到 200 的 40% 的距离)。

<animate attributeName="width" attributeType="XML" begin="5s" dur="10s" end="9s" from="100" to="200" fill="freeze"/>

重复动作

到目前为止,动画都只发生一次;fill 被设置为 freeze 使动画保持在最后阶段。如果想 要对象返回到动画起始状态,忽略这个属性即可(等价于设置 fill 为默认值 remove)。

  • repeatCount 设置一个整型值,告诉引擎我们想要将指定的动画重复多少次。
  • repeatDur,设置一个值,告诉引擎重复应该 持续多长时间。

示例:重复动画

http://oreillymedia.github.io/svg-essentials-examples/ch12/repeated_action.html

<svg width="350" height="120">
  <circle cx="60" cy="60" r="30" style="fill: none; stroke: red;">
    <animate attributeName="cx" attributeType="XML"
      begin="0s" dur="5s" repeatCount="2"
      from="60" to="260" fill="freeze"/>
  </circle>

  <circle cx="260" cy="90" r="30" style="fill: #ccf; stroke: black;">
    <animate attributeName="cx" attributeType="XML"
      begin="0s" dur="5s" repeatDur="8s"
      from="260" to="60" fill="freeze"/>
  </circle>
</svg>

对复杂的属性应用动画

示例:颜色动画

http://oreillymedia.github.io/svg-essentials-examples/ch12/animate_color.html

<svg width="150" height="100">
    <circle cx="60" cy="60" r="30"
      style="fill: #ff9; stroke: gray; stroke-width: 10;">
      <animate attributeName="fill"
        begin="2s" dur="4s" from="#ff9" to="red" fill="freeze"/>
      <animate attributeName="stroke"
        begin="2s" dur="4s" from="gray" to="blue" fill="freeze"/>
    </circle>
</svg>

示例:路径和多边形动画

http://oreillymedia.github.io/svg-essentials-examples/ch12/animate_path_poly.html

<svg width="250" height="250"
  xmlns="http://www.w3.org/2000/svg">
  <polygon points="30 30 70 30 90 70 10 70"
    style="fill:#fcc; stroke:black">
    <animate id="animation"
      attributeName="points"
      attributeType="XML"
      to="50 30 70 50 50 90 30 50"
      begin="0s" dur="5s" fill="freeze" />
  </polygon>

  <path d="M15 50 Q 40 15, 50 50, 65 32, 100 40"
    style="fill:none; stroke: black" transform="translate(0,50)">
    <animate attributeName="d"
      attributeType="XML"
      to="M50 15 Q 15 40, 50 50, 32 65, 40 100"
      begin="0s" dur="5s" fill="freeze"/>
  </path>
</svg>

我们可以为路径数据或者多边形的点应用动画,只要我们维持点的数量和路径片段的类型即可

指定多个值

示例:使用特定的值动画绘制颜色

目前为止呈现的所有动画元素都提供了一个起始(from 或默认)值和结束(to)值,然后让计算机计算如何从起始值到结束值。我们还可以给动画提供一个特定的中间值,从而允 许用一个独立的 <animate> 元素定义复杂的变化序列。我们可以提供一个用分号分隔的、动画在持续时间内使用的值列表

http://oreillymedia.github.io/svg-essentials-examples/ch12/animating_values.html

<svg width="100" height="100"
  xmlns="http://www.w3.org/2000/svg">
  <circle cx="50" cy="50" r="30"
    style="fill: #ff9; stroke:black;">
    <animate attributeName="fill"
      begin="2s" dur="4s" values="#ff9;#99f;#f99;#9f9"
      fill="freeze"/>
  </circle>
</svg>

<set>元素

所有这些动画归根结底都是修改值。有时候,特别是对于非数字属性或者不能过渡的属性,我们可能想要在动画序列的某个点上改变某个值。 比如,我们可能想要一个初始不可见的文本项,使它在某个时间变得可见;这里并不需要 fromto。因此,SVG 引入了方便速记的 <set> 元素,它只需要一个 to 属性以及适当的时间信息。

示例:<set>元素

http://oreillymedia.github.io/svg-essentials-examples/ch12/animation_set.html

<svg width="150" height="100">
    <circle cx="60" cy="60" r="30" style="fill: #ff9; stroke: gray;">
      <animate id="c1" attributeName="r" attributeType="XML"
        begin="0s" dur="4s" from="30" to="0" fill="freeze"/>
    </circle>
    
    <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>
</svg>

<animateTransform>元素

<animate> 元素并不适用于旋转、平移、缩放或倾斜变换,因为它们都“被包裹”在 transform 属性内。<animateTransform> 元素就是用来解决这个问题的。我们可以设置 它的 attributeNametransform。然后用 type 属性的值指定变换的哪个值应该变化 (translatescalerotateskewX 或者 skewY 之一)。fromto 的值指定为适当的要动画绘制的变换。撰写本文时,大多数实现当前都只支持在 XML transform 属性上使用 <animateTransform> 而不支持 CSS3 变换。

示例:<animateTransform>

http://oreillymedia.github.io/svg-essentials-examples/ch12/animate_transform.html

<svg width="200" height="100">
  <g transform="translate(100,60)">
  <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>
  </g>
</svg>

<animateMotion>元素

如果想要对直线运动使用 <animateMotion>,简单地设置 fromto 属性,然后给每个属 性分配一对 (x, y) 坐标即可。这个坐标用于指定要移动形状的坐标系统中 (0,0) 点的位置, 类似于translate(x, y)的工作原理。

示例:线性路径动画

http://oreillymedia.github.io/svg-essentials-examples/ch12/linear_animateMotion.html

<svg width="120" height="100" viewBox="0 0 120 100">
<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>

示例:沿复杂的路径运动

http://oreillymedia.github.io/svg-essentials-examples/ch12/complex_animate_motion.html

<svg width="250" height="250">
    <!-- show the path along which the triangle will move -->
    <path d="M50,125 C 100,25 150,225, 200, 125"
      style="fill: none; stroke: blue;"/>
    
    <!-- Triangle to be moved along the motion path.
       It is defined with an upright orientation with the base of
       the triangle centered horizontally just above the origin. -->
    <path d="M-10,-3 L10,-3 L0,-25z" style="fill: yellow; stroke: red;">
      <animateMotion
        path="M50,125 C 100,25 150,225, 200, 125"
        dur="6s" fill="freeze"/>
    </path>
</svg>

示例:自动旋转的复杂路径动画

http://oreillymedia.github.io/svg-essentials-examples/ch12/animate_motion_rotate.html

<svg width="250" height="250">
<!-- show the path along which the triangle will move -->
<path d="M50,125 C 100,25 150,225, 200, 125"
  style="fill: none; stroke: blue;"/>

<!-- Triangle to be moved along the motion path.
   It is defined with an upright orientation with the base of
   the triangle centered horizontally just above the origin. -->
<path d="M-10,-3 L10,-3 L0,-25z" style="fill: yellow; stroke: red;" >
  <animateMotion
    path="M50,125 C 100,25 150,225, 200, 125"
    rotate="auto"
    dur="6s" fill="freeze"/>
</path>
</svg>

如果希望对象倾斜以使其 x 轴始终平行于路径的方向,只要给 <animateMotion> 元素添加 一个值为 autorotate 属性就行了

当我们不使用 rotate 属性时,其默认值为 0,此时对象就像一个沿着路径浮动 的热气球。如果设置 rotateauto,对象就像坐过山车一样,沿着路行向上或向下倾斜。 我们也可以设置 roate 为一个数值,这会使对象在动画中旋转。因此,如果希望无论路径是什么方向,对象都旋转 45 度,就使用 rotate="45"

示例:使用<mpath>定义沿着复杂路径运动的动画

<svg width="250" height="250">
  <path id="cubicCurve" d="M50,125 C 100,25 150,225, 200, 125" style="fill: none; stroke: blue;"/>
  <path d="M-10,-3 L10,-3 L0,-25z" style="fill: yellow; stroke: red;" >
    <animateMotion rotate="auto" dur="6s" fill="freeze">
      <mpath xlink:href="#cubicCurve"/>
    </animateMotion>
  </path>
</svg>

使用CSS处理SVG动画

http://oreillymedia.github.io/svg-essentials-examples/ch12/svg_css_anim2.html

<svg width="200" height="200" viewBox="0 0 200 200"> 
  <defs>
    <g id="starDef">
      <path d="M 38.042 -12.361 9.405 -12.944 -0.000 -40.000
             -9.405 -12.944 -38.042 -12.361 -15.217 4.944
-23.511 32.361 0.000 16.000 23.511 32.361 15.217 4.944 Z"/>
    </g>
  </defs>
  <use id="star" class="starStyle" xlink:href="#starDef"
       transform="translate(100, 100)"
       style="fill: #008000; stroke: #008000"/> 
</svg>
.starStyle {
  animation-name: starAnim;
  animation-duration: 2s;
  animation-iteration-count: 4;
  animation-direction: alternate;
  animation-timing-function: ease;
  animation-play-state: running;
}

@keyframes starAnim  {
  0% {
    fill-opacity: 1.0;
    stroke-width: 0;
  }

  20% {
    transform: translate(100px, 50px)
  }

  50% {
    transform: translate(100px, 50px) rotate(180deg)
  }

  80% {
    transform: translate(100px, 50px) rotate(360deg)
  }

  100% {
    fill-opacity: 0.0;
    stroke-width: 6;
  }
}

描边动画

<style>
    .fill{
        fill: blue;
        fill-opacity: 0.5;
    }
    .an {
        stroke-width:3;
        stroke:red;
        stroke-linecap: round;
        animation: lineMove 3s ease-out infinite;
    }

    @keyframes lineMove {
        0% {
            stroke-dasharray: 0, 740;
        }
        100% {
            stroke-dasharray: 740, 740;
            opacity: 1;
        }
    }
</style>

<svg width="580" height="400" xmlns="http://www.w3.org/2000/svg">
    <path class="an fill" id="svg_1" d="m63.5,128.4375c56,-58 98,18.125 115.5,17.5625c17.5,-0.5625 41.5,-74.5625 103.5,-27.5625c62,47 -86,194 -104,194c-18,0 -171,-126 -115,-184z" stroke-width="1.5" stroke="#000" fill="#fff"/>
</svg>

<script>
    let obj = document.querySelector("#svg_1");
    let length = obj.getTotalLength();

    console.log(length); // 740
</script>

仪表盘形进度条

<svg width="100" height="100" viewBox="0 0 100 100">
  <path d="
        M 50 50
        m 0 47
        a 47 47 0 1 1 0 -94
        a 47 47 0 1 1 0 94
        " stroke="#e5e9f2" stroke-width="4.8" fill="none" class="el-progress-circle__track" style="stroke-dasharray: 221.482px, 295.31px; stroke-dashoffset: -36.9137px;"></path>
  <path d="
        M 50 50
        m 0 47
        a 47 47 0 1 1 0 -94
        a 47 47 0 1 1 0 94
        " stroke="#e6a23c" fill="none" stroke-linecap="round" stroke-width="4.8" id="svg_1"
        class="el-progress-circle__path"
        style="stroke-dasharray: 0px, 295.31px; stroke-dashoffset: -36.9137px; transition: stroke-dasharray 0.6s ease 0s, stroke 0.6s ease 0s;"></path>
</svg>
<button id="add">增加</button>
<script>
    let obj = document.querySelector("#svg_1")
    let length = obj.getTotalLength()
    length = 221.482
    let current = 0

    let add = document.getElementById('add')
    add.addEventListener('click', function () {
        current += 10
        let value = current * length * 0.01
        if(value > length) value = length
        obj.style.strokeDasharray = `${value}px ${length}px`
    })
</script>

Snap动画

Snap 的做动画主要有两种方式:

  • 使用 Element 里的 animate 方法,Element.animate(attrs, duration, [easing], [callback])
  • 使用 Snap 的静态方法,Snap.animate(from, to, setter, duration, [easing], [callback])
// 动画样例1
var svg = Snap('#svg');
svg.select('circle').animate({r: 100}, 1000, mina.easeout(), function() {
    console.log('animation end');
});

// 动画样例2
var svg = Snap('#svg');
var circle = svg.select('circle');
var rect = svg.select('rect');
Snap.animate(0, 100, function(val) {
    circle.attr({r: val});
    rect.attr({x: val});
}, 1000, mina.easeout(), function() {
    console.log('animation end');
});

动画的属性

  • 简单数值类,如坐标、宽高、opacity、大部分 Paper API 可配置的属性值,甚至滤镜相关的属性。如{x:100} -> {x:200}, {width:0} -> {width:100}
  • path 相关动画,如d属性(变形动画)、描边动画、路径跟随动画
  • matrix 类,放大缩小、位移、旋转等,和 CSS 的 transform 类似
  • 颜色类,颜色变换动画,如 fill、stroke 属性,如{fill:’#f00’} -> {fill:’#f0f’}
// 动画样例,颜色变化动画
var svg = Snap('#svg');
var circle = svg.paper.circle({cx: 100, cy: 100, r: 50, fill: '#f00'});
circle.animate({fill: '#00f'}, 1000, mina.easeout(), function() {
    console.log('animation end');
});

path 变形动画

path 的d属性在 Snap 的解析规则里可以通过一系列的数学运算,动画中通过插值,达到最终态的d值,不过中间的插值计算我们无法干预。

http://jdc.jd.com/demo/snapsvg/index.html#demo3

http://jdc.jd.com/demo/snapsvg/index.html#demo3-1

// 开始态
var path = svg.paper.path({d: 'M0.500,65.500 C18.680,33.758 45.141,-6.797 72.500,2.500 C99.859,11.797 72.148,59.027 79.500,98.500 C86.852,137.973 117.668,128.914 138.500,59.500 C159.332,-9.914 246.500,59.500 246.500,59.500 C273.181,117.750 137.350,184.417 225.500,173.500 C351.137,157.940 155.369,160.617 162.500,86.500 C165.180,58.645 237.169,-2.418 283.500,2.500 C357.654,10.371 363.758,80.355 364.500,109.500',stroke:'#f00', fill: 'rgba(0,0,0,0)'});
setTimeout(function() {
    // 终止态:曲线变直
    // path.animate({d: 'M1,100 L350,100'}, 1000, mina.easeout(), function() {
    //     console.log('animation end');
    // });
    // 终止态:心形
    path.animate({d: 'M114.500,58.500 C106.230,58.751 23.907,-37.262 5.500,21.500 C-26.759,124.483 111.761,221.360 119.500,219.500 C154.464,211.096 201.234,149.580 220.500,104.500 C250.260,34.864 220.892,7.159 194.500,1.500 C160.455,-5.800 122.344,58.262 114.500,58.500 Z'}, 1000, mina.easeout(), function() {
        console.log('animation end');
    });
}, 1000);

path 描边(dash)动画

path 路径跟随动画

http://jdc.jd.com/demo/snapsvg/index.html#demo5

var length = Snap.path.getTotalLength(path); // 获取path的长度
Snap.animate(0, length, function(val) {
    var point = Snap.path.getPointAtLength(path, val); // 根据path长度变化获取坐标
    var m = new Snap.Matrix();
    m.translate(point.x, point.y);
    m.rotate(point.alpha - 90); // 使飞机总是朝着曲线方向。point.alpha:点的切线和水平线形成的夹角
    plane.transform(m);
}, 30000, mina.easeout(), function() {
    console.log('animation end');
});

matrix动画

Snap 的 matrix 动画包含各位熟悉的 translate/scale/rotate/skew 动画,原理和 CSS 的 transform 也几乎一致。

参考

⚠️ **GitHub.com Fallback** ⚠️