背景
不经意搜canvas特效,看到有个还不错,改了改实现了一下,觉得挺有意思,加工分享一下。
效果
另外一个效果
原理和代码
-
- 先创建一个副画布,写好自己想写的文字,然后通过像素读点的方法获取目标每个像素点的位置
const viceCanvas = document.createElement('canvas')
viceCanvas.width = WIDTH;
viceCanvas.height = HEIGHT;
let viceCxt = viceCanvas.getContext('2d')
// 绘制最终结果文字
const font = '九三'
viceCxt.font = '200px Arial';
const measure = viceCxt.measureText(font)
viceCxt.fillText(font, (WIDTH - measure.width) / 2, HEIGHT / 2);
return getFontInfo(viceCxt);
复制代码
其中getFontInfo就是通过遍历像素读点的方法
// 辅助,通过取像素获取像素点信息
function getFontInfo(ctx) {
let imageData = ctx.getImageData(0, 0, WIDTH, HEIGHT).data;
const particles = [];
for (let x = 0; x < WIDTH; x += 4) {
for (let y = 0; y < HEIGHT; y += 4) {
const fontIndex = (x + y * WIDTH) * 4 + 3;
if (imageData[fontIndex] > 0) {
particles.push(new Particle({
x,
y,
}))
}
}
}
return particles;
}
复制代码
这里面Particle是自己定义的一个类,表示的每个点的轨迹。 他的constructor中有初始点(随机)和目标位置(前面读副本获取到且传入的)和速度。
constructor(center) {
this.x = center.x; // 记录点位最终应该停留在的x轴位置
this.y = center.y; // 记录点位最终应该停留在的y轴位置
this.item = 0; // 贝塞尔曲线系数
this.vx = 20; // 点位在x轴的移动速度
this.vy = 16; // 点位在y轴的移动速度
this.initX = Math.random() * WIDTH; // 点位随机在画布中的x坐标
this.initY = Math.random() * HEIGHT; // 点位随机在画布中的y坐标
}
复制代码
- 2 创建主画布,然后就是render的过程了
let WIDTH, HEIGHT, cxt, raf, points;
// init
window.onload = () => {
WIDTH = document.body.clientWidth;
HEIGHT = document.body.clientHeight;
const canvas = document.getElementById('canvas');
canvas.width = WIDTH;
canvas.height = HEIGHT;
ctx = canvas.getContext('2d');
points = createViceCanvas();
render()
}
function render() {
ctx.clearRect(0, 0, WIDTH, HEIGHT)
points.forEach((value) => { //
value.draw(); // 每一条点单独绘制一个随机到目标位置的曲线
})
raf = window.requestAnimationFrame(render)
if(points[0].item>=1){
window.cancelAnimationFrame(raf)
}
}
复制代码
特别注意requestAnimationFrame和cancelAnimationFrame。 points就是从副本中读出的目标点的集合。 每一次render都要绘制一小步。调用Particle类draw方法。
-
- render时的绘制
draw的两个版本,版本一 贝塞尔曲线,原文作者就用的是这个。
draw() { // 绘制点位
ctx.beginPath();
const { x, y } = threeBezier( // 贝塞尔曲线,获取每一个tick点位所在位置
this.item,
[this.initX, this.initY],
[this.x, this.y],
[this.x, this.y],
[this.x, this.y]
)
ctx.arc(x, y, 2, 0, 2 * Math.PI, true);
ctx.fillStyle = randomHexColor()
ctx.fill();
ctx.closePath();
this.speed(); // 点位下次tick绘制时的坐标
}
复制代码
贝塞尔曲线么,就是曲线美观一点,每一次根据时刻t,去算下一个点的位置,然后去做一个arc
贝塞尔曲线也封装了一个方法。
其实就是一个数学公式。
const threeBezier = (t, p1, p2, cp1, cp2) => {
const [startX, startY] = p1;
const [endX, endY] = p2;
const [cpX1, cpY1] = cp1;
const [cpX2, cpY2] = cp2
let x = startX * Math.pow(1 - t, 3) +
3 * cpX1 * t * Math.pow(1 - t, 2) +
3 * cpX2 * Math.pow(t, 2) * (1 - t) +
endX * Math.pow(t, 3);
let y = startY * Math.pow(1 - t, 3) +
3 * cpY1 * Math.pow(1 - t, 2) * t +
3 * cpY2 * (1 - t) * Math.pow(t, 2) +
endY * Math.pow(t, 3)
return {
x,
y,
}
}
复制代码
然后就是版本二,不理解贝塞尔公式,我画直线行不行。 也行。
const { x, y } = lineAAA( // 贝塞尔曲线,获取每一个tick点位所在位置
this.item,
[this.initX, this.initY],
[this.x, this.y],
[this.x, this.y],
[this.x, this.y]
)
ctx.moveTo(x, y)
ctx.lineTo(this.x, this.y)
ctx.strokeStyle = randomHexColor()
ctx.stroke()
const lineAAA = (t, p1, p2, cp1, cp2) => {
const [startX, startY] = p1;
const [endX, endY] = p2;
let x = startX + (endX - startX) * t
let y = startY + (endY - startY) * t
return {
x,
y,
}
}
复制代码
-
- 到上面,所有的都讲完了,对于单个粒子,他有一个随机的初始点,和一个明确的目标点,然后每一次render的过程中,都会计算得到它应该经过的点。如上,就做好了一个粒子轨迹。
-
- 颜色随机
这个就很easy
function randomHexColor() { //随机生成十六进制颜色
return '#' + ('00000' + (Math.random() * 0x1000000 << 0).toString(16)).substr(-6);
}
复制代码