blog
blog copied to clipboard
一个粒子时钟 动画
效果展示
前言
我就写写...
第一步 当然是新建一个JSON文件
Map.js,他就是一个类似地图的东西。
第二步 vue实例中的methods方法下面新建一个init方法
在mounted方法下面调用init这个方法。
调用方式this.init()
,先说主思路,时钟作为底层canvas,它每秒更新一次,小球作为表层canvas,它50ms更新一次,事实上为什么是50ms呢,因为它比60ms要小其次50被整除是一个整数,方便计算。
嗯嗯,由于绘制时钟比较简单,我将drawClock方法中同时包括了绘制以及时刻调用自我,不断更新canvas。而更新小球这一图层,比较复杂。包括以下步骤:
- 首先,提供一个数组给他储存一系列小球的相关数据,x,y,color,stepX轴速度,stepY轴速度,disY轴加速度。这个是小球的数据结构。这个就是
updateBalls()
方法要做的事情,时刻根据速度以及加速度,更新自己那个x,y坐标。
init(){
//更新时钟
setInterval(()=>{
this.drawClock();
},1000);
//更新小球
setInterval(()=>{
//更新小球状态
this.updateBalls();
// //渲染
this.renderBalls();
},50);
},
- 其次。渲染,绘制每个小球canvas 具体方法也很简单,循环this.ball数组就好了,数组提供每一个arr就是单个小球的数据结构,具体如下代码。
this.ball.forEach(arr=>{
this.ballCxt.beginPath();
this.ballCxt.arc(arr.x,arr.y,this.R,0,2*Math.PI);
this.ballCxt.fillStyle = arr.color;
this.ballCxt.closePath();
this.ballCxt.fill();
})
第三步
好吧。我也好缭乱。 说说概念性质的东西,
- this.ball储存的数组过长,会发生什么事情呢? 答案是浏览器卡死, 所以要做不定期清理this.ball的长度,我这种做法是通过观察this.ball的长度的规律进行的强行清理,没有做过精确计算。我觉得也没有必要。
this.cache = time.split('');
if(this.ball.length > 500) {
this.ball.splice(0, 200)
}
- 选择性push数组到this.ball里面 首先,小时不是每时每刻都会动,分钟也不是,所以在push的时候,设置一个cache数组,他记录上一秒的时间,那么他们之间如何对比呢???通过split('')分裂字符串,对比你现在的某个数字,如果一致,则不会再push数组到ball里面,代码如下,简单来说就是做一个if判断而已。
if(letter !== this.cache[index]){
map[letter].forEach((arr1,i)=>{
arr1.forEach((arr2,j)=>{
if(arr2 === 1){
this.ball.push({
x: (j*10 + index*100) + 100,
y: i*10 + 300,
stepX: Math.floor(Math.random() * 4 -2) + (Math.random() - 0.5)*5,
stepY: -2*this.numArray[Math.floor(Math.random()*this.numArray.length)],
color: this.colorArray[Math.floor(Math.random()* this.colorArray.length)],
disY:1
})
}
})
})
}
这样下来,this.ball数组长度每次增加一般就是28个数组,不至于太长。 好吧,简单赘述了一下。 下面贴一下全部代码,
<template>
<div class="content">
<canvas width="1000px" height="700px" class="background"/>
<canvas width="1000px" height="700px" class="animate"/>
</div>
</template>
<script>
import axios from 'axios';
import store from '../../store';
import map from './map.js'
export default {
store,
data(){
return {
ball:[],
cache: [],
numArray: [1,2,3],
colorArray: ["#3BE","#09C","#A6C","#93C","#9C0","#690","#FB3","#F80","#F44","#C00"],
R: 4,
W: 700,
H: 1000,
}
},
created(){
window.app = this;
},
mounted(e){
this.init();
},
methods:{
renderBalls(){
this.ballCxt.clearRect( 0, 0 ,1000, 700);
//
this.ball.forEach(arr=>{
this.ballCxt.beginPath();
this.ballCxt.arc(arr.x,arr.y,this.R,0,2*Math.PI);
this.ballCxt.fillStyle = arr.color;
this.ballCxt.closePath();
this.ballCxt.fill();
})
},
updateBalls(){
// console.log(this.ball);
const canvas = document.querySelector('canvas.animate');
this.ballCxt = canvas.getContext('2d');
const canvasBound = canvas.getBoundingClientRect();
this.ball.forEach(arr=>{
if(arr.y> 650){
arr.stepY = -arr.stepY*0.7
arr.y += arr.stepY - 10;
}
arr.stepY += arr.disY;
arr.x += arr.stepX;
arr.y += arr.stepY;
})
},
init(){
//更新时钟
setInterval(()=>{
this.drawClock();
},1000);
//更新小球
setInterval(()=>{
//更新小球状态
this.updateBalls();
// //渲染
this.renderBalls();
},50);
},
drawClock(){
const canvas = document.querySelector('canvas.background');
const ctx = canvas.getContext('2d');
this.ctx = ctx;
const canvasBound = canvas.getBoundingClientRect();
const time = Intl.DateTimeFormat('en-US',{
hour: 'numeric', minute: 'numeric', second: 'numeric',
hour12: false
}).format()
this.ctx.clearRect( 0, 0 ,canvasBound.width, canvasBound.height);
this.ctx.fillStyle = '#123';
this.ctx.fillRect( 0, 0 ,canvasBound.width, canvasBound.height);
this.drawTime(time);
// ctx.fillText(time,500,350 );
this.ctx.stroke();
},
drawTime(time){
time.split('').forEach((letter,index)=>{
// if(this.cache[index]!== letter){
this.drawWord({
letter,
index
})
// }
})
this.cache = time.split('');
if(this.ball.length > 500) {
this.ball.splice(0, 200)
}
},
drawWord({
letter,index
}){
map[letter].forEach((arr1,i)=>{
arr1.forEach((arr2,j)=>{
if(arr2 === 1){
this.drawBall({
x: (j*10 + index*100) + 100,
y: i*10 + 300
})
}
})
})
if(letter !== this.cache[index]){
map[letter].forEach((arr1,i)=>{
arr1.forEach((arr2,j)=>{
if(arr2 === 1){
this.ball.push({
x: (j*10 + index*100) + 100,
y: i*10 + 300,
stepX: Math.floor(Math.random() * 4 -2) + (Math.random() - 0.5)*5,
stepY: -2*this.numArray[Math.floor(Math.random()*this.numArray.length)],
color: this.colorArray[Math.floor(Math.random()* this.colorArray.length)],
disY:1
})
}
})
})
}
},
drawBall({x,y}){
this.ctx.beginPath();
this.ctx.arc(x,y,3,0,2*Math.PI);
this.ctx.closePath();
this.ctx.fillStyle="red";
this.ctx.fill();
},
}
};
</script>
<style lang="scss" scoped>
canvas.background {
position: absolute;
}
canvas.animate {
position: absolute;
}
</style>
Reference: