【canvas】canvas绘制粒子轨迹

·  阅读 608

背景

不经意搜canvas特效,看到有个还不错,改了改实现了一下,觉得挺有意思,加工分享一下。

效果

image.png

image.png

另外一个效果

image.png

原理和代码

    1. 先创建一个副画布,写好自己想写的文字,然后通过像素读点的方法获取目标每个像素点的位置

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方法。

    1. 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,
        }
    }
    
复制代码

    1. 到上面,所有的都讲完了,对于单个粒子,他有一个随机的初始点,和一个明确的目标点,然后每一次render的过程中,都会计算得到它应该经过的点。如上,就做好了一个粒子轨迹。
    1. 颜色随机

这个就很easy


    function randomHexColor() { //随机生成十六进制颜色
        return '#' + ('00000' + (Math.random() * 0x1000000 << 0).toString(16)).substr(-6);
    }
复制代码

源码

源码

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改