canvas-缤纷纸片

1,664 阅读4分钟

前言

第一次用canvas绘图,其实难度是挺大的,基于js和一些数学知识,还有参考了网上很多的例子,最终完成了demo。

效果如下:

![paper.gif-909.1kB][1] [1]: http://static.zybuluo.com/juanmao/bn8f0lluesbzqadn5tidi14y/paper.gif

调用代码

<html>
    <head>
        <meta charset="utf-8">
        <title>缤纷纸片</title>
        <style>
            body{
                background:url("img/background.jpg") no-repeat top;
            }
        </style>
    </head>
    <body>
        <div id="app"></div>
        <script src="./index.js"></script>
        <script>
             var a = new Paper({
                    param:{
                        width: 1024,//背景图的宽
                        height: 1344, //背景图的高
                    }
                })

        </script>
    </body>
    
</html>

源码讲解

好了,接下来讲解一下简单的实现原理 首先,先定义一些我们会用到的变量,和2个主要的对象。

/**
 * 设计思路:2个主要方法,Paper(),Process();
 * Paper()方法主要用于
 * 1.创建canvas实例,build();
 * 2.渲染canvas实例,render();
 * 
 * Process()方法主要是用于:计算进程,控制动画的状态。 
 * 我之前写过2种方式去绘制纸片,
 * 以前的是每一次都重新绘制纸片的样式,现在我把每一个纸片的样式都提前绘制好,
 * 放在一个sprites的数组里,以后的每一次渲染都不会重新绘制单个纸片了,只是回去改变这个纸片的位置和旋转角度
 */
 class Paper{
     constructor(){
        //定义一些默认的属性,后面会给出口子去修改
        this.CONST ={
            SPRITE_WIDTH: 120, //纸片宽
            SPRITE_HEIGHT: 120, //纸片高
            PAPER_LENGTH: 5,//纸片数量
            DURATION: 8000, 
            TRANSLATE_RATE: 50, //横行平移系数
            COLORS: [ //纸片的色彩值
            "#EF5350","#EC407A","#AB47BC","#7E57C2",
            "#5C6BC0","#42A5F5","#29B6F6","#26C6DA", 
            "#26A69A", "#66BB6A", "#9CCC65", "#D4E157", 
            "#FFEE58", "#FFCA28", "#FFA726", "#FF7043", 
            "#8D6E63", "#BDBDBD", "#78909C"]
        }
     }
 }

接下来把一些要用的变量全部定义好

class Paper{
      constructor({params}){
         // {...}
        //定义出需要用到的一些基础变量,还有传进来的一些参数
        //定义父元素,最基础canvas宽高,纸片数量,定位时y的范围,间隔时常,旋转角度,旋转速度
        const { elm,width,height,length,yRange,duration,rotationRange,speedRange} = params
        this.parent = document.getElementById(elm) || document.body; 
         //删除已有的canvas
        if(document.getElementsByTagName("canvas").length >0){
            this.canvas = null;
            this.parent.removeChild(parent.childNodes[0])
        }
        this.canvas = document.createElement("canvas"); 
        this.ctx = this.canvas.getContext("2d");
        this.width = width || this.parent.offsetWidth;
        this.height = height || this.parent.offsetHeight;
        this.length = length || this.CONST.PAPER_LENGTH;
        this.yRange = yRange || this.height * 2;
        //创建progress实例,将Progress的属性继承到Paper中的progress属性上。
        this.progress = new Progress({
            duration: duration||this.CONST.DURATION,
            isLoop: true
          });
        //旋转角度
        this.rotationRange = typeof rotationRange === "number" ? rotationRange : 0;
        //旋转速度
        this.speedRange = typeof speedRange === "number" ? speedRange : 1;
        //单个纸片canvas集合
        this.sprites = [];
        //设置最大canvas的样式
        this.canvas.style.cssText = ["display: block", "position: absolute", "top: 0",              "left: 0", "pointer-events: none"].join(";");
        //在页面上渲染出来
        this.parent.append(this.canvas);
      }  
    }

开始定义Paper里2个主要的方法:build(),render()

build(){
    for(let i=0; i<this.length;++i){ //循环要创建的纸片数量
            //生成每一个纸片的每一个小的cnavas
            let canvas = document.createElement("canvas"),
                ctx = canvas.getContext("2d");

            canvas.width = this.CONST.SPRITE_WIDTH; //定义的常量纸片的宽
            canvas.height = this.CONST.SPRITE_HEIGHT;//定义的常量纸片的高
            //定义基本的位置
            canvas.position = {
                initX: Math.random() * this.width,
                initY: -canvas.height - Math.random() * this.yRange
              };
        
            canvas.rotation = this.rotationRange / 2 - Math.random() * this.rotationRange; 
            canvas.speed = this.speedRange / 2 + Math.random() * (this.speedRange / 2);
            ctx.save();
            //随机的填充颜色
            ctx.fillStyle = this.CONST.COLORS[Math.random() * this.CONST.COLORS.length | 0];                 //随机数判断:圆形<1 ,四边形<2,剩下的生成圆形
            let random = Math.random()*3
            let random = 2
            if(random <1){ 
                ctx.arc(10, 10, 10, 0,Math.PI*2);
            }else if(random < 2){
                ctx.fillRect(0, 0, canvas.width, canvas.height);
            }else{
                ctx.moveTo(0,0);
                ctx.lineTo(0,20);
                ctx.lineTo(20,20);
                ctx.closePath()
            }
            ctx.fill();
            ctx.restore();
            this.sprites.push(canvas);
        }
}
render(){
    //核心代码
     for(let i = 0; i < this.length; ++i){
            this.ctx.save();
            /**
             * 纸片的初始位置x + 纸片旋转 * 常量平移*进程
             */
            this.ctx.translate(
                this.sprites[i].position.initX + this.sprites[i].rotation * this.CONST.TRANSLATE_RATE * progress, //添加到水平坐标(x)上的值
                this.sprites[i].position.initY + progress * (this.height + this.yRange));//添加到垂直坐标(y)上的值。
            this.ctx.rotate(this.sprites[i].rotation); //方法旋转当前的绘图。
            this.ctx.drawImage(
                this.sprites[i], //图像,画布或视频
                    -this.CONST.SPRITE_WIDTH * Math.abs(Math.sin(progress * Math.PI * 2 * this.sprites[i].speed)) / 2, //在画布上放置图像的 x 坐标位置。
                    //雪碧图的width * 
                    -this.CONST.SPRITE_HEIGHT / 2,//在画布上放置图像的 y 坐标位置。
                    this.CONST.SPRITE_WIDTH * Math.abs(Math.sin(progress * Math.PI * 2 * this.sprites[i].speed)), //可选。要使用的图像的宽度(伸展或缩小图像)
                    this.CONST.SPRITE_HEIGHT); //可选。要使用的图像的高度(伸展或缩小图像)。
            this.ctx.restore();
        }
}

接下来我们要定义另外一个主要的对象Progress

    class Progress{
     constructor(param){
         //定义默认时常,是否重复动画
        const {duration,isLoop} = param
        this.timestamp = null;
        this.duration = duration || 1000;
        this.progress = 0;
        this.delta = 0;
        this.progress = 0;
        this.isLoop = !!isLoop;
     }
 }

这个对象里有3个核心的方法

//重置时间戳
rest(){
    this.timestamp = null;
}
//记录重新开始的时间戳
start(now){
    this.timestamp = now;
}
tick(now){
    if (this.timestamp) {
        this.delta = now - this.timestamp;
        this.progress = Math.min(this.delta / this.duration, 1); //取最小值

        if (this.progress >= 1 && this.isLoop) {
             this.start(now);
        }
        return this.progress;
    } else {
        return 0;
    }
}

至此,已贴出了部分的核心代码,稍晚我会将全部的代码贴到我的github上,喜欢的小伙伴可以帮忙点个star~有错误的写法还需要小伙伴多多指出!谢谢~


分割线 更新于2018/6/29

最新更新 我已把这个项目弄在了npm上,大家可以去下载下来看到源码,也可以在项目中去调用。

    npm i bling-paper