Canvas 文字转颗粒实际效果的完成编码

2021-02-26 18:56 jianzhan

本文详细介绍了Canvas 文字转颗粒实际效果的完成编码,共享给大伙儿,期待对大伙儿有一定的协助,实际以下:

根据颗粒来绘图文字令人觉得很成心思,相互配合颗粒的健身运动更会让这个实际效果更为炫酷。本文详细介绍在 canvas 中根据颗粒来绘图文字的方式。

完成基本原理

总的来讲要做出将文字变为颗粒展现的实际效果实际上很简易,完成的基本原理便是应用两张 canvas,1张是客户看不见的 A canvas,用来绘图文字;另外一张是客户看到的 B canvas,用来依据 A 的文字数据信息来转化成颗粒。直观表明如图:

建立离屏 canvas

HTML 只必须置放主 canvas 便可:

<!-- HTML 构造 -->
<html>
<head>
  ...
</head>
<body>
  <canvas id="stage"></canvas>
</body>
</html>

随后建立1个离屏 canvas,并绘图文字:

const WIDTH = window.innerWidth;
const HEIGHT = window.innerHeight;

const offscreenCanvas = document.createElement('canvas');
const offscreenCtx = offscreenCanvas.getContext('2d');

offscreenCanvas.width = WIDTH;
offscreenCanvas.height = HEIGHT;

offscreenCtx.font = '100px PingFang SC';
offscreenCtx.textAlign = 'center';
offscreenCtx.baseline = 'middle';
offscreenCtx.fillText('Hello', WIDTH / 2, HEIGHT / 2);

这时候网页页面上甚么也沒有产生,但具体上能够想像在离屏 canvas 上,此时应当如图所示:

关键方式 getImageData

应用 canvas 的 getImageData 方式,能够获得1个 ImageData 目标,这个目标用来叙述 canvas 特定地区内的像素数据信息。也便是说,大家能够获得 “Hello” 这个文字每一个像素点的部位和色调,也便可以在特定部位转化成颗粒,最终产生的实际效果便是颗粒拼凑成文字了。

要获得像素信息内容,必须应用 ImageData 目标的 data 特性,它将全部像素点的 rgba 值铺平变成1个数字能量数组,每一个像素点有 rgba 4个值,这个数字能量数组的个数也便是 像素点数量 * 4

假定我选择了1个 3 * 4 地区,那末1共 12 个像素点,每一个像素点有 rgba 4个值,因此 data 这个数字能量数组就会有 12 * 4 = 48 个元素。

假如复印出 data,能够看到即从左往右,从上往下排序这些像素点的 rgba。

自然大家要获得的地区务必要包括文字,因此应当获得全部离屏 canvas 的地区:

const imgData = offscreenCtx.getImageData(0, 0, WIDTH, HEIGHT).data;

转化成颗粒

拿到 ImageData 后,根据遍历 data 数字能量数组,能够分辨在离屏 canvas 的画布中,哪些点是有颜色的(处在文字正中间),哪些点是沒有颜色的(不在文字上),把那些有颜色的像素部位记下来,随后在主 canvas 上转化成颗粒,就 ok 了。

最先建立1下颗粒类:

class Particle {
    constructor (options = {}) {
        const { x = 0, y = 0, color = '#fff', radius = 5} = options;
        this.radius = radius;
        this.x = x;
        this.y = y;
        this.color = color;
    }

    draw (ctx) {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
        ctx.fillStyle = this.color;
        ctx.fill();
        ctx.closePath();
    }
}

遍历 data,大家能够依据全透明度,也便是 rgba 中的第4个元素是不是不为 0 来分辨该像素是不是在文字中。

const particles = [];
const skip = 4;
for (var y = 0; y < HEIGHT; y += skip) {
    for (var x = 0; x < WIDTH; x += skip) {
        var opacityIndex = (x + y * WIDTH) * 4 + 3;
        if (imgData[opacityIndex] > 0) {
            particles.push(new Particle({
                x,
                y,
                radius: 1,
                color: '#2EA9DF'
            }));
        }
    }
}

大家用 particles 数字能量数组来储放全部的颗粒,这里的 skip 的功效是遍历的步长,假如大家1个像素1个像素地扫,那末最终拼凑文字的颗粒可能十分聚集,增大这个值,最终造成的颗粒就会更稀少。

最终在建立主 canvas 并绘图便可:

const canvas = document.querySelector('#stage');
canvas.width = WIDTH;
canvas.height = HEIGHT;
const ctx = canvas.getContext('2d');

for (const particle of particles) {
    particle.draw(ctx);
}

实际效果以下:

详细编码见01-basic-text-to-particles

加上实际效果

掌握完成基本原理以后,实际上别的的就全是给颗粒加上1些动效了。最先可让颗粒有1些任意的位移,防止看上去过度齐整。

const particles = [];
const skip = 4;
for (var y = 0; y < HEIGHT; y += skip) {
    for (var x = 0; x < WIDTH; x += skip) {
        var opacityIndex = (x + y * WIDTH) * 4 + 3;
        if (imgData[opacityIndex] > 0) {
            // 建立颗粒时添加任意位移
            particles.push(new Particle({
                x: x + Math.random() * 6 - 3,
                y: y + Math.random() * 6 - 3,
                radius: 1,
                color: '#2EA9DF'
            }));
        }
    }
}

实际效果以下:

假如想完成增大的实际效果,如:

这类要如何完成呢,最先必须任意造成颗粒的尺寸,这只必须在建立颗粒时对 radius 开展 random 便可。此外假如要让颗粒半径动态性更改,那末必须区别开颗粒的3D渲染半径和原始半径,并应用 requestAnimationFrame 开展动漫3D渲染:

class Particle {
    constructor (options = {}) {
        const { x = 0, y = 0, color = '#fff', radius = 5} = options;
        this.radius = radius;
        // ...
        this.dynamicRadius = radius; // 加上 dynamicRadius 特性
    }

    draw (ctx) {
        // ...
        ctx.arc(this.x, this.y, this.dynamicRadius, 0, 2 * Math.PI, false); // 更换为 dynamicRadius
        // ...
    }
    
    update () {
        // TODO
    }
}

requestAnimationFrame(function loop() {
    requestAnimationFrame(loop);

    ctx.fillStyle = '#fff';
    ctx.fillRect(0, 0, WIDTH, HEIGHT);

    for (const particle of particles) {
        particle.update();
        particle.draw(ctx);
    }
});

那末重要就在于颗粒的 update 方式要怎样完成了,假定大家想让颗粒半径在 1 到 5 中光滑循环系统更改,很非常容易令人想到到3角涵数,如:

横轴应当是与時间有关,能够再维护保养1个自变量每次启用 update 的情况下开展加实际操作,简易做还可以立即用時间戳来开展测算。update 方式示比如下:

update () {
    this.dynamicRadius = 3 + 2 * Math.sin(new Date() / 1000 % 1000 * this.radius);
}

详细编码见 02-text-to-particles-with-size-changing

以上便是本文的所有內容,期待对大伙儿的学习培训有一定的协助,也期待大伙儿多多适用脚本制作之家。