最近因为项目需要,要将设计师给到的一个粒子图片(类似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>
);
};