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