canvas-繽紛紙片

我母雞啊!發表於2018-06-26

前言

第一次用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
複製程式碼

相關文章