SVG学习笔记(3)动画、Snap.svg、Svg.js

首先,推荐看看《三看 SVG Web 动效》《SVG+JS path等值变化实现CSS3兴叹的图形动画》,看后受益匪浅。

然后,《使用 Snap.svg 制作动画》可以帮助我们快速入门Snap.svg

另外,svg.js也是非常强大的库,但我个人不是很习惯它的方法拆分方式,需要经常链式调用多个方法来执行一个简单动作。相比之下,Snap.svg 倾向于用 json 作参数,Snap.svg 更近似 jQuery。

由于 Snap.svg 0.5.0 和 0.5.1 出现旋转中心无效的 BUG,0.5.2 得到修复,但在这个时间点所有cdn都没有0.5.2的资源,很是无奈,不想将就而用0.4.1,于是改用 svg.js,用起来感觉棒棒的,后面有时间再另开一篇展开来说


基础理论

SVG的动画跟传统动效原理是一样的,都是靠改变元素样式并创建补间来完成。过程的驱动形式主要包括:

  • css3驱动,简洁快捷,局限于图形整体动画
  • js驱动,更多细节可控,可以实现很多让css3兴叹的动画效果
  • smil驱动,不支持IE是一个问题,并且需要用标签来写动画,等于把动画写在html文件里,不符合维护方便的规范,接下来就不讨论它了

由css3驱动的svg动画

  • 尝试1:transform / keyframes / 伪类 / 整体 / 部分

    QQ20180221-181710-HD

    如上图,svg包含一个红色圆,一个半透明的黑色箭头,和一条不断旋转的直线。作为学习中的尝试,我希望同时尝试多种可能的玩法。其中直线通过transition配合keyframes实现无限旋转的效果;通过:hover伪类触发transform变化,另外,我把scale放在<svg>上,rotate放在<polygon>上,效果符合预期。

    jsfiddle 源码

    html:

      <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="300" height="300" id="Demo">
        <circle cx="150" cy="150" r="100"/>
        <polygon points="110,110 150,80 190,110 190,210 150,150 110,210"/>
        <path d="M 130,225 h40"/>
      </svg>
    

    scss:

      @keyframes pathani{
        100%{
          transform:rotate(360deg);
        }
      }
      #Demo{
        transform-origin:50%;
        transition:all 0.4s;
        circle{
          fill:#dd4444;
          stroke-width:0;
          stroke:#ffff00;
          transition:all 0.4s;
        }
        polygon{
          fill:#000000;
          fill-opacity:0.6;
          stroke:#ffffff;
          stroke-width:4;
          transform-origin:50%;
          transition:all 0.3s ease 0.1s;
        }
        path{
          stroke-width:2;
          stroke:#ffff00;
          transform-origin:150px 225px;
          animation: pathani 1s linear 0s infinite;
        }
        &:hover{
          transform:scale(1.3);
          polygon{
            fill-opacity:1;
            transform:rotate(90deg);
          }
        }
      }
    
  • 尝试2:借助stroke-dasharraystroke-dashoffset做出描边动效

    QQ20180221-212724-HD

    如上图,该曲线由9段三次贝塞尔曲线连接成,总长1400,通过stroke-dasharray值可以让空白长度达到曲线总长、然后创建stroke-dashoffset值改变的补间即可实现描边动效

    jsfiddle 源码

    html:

      <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="500" height="500" id="Demo">
        <path d="M 128,159 C 89,71 -65,389 191,203 C 155,263 242,261 250,225 C 265,153 177,193 206,198 C 263,205 269,197 272,197 C 256,251 310,265 336,223 C 374,153 259,177 281,195 C 300,210 429,136 408,136 C 387,136 378,238 354,257 C 299,252 436,266 452,250"/>
      </svg>
    

    scss:

      @keyframes ani{
        100%{
          stroke-dashoffset:0;
        }
      }
      #Demo{
        path{
          fill:transparent;
          stroke:#000000;
          stroke-width:3;
          stroke-dasharray:1400;
          stroke-dashoffset:1400;
          animation:ani 2s ease-out 0s infinite;
        }
      }
    
  • 尝试3:借助css3的offset-path实现图形沿路径移动

    QQ20180222-140423-HD

    如上图,我尝试用一个红色背景和一个白色光点组成一个图案pattern,然后填充在一个圆circle里,最后让圆沿着几波曲线进行移动。基本效果实验成功,但无法实现球的转动,消耗了很多时间,无论使用offset-anchor或是transform-origin,都无法让旋转中心定位在圆心上。。。

    jsfiddle 源码

    html:

      <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="500" height="500">
        <defs>
          <pattern id="PointLight" x="0" y="0" width="1" height="1" patternContentUnits="objectBoundingBox">
            <rect x="0" y="0" width="1" height="1" fill="#dd4444"/>
            <circle cx="0.65" cy="0.3" r="0.13" fill="#ffffff"/>
          </pattern>
        </defs>
        <path id="Pipe" d="M 22,40 C 46,86 74,216 152,114 C 216,36 258,138 258,138 C 287,194 334,141 390,156"/>
        <circle id="Ball" cx="22" cy="40" r="10"/>
      </svg>
    

    scss:

      @keyframes ballmove{
        0%{
          offset-distance:0%;
        }
        42%{
          offset-distance:42%;
        }
        65%{
          offset-distance:54%;
        }
        100%{
          offset-distance:100%;
        }
      }
      #Pipe{
        fill:transparent;
        stroke:#eeeeee;
        stroke-width:24;
        stroke-linecap:square;
      }
      #Ball{
        fill:url(#PointLight);
        offset-path:path('M 0,0 C 24,46 52,176 130,74 C 198,-4 236,98 236,98 C 265,154 312,101 368,116');
        offset-rotate:0deg;
        animation:ballmove 1.5s linear 1s infinite;
      }
    

由js驱动的svg动画

既然 Snap.svg 和 svg.js 已经被公认是优秀的库,js驱动的尝试我们就不走弯路了!上文提到 Snap.svg 目前有 BUG,故使用 svg.js 完成实例学习

  • 尝试1:用js驱动实现与上文css驱动的《尝试1》一样的效果

    QQ20180221-181710-HD

    html 代码中只用一个 div 作为 svg.js 的绘制入口,由于<svg>不支持非css的transform,所以加了一个<g>来组合所有元素,这一点与css驱动不一样。具体请看代码,显然,这种整体的缩放旋转类动效,用css驱动要比js驱动简便得多

    jsfiddle 源码

    html:

      <div id="Demo"></div>
    

    js:

      var draw = SVG('Demo').size(300, 300);
      var group = draw.group();
      var circle = group
        .circle(200)
        .move(50, 50)
        .attr({
          fill: '#dd4444'
        });
      var polygon = group
          .polygon('110,110 150,80 190,110 190,210 150,150 110,210')
        .attr({
          fill: '#000000',
          'fill-opacity': 0.6,
          stroke: '#ffffff',
          'stroke-width': 4
        });
      var line = group
          .line(130, 225, 170, 225)
        .attr({
          stroke: '#ffff00',
          'stroke-width': 2
        })
        .animate(1000)
        .transform({
          rotation: 360
        })
        .loop();
      group.on('mouseenter', function () {
          group.animate(400).transform({
          scale: 1.3
        });
        polygon.animate(300, '<>', 100).transform({
          rotation: 90
        }).attr({
          'fill-opacity':1
        });
      }).on('mouseleave', function () {
          group.animate(400).transform({
          scale: 1
        });
        polygon.animate(300, '<>', 100).transform({
          rotation: 0
        }).attr({
          'fill-opacity':0.6
        });
      });
    
  • 尝试2:实现形状的变化,这是css驱动无法实现的效果

    svg.js 只支持相同 path 命令顺序的变换,所以这个例子做起来有点麻烦,如果需要自动插入删除命令,引用 svg.pathmorphing.js 可实现

    QQ20180223-104506-HD

    jsfiddle 源码

    html:

      <div id="Demo"></div>
    

    js:

      var draw = SVG('Demo').size(500, 500);
    
      var path = draw
      .path('M 128,159 C 89,71 -65,389 191,203 C 155,263 242,261 250,225 C 265,153 177,193 206,198 C 263,205 269,197 272,197 C 256,251 310,265 336,223 C 374,153 259,177 281,195 C 300,210 429,136 408,136 C 387,136 378,238 354,257 C 299,252 436,266 452,250')
      .attr({
          fill: 'none',
        stroke: '#dd4444',
        'stroke-width': 3
      });
    
      var btn = draw.group().style('cursor', 'pointer');
      btn
      .rect(100, 30)
      .move(200, 20)
      .attr({
          fill:'#f5f5f5',
        stroke:'#dddddd',
        'stroke-width':1
      });
      var btnText = btn
      .text('变直线')
      .move(250, 26)
      .font({
          size:16,
        anchor:'middle'
      })
      .attr({
          fill:'#333333'
      });
    
      btn.click(function () {
          if (btnText.text() == '变直线') {
              path.animate(500).plot('M 49,209 C 89,209 89,209 191,209 C 191,209 242,209 250,209 C 265,209 280,209 280,209 C 290,209 300,209 300,209 C 300,209 310,209 336,209 C 374,209 390,209 390,209 C 390,209 429,209 439,209 C 439,209 439,209 439,209 C 439,209 452,209 452,209');
          btnText.text('变CooL');
        } else {
              path.animate(500).plot('M 128,159 C 89,71 -65,389 191,203 C 155,263 242,261 250,225 C 265,153 177,193 206,198 C 263,205 269,197 272,197 C 256,251 310,265 336,223 C 374,153 259,177 281,195 C 300,210 429,136 408,136 C 387,136 378,238 354,257 C 299,252 436,266 452,250');
          btnText.text('变直线');
        }
      });
    

    下面是一个引用svg.pathmorphing.js的例子,由于找不到该扩展的cdn,只能放在 js 代码当中,jsfiddle 上耗时有点过分

    QQ20180223-112540-HD

    jsfiddle 源码

  • 尝试3:用js驱动实现与上文css驱动的《尝试3》一样的效果

    QQ20180223-230948-HD

    使用js终于可以让球滚动了,由于svg的旋转是以0,0为中心,起初操作和理解都不那么容易,过程不顺利,不过最终也算是摆平了,svg.js十分强大!

    jsfiddle 源码

    html:

      <div id="Demo"></div>
    

    js:

      var draw = SVG('Demo').size(500, 500);
      var pipe = draw
      .path('M 22,40 C 46,86 74,216 152,114 C 216,36 258,138 258,138 C 287,194 334,141 390,156')
      .attr({
          fill:'none',
        stroke:'#eeeeee',
        'stroke-width':24,
        'stroke-linecap':'square'
      });
      var ball = draw.group();
      ball
      .circle(20)
      .fill('#dd4444')
      .center(10, 10);
      ball
      .circle(5.2)
      .fill('#ffffff')
      .center(13, 6);
      ball.center(22, 40);
      var pipeLength = pipe.length();
      var finalDeg = pipeLength / (3.14 * 20) * 360;
      ball.animate(5000, '<', 1000).during(function (pos, morph, eased) {
        var p = pipe.pointAt(eased * pipeLength);
        ball
          .untransform()
          .center(p.x, p.y)
          .rotate(eased * finalDeg)
      }).loop();
    

实践

SVG笔记到此告一段落,开始下一个学习任务之前,准备对个人网站加入一点SVG元素,做一点象征性改变,嘻嘻

若您觉得我的博文对您有帮助,欢迎点击下方按钮对我打赏
打赏