HTML5 SVG实践(二):栅格动画

Rykka at 2014/09/12

本文地址:http://rykka.me/html5_svg_animation.html

本文主要演示了如何通过Snap.SVG创造一组栅格动画。

首先演示了如何用Snap.SVG创造元素, 然后说明了如何使设置元素的属性。 之后让一组元素平滑的进行动画, 最后使这组动画无限循环。

点击预览最终 Demo

1.   什么是Snap.SVG?

Snap.svg 是Adobe的一个开源JavaScript库, 用来更方便的控制SVG元素,就像用jQuery控制DOM元素一样。

在使用前,我们需要在官网上下载相对应的js文件。

这里只对功能做大致描述。 具体文档见 http://snapsvg.io/docs/

也可以查看 张鑫旭同学的中文文档

2.   Snap a SVG

通过Snap.svg,我们可以Snap一个SVG元素:

var svg = Snap(width, height) ;

得到的svg元素则被Snap化了,使得我们更方便进行操控。 我们也可以像jQuery一样,直接选择一个SVG元素:

var svg = Snap("#svg-elem") ;

首先我们在页面的showSVG的div中创建一个SVG元素:

var showSVG = document.querySelector(".showSVG");
var svg = Snap(100, 100) ;
showSVG.append(svg.node);

接下来我们就可以对其中的元素进行控制了。

3.   控制SVG

3.1.   添加SVG

添加一个元素, 比如 svg.paper.rect(x, y, h, w, [rx], [ry]):

var r1 = svg.paper.rect(0,0,20,100);

相当于在svg中插入一个rect:

<rect x=0,y=0,height='20',width=100></rect>

返回一个snap元素。

3.2.   更改属性

我们可以直接对得到的snap元素进行赋值:

r1.attr({fill:'#f33'});

则相当于为r1添加相应属性。

3.3.   进行动画

更重要的是,我们可以对元素直接赋予动画, 这样就避免了书写复杂的SVG动画。

比如:

r1.animate({x:100},1000);

表示在1000ms的持续时间内, 将r1的x坐标移动到100。

Snap本身也有一个动画函数,可以更平滑的控制动画

Snap.animate(from, to, setter, duration, [easing], [callback]):

Snap.animate(0,100,function(val){
    r1.attr({x:val})
}, 1000)

该动画效果类似上一个效果。 但是我们可以同时对多个元素和属性进行同步设置。

4.   让一组元素平滑动画

让一组元素平滑的进行动画,则需要我们对一组元素按顺序进行控制。

4.1.   创建一组元素

首先我们创建一组元素,代码如下

var g = svg.paper.g();
var c = Array.apply(null, {length: 20})
    .map(Number.call, Number)
    .map(function(num){
        var _t = svg.paper.rect((num-1)*10, 0,
                9, 100).attr({fill:'#b00'});
        g.add(_t);
        return _t;
    });
var s = g.selectAll('rect');

这里我们创建了一个包含20个元素的数组c, 里面每一个元素的x坐标分别增加10。

并将这些元素在SVG里面用Paper.g()进行分组。 最后将这些元素放到一个s的Snap.set里面,方便进行控制。

4.2.   让这组元素动起来

首先我们可以想到的,就是用 Snap.animate()

我们可以先添加元素到一个Snap.set组里面, 然后在进行动画时,在setter函数里逐个对set元素组里的的attr进行设置。

Snap.animate(0,100,function(val){
    s.forEach(function(elem,i){
        elem.attr({y : val/10+i*10});
    });
},1000)

但是这样有几个弊端:

  1. 我们很难设置其他属性,比如颜色的变化。
  2. 每一个元素不能设置相应的easing特效。比如有的元素用bounce,有的用ease-in.
  3. 很难使用路径。

那么,我们想这样,对组里的每一个元素 分时段分别开始进行animate。

即类似以下伪代码:

for i in [i1,i2,i3]
    i animate
    wait 500
endfor

那么我们需要实现一个简单的 step_animate。

代码如下:

// animate by step
// steps is the total number of step,
// after init timeout
// run step_func with each step.
// call the callback after finished.
function step_animate(init_timeout, steps, total_time, step_func, callback){
    setTimeout(function(){
        var intervalId = setInterval(step_func, total_time/steps);
        setTimeout(function(){
            clearInterval(intervalId);
            if (callback != null) {
                callback();
            }
        }, total_time);
    },init_timeout)
}

注:不用Snap.animate进行step的设置,而是直接用setInterval, 因为Snap.animate有时运行会顿一下,然后Step就会出现间隔。

直接使用即可:

var k = 0;
step_animate(0,20, 2000, function(){
    c[k].animate({y:300},1500);
    c[k].animate({fill:'#3800BA'},1500);
    k++;
})

测试代码,则可以看到可以运行了。

5.   循环动画

最后一个问题。 怎么让动画无限循环呢?

我们可以通过将step_animate函数包裹在一个Wrapper Function里面, 并在 callback里面递归调用Wrapper Function来实现:

function Anim() {
     var k = 0 ;
     s.attr({y:0});
     s.attr({fill:'#c00'});
     step_animate(0,20, 3000, function(){
         c[k].animate({y:300},1500);
         c[k].animate({fill:'#3800BA'},1500);
         k++;
     },
     function(){
         setTimeout(Anim, 1600);
     })
 }
 Anim();

需要注意的是,我们必须在step_animate开始前对相应属性初始化。 同时setTimeout应该在所有子元素的动画都完成之后再开始。

而如果我们要加入多重动画的话,则我们必须嵌套多层step_animation。

最后再进行了一些调整和内容的丰富, 预览见 Demo

本文完