Canvas<粒子文字效果>

效果图:

20200318.gif

参考效果地址:http://www.jq22.com/code1148
(孰能生巧,现在只能先 “比葫芦画瓢”,慢慢来吧,注释很全,不过还是需要自己加以理解 👍)

🎈背景色修改:

background: rgb(0, 0, 0);

🎈粒子相关配置:

var max_radius = 3; //粒子的最大半径
var min_radius = 1;  //粒子的最小半径
var drag = 20; //切换文字,粒子组成速度 值越小越快
var colors = ["rgba(255, 255, 255, 0.7)","rgba(255, 255, 255, 0.1)"];//粒子颜色
var bool = true; //默认粒子文字是直接显示,反之则是粒子打乱后显示
var textFont = 80; //粒子文字大小

🎈方法调用:

changeText("❤");
202003181718.gif

获得在画布上绘制的像素坐标位置

    let temp;
        for (let i = 0; i < data.length; i += 4) {
            temp = {
                x: (i / 4) % w,
                y: ~~((i / 4) / w)
            };
            if (data[i] !== 0 && ~~(Math.random() * 5) == 1) {
                ctx.beginPath();
                ctx.fillStyle = 'red';
                ctx.arc(temp.x, temp.y, 2, 0, Math.PI * 2);
                ctx.fill();
            }
        }

若你不在画布上做任何操作,那么当你输出data时为。所以加上 data[i] !== 0 (rgb(0, 0, 0)为黑色,只要你绘制的东西不是这个色值,肯定会出现不等于0的像素模块,因此就能确定出你作画的位置坐标)

image.png

代码如下:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            width: 100%;
            height: 100vh;
            overflow: hidden;
            /* background-color: black; */
            margin: 0;
        }

        canvas {
            width: 100%;
            height: 100%;
            background: rgb(0, 0, 0);
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
        }

        #input {
            font-size: 30px;
            color: white;
            outline: none;
            display: block;
            width: 300px;
            position: absolute;
            left: calc(50% - 150px);
            top: 30%;
            border: none;
            background: transparent;
            border-bottom: 2px solid rgba(255, 255, 255, 0.822);
            text-align: center;
        }
    </style>
</head>

<body>
    <input id="input" type="text" maxlength="8" oninput="changeText(input.value)">
</body>
<script>
    /*jshint esversion:6*/
    var canvas = document.createElement("canvas");
    var w = window.innerWidth;
    var h = window.innerHeight;
    document.body.appendChild(canvas);
    canvas.width = w;
    canvas.height = h;
    var ctx = canvas.getContext('2d');

    //储存粒子数组
    var particles = [];
    var max_radius = 3; //粒子的最大半径
    var min_radius = 1;  //粒子的最小半径
    var drag = 20; //切换文字,粒子组成速度 值越小越快
    var colors = ["rgba(255, 255, 255, 0.7)", "rgba(255, 255, 255, 0.1)"];//粒子颜色
    var bool = true; //默认粒子文字是直接显示,反之则是粒子打乱后显示
    var textFont = 80; //粒子文字大小
    //初始鼠标坐标
    var mouse = {
        x: -1000,
        y: -1000
    };
    //pc 鼠标移动
    canvas.onmousemove = function (e) {
        mouse.x = e.clientX;
        mouse.y = e.clientY;
    };
    //mobile 手势移动
    canvas.ontouchmove = function (e) {
        mouse.x = e.touches[0].clientX;
        mouse.y = e.touches[0].clientY;
    };

    // 返回一个数的平方根
    function distance(x, y, x1, y1) {
        // sqrt() 方法可返回一个数的平方根。
        // a² + b² = c²
        return Math.sqrt((x1 - x) * (x1 - x) + (y1 - y) * (y1 - y));
    }

    //创建粒子文字或改变粒子文字
    //根据文字生成相对应的 粒子 ,并储存到 particles 数组内
    function changeText(text) {
        ctx.clearRect(0, 0, w, h);
        var current = 0; //初始化 粒子个数 默认为0个
        var temp; //初始化 粒子坐标
        var radius; //初始化 粒子半径
        var color; //初始化 粒子颜色
        //文字部分
        ctx.fillStyle = "rgb(255, 255, 255)";
        ctx.font = textFont + "px -apple-system";
        //https://www.w3school.com.cn/tags/canvas_filltext.asp
        //https://www.w3school.com.cn/tags/canvas_measuretext.asp
        //measureText 在画布上输出文本之前,检查字体的宽度
        // 实现文字居中
        ctx.fillText(text, w / 2 - ctx.measureText(text).width / 2, h / 2 + textFont / 2);
        //https://www.w3school.com.cn/tags/canvas_getimagedata.asp
        //通过 getImageData() 复制画布上指定矩形的像素数据
        var data = ctx.getImageData(0, 0, w, h).data;
        // console.log(data)
        // +=4 对于 ImageData 对象中的每个像素,都存在着4方面的信息,即 RGBA 值
        //此处 +=8 是为了避免资源浪费,减少粒子个数
        // += 越多,避免筛选的粒子越少,符合条件的,显示的粒子越少
        //data 会有特别 特别多
        for (let i = 0; i < data.length; i += 4) {
            temp = {
                x: (i / 4) % w,
                y: ~~((i / 4) / w)
            };

            //比较结果上的区别
            // != 返回同类型值比较结果。
            // !== 不同类型不比较,且无结果,同类型才比较。
            //比较过程上的区别
            //!= 比较时,若类型不同,会偿试转换类型。
            //!== 只有相同类型才会比较。
            //此处使用 !==,只能说是为了节约资源

            //http://www.fly63.com/article/detial/2802
            //~~ 同Math.floor() 向下取整
            //判断不为0 && 随机一个数值取值为 1 时,才生成一个粒子
            if (data[i] !== 0 && ~~(Math.random() * 5) == 1) {
                //因为之前定义文字颜色为 rgb(255, 255, 255)w
                //所以此处判断当遇到像素色值为 255 时,不显示粒子(若加上此处判断,则会实现描边效果,反之为填充效果)
                // if (data[i + 4] !== 255 || data[i - 4] !== 255 || data[i + w * 4] !== 255 || data[i - w * 4] !== 255) {
                //判断当再次调用 changeText 改变文字时,重新打乱并组成新的粒子文字
                if (current < particles.length) {
                    particles[current].target = temp
                    // console.log(current)
                } else {
                    //随机生成一个 min max 之间的粒子半径
                    radius = max_radius - Math.random() * min_radius;
                    //粒子从随机位置生成
                    if (!bool) {
                        temp = {
                            x: Math.random() * w,
                            y: Math.random() * h
                        };
                    }

                    //取粒子随机色
                    color = colors[~~(Math.random() * colors.length)];

                    //创建一个粒子,并添加到粒子数组中
                    var p = new Particle(
                        temp,
                        { x: (i / 4) % w, y: ~~((i / 4) / w) },
                        { x: 0, y: 0 },
                        color,
                        radius);

                    particles.push(p);
                }
                // ++current先自己加1,再做别的事情
                //存储符合条件的粒子数量
                ++current;
            }
            // }
        }
        bool = false;
        //当粒子文字变化时,删除多余的粒子
        particles.splice(current, particles.length - current);
    }

    //创建一个粒子的类,制定相关属性
    class Particle {
        constructor(pos, target, vel, color, radius) {
            this.pos = pos; //粒子坐标(随机位置或粒子坐标,方便实现文字切换时粒子打乱效果)
            this.target = target; //粒子坐标
            this.vel = vel; //记录鼠标移动打散的粒子坐标
            this.color = color; //粒子颜色
            this.radius = radius; //粒子半径
            var arr = [-1, 1];
            //粒子大小的变化速率
            this.direction = arr[~~(Math.random() * 2)] * Math.random() / 10;
        }
        //实时更新数据
        update() {
            //粒子半径的变化
            this.radius += this.direction;
            this.vel.x = (this.pos.x - this.target.x) / drag;
            this.vel.y = (this.pos.y - this.target.y) / drag;

            //鼠标移动到文字上时的效果
            //根据勾股定理 a² + b² = c²,鼠标坐标位置 - 粒子坐标位置 得到 a b 的长度,以此求得c的长度(及打散半径)
            //50 鼠标打散范围,当 c 的长度小于 50 时执行
            if (distance(this.pos.x, this.pos.y, mouse.x, mouse.y) < 50) {
                this.vel.x += this.vel.x - (this.pos.x - mouse.x) / 15;
                this.vel.y += this.vel.y - (this.pos.y - mouse.y) / 15;
            }

            //实现粒子变大缩小一直循环的效果
            if (this.radius >= max_radius) {
                this.direction *= -1;
            }
            if (this.radius <= 1) {
                this.direction *= -1;
            }

            this.pos.x -= this.vel.x;
            this.pos.y -= this.vel.y;
            this.draw()
        }
        //绘制粒子
        draw() {
            ctx.beginPath();
            ctx.fillStyle = this.color;
            ctx.arc(this.pos.x, this.pos.y, this.radius, 0, Math.PI * 2);
            ctx.fill();
        }
    }

    //绘制粒子文字
    function draw() {
        ctx.clearRect(0, 0, w, h);
        for (let n of particles) {
            n.update()
        }
    }

    //每帧执行的粒子变化动画
    function animation() {
        // 相当于每一帧都会执行一次方法
        window.requestAnimationFrame(animation)
        draw()
    }
    animation()

    //默认显示粒子文字为 input
    changeText("❤");
</script>

</html>

推荐阅读更多精彩内容

  • 都说久病床前无孝子,现在社会的人都现实得很,是金钱面前无孝子!! 外婆一生劳碌,抚养了三个女儿一个儿子,又...
    丁香菇凉阅读 148评论 0 0
  • Forexample 2016-09-30 13:43 moment 时间转化插件 缺点是包有点大
    可爱的木头阅读 49评论 0 0
  • 妈妈说我单纯的像一张白纸,是一件好事情。可我理解不透“单纯”是什么意思,一天天快乐成长着,爸爸、妈妈和我给...
    Kitty乐园阅读 48评论 0 1
  • 我老公是不会主动和我有任何链接的人,他宁可不要关系,也要躲起来。所以我们的相处常常是我实在受不了忽视闹一下,闹到差...
    安于不安阅读 23评论 0 1