前言
第一次用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
原文发布时间为:2018年06月26日
原文作者:
我母鸡啊!
本文来源: 掘金
如需转载请联系原作者