一个粒子时钟 动画

avatarplhDigital nomad

效果展示

前言

我就写写...

第一步 当然是新建一个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: