将图片或文字生成粒子动画

583 阅读2分钟

最近因为项目需要,要将设计师给到的一个粒子图片(类似DNA)序列图,生成一个粒子动画效果。查阅了相关的api,发现其实可以ctx.getImageData将图片的像素先读取,并生成数组信息。然后再根据这个信息做动画位移。

直接上我做的一个组件

import React, { useEffect } from 'react';
import './index.less';
export default () => {
  useEffect(() => {
    let particleArray: any[] = [];
    let animateArray: any[] = [];
    let particleSize_x = 1;
    let particleSize_y = 2;
    let oldColor = '';
    let timer: any;
    const canvas = document.getElementById('canvas') as any;
    const ctx = canvas.getContext('2d');
    let img: any;
    const RAF = (function() {
      return (
        window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        function(callback) {
          window.setTimeout(callback, 1000 / 60);
        }
      );
    })();

    let canvasHandle = {
      ite: 0,
      start: 0,
      end: 0,
      imgx: 0,
      imgy: 0,
      isInit: false,
      init: function() {
        this._reset();
        this._initImageData();
        this._execAnimate();
      },
      _reset: function() {
        particleArray.length = 0;
        animateArray.length = 0;
        this.ite = 30;
        this.start = 0;
        this.end = this.start + this.ite;
      },
      _initImageData: function() {
        if (canvas && img) {
          this.imgx = 0;
          this.imgy = 0;
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          ctx.drawImage(img, 0, 0, img.width, img.height);
          let imgData = ctx.getImageData(
            this.imgx,
            this.imgy,
            img.width,
            img.height
          );
          for (let x = 0; x < img.width; x += particleSize_x) {
            for (let y = 0; y < img.height; y += particleSize_y) {
              let i = (y * imgData.width + x) * 4;
              if (imgData.data[i + 3] >= 125) {
                let color =
                  'rgba(' +
                  imgData.data[i] +
                  ',' +
                  imgData.data[i + 1] +
                  ',' +
                  imgData.data[i + 2] +
                  ',' +
                  imgData.data[i + 3] +
                  ')';
                let x_random = x + Math.random() * 20,
                  vx = -Math.random() * 200 + 400,
                  y_random = img.height / 2 - Math.random() * 40 + 20,
                  vy;
                if (y_random < this.imgy + img.height / 2) {
                  vy = Math.random() * 300;
                } else {
                  vy = -Math.random() * 300;
                }
                particleArray.push(
                  new Particle(
                    x_random + this.imgx,
                    y_random + this.imgy,
                    x + this.imgx,
                    y + this.imgy,
                    vx,
                    vy,
                    color
                  )
                );
                particleArray[particleArray.length - 1].drawSelf();
              }
            }
          }
        }
      },
      _execAnimate: function() {
        const that = this;
        particleArray.sort((a, b) => {
          return a.ex - b.ex;
        });
        if (!this.isInit) {
          this.isInit = true;
          animate((tickTime: any) => {
            if (animateArray.length < particleArray.length) {
              if (that.end > particleArray.length - 1) {
                that.end = particleArray.length - 1;
              }
              animateArray = animateArray.concat(
                particleArray.slice(that.start, that.end)
              );
              that.start += that.ite;
              that.end += that.ite;
            }
            animateArray.forEach(i => {
              i.update(tickTime);
            });
          });
        }
      }
    };
    let timestamp: any;

    function animate(tick: any) {
      if (typeof tick == 'function') {
        let newtime: any = new Date();
        let tickTime = timestamp ? newtime - timestamp : 0;
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        timestamp = newtime;
        tick(tickTime);
        timer = RAF(function() {
          animate(tick);
        });
      }
    }

    class Particle {
      private a = 1200;
      private width = particleSize_x;
      private height = particleSize_y;
      private stop = false;
      private static = false;
      private maxCheckTimes = 10;
      private checkLength = 5;
      private checkTimes = 0;
      constructor(
        private x: number,
        private y: number,
        private ex: number,
        private ey: number,
        private vx: number,
        private vy: number,
        private color: string
      ) {}
      public drawSelf() {
        if (oldColor !== this.color) {
          ctx.fillStyle = this.color;
          oldColor = this.color;
        }
        ctx.fillRect(
          this.x - this.width / 2,
          this.y - this.height / 2,
          this.width,
          this.height
        );
      }
      public update(tickTime: any) {
        if (this.stop) {
          this.x = this.ex;
          this.y = this.ey;
        } else {
          tickTime = tickTime / 1000;
          let cx = this.ex - this.x;
          let cy = this.ey - this.y;
          let angle = Math.atan(cy / cx);
          let ax = Math.abs(this.a * Math.cos(angle));
          ax = this.x > this.ex ? -ax : ax;
          let ay = Math.abs(this.a * Math.sin(angle));
          ay = this.y > this.ey ? -ay : ay;
          this.vx += ax * tickTime;
          this.vy += ay * tickTime;
          this.vx = ~~this.vx * 0.95;
          this.vy = ~~this.vy * 0.95;
          this.x += this.vx * tickTime;
          this.y += this.vy * tickTime;
          if (
            Math.abs(this.x - this.ex) <= this.checkLength &&
            Math.abs(this.y - this.ey) <= this.checkLength
          ) {
            this.checkTimes++;
            if (this.checkTimes >= this.maxCheckTimes) {
              this.stop = true;
            }
          } else {
            this.checkTimes = 0;
          }
        }
        this.drawSelf();
      }
    }
    //use image
    function useImage() {
      const screenWidth = document.documentElement.clientWidth;
      const screenHeight = document.documentElement.clientHeight;
      img = new Image();
      img.src = require('./page1-bg.png');
      if (canvas && img) {
        canvas.width = screenWidth;
        canvas.height = screenHeight;
        const scale = screenWidth / 639;
        img.width = screenWidth;
        img.height = 965 * scale;
        img.onload = function() {
          canvasHandle.init();
        };
      }
    }
    //use text
    // function useText(text: any) {
    //   const img = document.createElement('canvas') as any;
    //   img.width = 600;
    //   img.height = 180;
    //   var imgctx = img.getContext('2d');
    //   imgctx.textAlign = 'center';
    //   imgctx.textBaseline = 'middle';
    //   imgctx.font = '100px 微软雅黑';
    //   imgctx.fillText(text || 'TECHBROOD', img.width / 2, img.height / 2);
    //   canvasHandle.init();
    // }
    useImage();
    return () => {
      if (window.requestAnimationFrame) {
        cancelAnimationFrame(timer);
      } else {
        clearInterval(timer);
      }
    };
  });
  return (
    <div className='dotAnimal' id='dotBox'>
      <canvas id='canvas'></canvas>
    </div>
  );
};