首页 > 代码库 > ZRender实现粒子网格动画实战

ZRender实现粒子网格动画实战

注:本博文代码基于ZRender 3.4.3版本开发,对应版本库地址:ZRender 库。


效果

技术分享


实现分析

通过上面显示的效果图,可以看出,这种效果就是在Canvas中生成多个可移动的点,然后根据点之间的距离来确定是否连线,思路比较简单。

实现问题:

  • 保持Canvas 100%显示
  • resize时,自动调节Canvas尺寸和内部变量
  • 生成圆点
  • 实现圆点的移动,及边界处理
  • 实现原点的直线连接

Canvas设置

html:

<canvas id="main"></canvas>

css:

#main{
          position: absolute; //用于100%填充
          left:0;
          top:0;
          background: #000;
          z-index: -1; //方便做背景层使用
      }

ZRender部分

这里主要用到的形状就是CircleLine,先引入这两个组件:

[‘zrender‘,
    ‘zrender/graphic/shape/Circle‘,
    ‘zrender/graphic/shape/Line‘], 
function(zrender, Circle, Line){}

设置全局及配置项用到的变量

var winH = window.innerHeight; //同步页面宽、高
var winW = window.innerWidth; //同步页面宽、高

var opts = { //可配置参数
    background: ‘#000‘, //Canvas背景色
    paricalRadius: 2, //粒子半径
    paricalColor: ‘rgb(0, 255, 0)‘, //粒子颜色
    lineColor: ‘rgb(0, 255, 0)‘, //连线颜色
    joinLineDis: 300, //粒子间连线的要求距离
    particalAmount: 30, //生成的粒子数量
    speed: 1, //粒子速度
};
var tid; //setTimeout id,防抖处理
var particals = []; //用于存储partical对象

初始化ZRender

var zr= zrender.init(main, {width: winW, height: winH});

zr.dom.style.backgroundColor = opts.background; //设置背景色

窗口 resize 处理

window.addEventListener(‘resize‘, function(){
    clearTimeout(tid);

    var tid = setTimeout(function(){ //防抖处理
        winW = zr.dom.width = window.innerWidth;
        winH = zr.dom.height = window.innerHeight;

        zr.refresh();
    }, 300); //这里设置了300ms的防抖间隔
}, false);

效果:


技术分享

创建粒子类 Partical

总结一下这个类,需要以下属性:

  • 坐标位置 x, y
  • 粒子速度
  • 粒子移动角度
  • 粒子颜色
  • 粒子半径
  • 粒子的角度方向变量
  • 粒子的ZRender形状实例

方法:

  • 更新位置坐标
  • 划线

这边直接用ES6的语法来创建类:

class Partical {}

构造器:

constructor(){
    this.lines = [], //用于存储连线
    //粒子坐标初始化
    this.x = winW * Math.random();
    this.y = winH * Math.random();
    this.speed = opts.speed + Math.random(); //这个random可不加,主要是为了制作不同的速度的
    this.angle = ~~(360 * Math.random());
    this.color = opts.paricalColor;
    this.radius = opts.paricalRadius + Math.random();
    this.vector = {
        x: this.speed * Math.cos(this.angle),
        y: this.speed * Math.sin(this.angle),
    } 
    this.element = new Circle({
        shape: {
            cx: this.x,
            cy: this.y,
            r: this.radius,
        },
        style: {
            fill: this.color,
        }
    });
};

更新位置坐标方法:

updatePosition(){
    //边界判断
    if(this.x >= winW || this.x <= 0){
        this.vector.x *= -1;
    }

    if(this.y >= winH || this.y <= 0){
        this.vector.y *= -1;
    }

    if(this.x > winW){
        this.x = winW;
    }

    if(this.x < 0){
        this.x = 0;
    }

    if(this.y > winH){
        this.y = winH;
    }

    if(this.y < 0){
        this.y = 0;
    }

    //更新位置坐标
    this.x += this.vector.x;
    this.y += this.vector.y;

    //更新形状坐标
    this.element.shape.cx = this.x;
    this.element.shape.cy = this.y;

    this.element.dirty();
};

划线方法:

drawLines(){
    //清空lines,用于重绘线
    for(let i = 0; i < this.lines.length; i ++){
        let l = this.lines[i];

        zr.remove(l); //删除形状
        l = null; //并解除绑定
    }
    this.lines = []; //删除后,清空数组

    //遍历各个点之间的距离
    for(let i = 0; i < particals.length; i ++){
        let p = particals[i];

        //勾股定理,获取两点之间的距离
        let distance = Math.sqrt(Math.pow(this.x - p.x, 2) + Math.pow(this.y - p.y, 2));

        if(distance <= opts.joinLineDis && distance > 0){
            let opacity = 1 - distance / opts.joinLineDis; //根据距离大小来设置透明度
            let color = opts.lineColor.match(/\d+/g); //因为这里要用到透明度,所以需要重新组合rgba,先把各个颜色值取到数组中

            let l = new Line({
                 shape: {
                    x1: this.x,
                    y1: this.y,
                    x2: p.x,
                    y2: p.y,
                },

                style: {
                    stroke: ‘rgba(‘ + color[0] + ‘,‘ + color[1] + ‘,‘ + color[2] + ‘,‘ + opacity + ‘)‘, //组建颜色
                    fill: null
                },
            });

            this.lines.push(l); //存入lines
            zr.add(l); //加入ZRender Storage中
        }
    };
}

目前所有核心部分已完成,现在来初始化它:


var init = function(){
    for (let i = 0; i < opts.particalAmount; i++) {
        let p = new Partical();

        particals.push(p); // 把粒子实例 存入particals中,方便后面操作
        zr.add(p.element); //加入 ZRender Storage中
    }
};

效果:


技术分享

开始动画函数,让粒子动起来,并生成连接线:

function loop(){
    for(let i = 0; i < particals.length; i ++){
        let p = particals[i];

        p.updatePosition(); //更新位置
        p.drawLines(); //绘制线段
    }

    window.requestAnimationFrame(loop);
};

最终效果:

技术分享


全部代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="./esl.js"></script>
    <style>
        #main{
            position: absolute;
            left:0;
            top:0;
            background: #000;
            z-index: -1;
        }
    </style>
</head>
<body>
    <canvas id="main"></canvas>

<script>

require.config({
    packages:[
        {
            name: ‘zrender‘,
            location: ‘./src‘,
            main: ‘zrender‘,
        },
    ],
});

require([‘zrender‘,
    ‘zrender/graphic/shape/Circle‘,
    ‘zrender/graphic/shape/Line‘], function(zrender, Circle, Line){

    /*
     * 作者:王乐平
     * 博客:http://blog.csdn.net/lecepin/
     */

    //-----全局var-----{
    var winH = window.innerHeight;
    var winW = window.innerWidth;
    var opts = {
        background: ‘#000‘, //Canvas背景色
        paricalRadius: 2,
        paricalColor: ‘rgb(0, 255, 0)‘,
        lineColor: ‘rgb(0, 255, 0)‘,
        joinLineDis: 300,
        particalAmount: 30,
        speed: 1,
    };
    var tid; //setTimeout id,防抖处理
    var particals = []; //用于存储partical对象

    //-----------------}

    var zr = zrender.init(main, {width: winW, height: winH});

    zr.dom.style.backgroundColor = opts.background;

    window.addEventListener(‘resize‘, function(){
        clearTimeout(tid);

        var tid = setTimeout(function(){
            winW = zr.dom.width = window.innerWidth;
            winH = zr.dom.height = window.innerHeight;

            zr.refresh();
        }, 300); //这里设置了300ms的防抖间隔
    }, false);

    class Partical {
        constructor(){
            this.lines = [], //用于存储连线
            //粒子坐标初始化
            this.x = winW * Math.random();
            this.y = winH * Math.random();
            this.speed = opts.speed + Math.random(); //这个random可不加,主要是为了制作不同的速度的
            this.angle = ~~(360 * Math.random());
            this.color = opts.paricalColor;
            this.radius = opts.paricalRadius + Math.random();
            this.vector = {
                x: this.speed * Math.cos(this.angle),
                y: this.speed * Math.sin(this.angle),
            } 
            this.element = new Circle({
                shape: {
                    cx: this.x,
                    cy: this.y,
                    r: this.radius,
                },
                style: {
                    fill: this.color,
                }
            });
        };


        updatePosition(){
            if(this.x >= winW || this.x <= 0){
                this.vector.x *= -1;
            }

            if(this.y >= winH || this.y <= 0){
                this.vector.y *= -1;
            }

            if(this.x > winW){
                this.x = winW;
            }

            if(this.x < 0){
                this.x = 0;
            }

            if(this.y > winH){
                this.y = winH;
            }

            if(this.y < 0){
                this.y = 0;
            }

            this.x += this.vector.x;
            this.y += this.vector.y;

            this.element.shape.cx = this.x;
            this.element.shape.cy = this.y;

            this.element.dirty();
        };


        drawLines(){
            //清空lines
            for(let i = 0; i < this.lines.length; i ++){
                let l = this.lines[i];

                zr.remove(l);
                l = null;
            }
            this.lines = [];

            //遍历各个点之间的距离
            for(let i = 0; i < particals.length; i ++){
                let p = particals[i];

                //勾股定理
                let distance = Math.sqrt(Math.pow(this.x - p.x, 2) + Math.pow(this.y - p.y, 2));

                if(distance <= opts.joinLineDis && distance > 0){
                    let opacity = 1 - distance / opts.joinLineDis;
                    let color = opts.lineColor.match(/\d+/g);

                    let l = new Line({
                         shape: {
                            x1: this.x,
                            y1: this.y,
                            x2: p.x,
                            y2: p.y,
                        },

                        style: {
                            stroke: ‘rgba(‘ + color[0] + ‘,‘ + color[1] + ‘,‘ + color[2] + ‘,‘ + opacity + ‘)‘,
                            fill: null
                        },
                    });

                    this.lines.push(l);
                    zr.add(l);
                }
            };
        }
    }

    var init = function(){
        for (let i = 0; i < opts.particalAmount; i++) {
            let p = new Partical();

            particals.push(p);
            zr.add(p.element);
        }
    };

    function loop(){
        for(let i = 0; i < particals.length; i ++){
            let p = particals[i];

            p.updatePosition();
            p.drawLines();
        }

        window.requestAnimationFrame(loop);
    };

    init();
    loop();

});
</script>
</body>
</html>

博客名称:王乐平博客

CSDN博客地址:http://blog.csdn.net/lecepin

技术分享
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    ZRender实现粒子网格动画实战