资讯专栏INFORMATION COLUMN

程序员的小浪漫----文字粒子效果

xiaodao / 1222人阅读

摘要:预览完整项目预览预览地址粒子效果原理在中,可以通过方法来获取像素数据。下例是通过改变像素的数据而重新写出来的文字。不过可能会造成文字部分地方缺失。烟花效果可以看一下我的上一篇,程序员的小浪漫烟火完整项目项目地址如果觉得还不错,请一个吧。

预览

完整项目预览----预览地址;

粒子效果原理

在canvas中,可以通过getImageData()方法来获取像素数据。

ctx.fillStyle = "#ff0000";
ctx.fillRect(0, 0, 1, 1);
const imageData = ctx.getImageData(0, 0, 1, 1);

imageData有三个属性:

data:数组,包含了像素信息,每个像素会有四个长度,如[255,0,0,255, ... ,255,127,0,255],分别代表该像素的RGBA值。

widthimageData对象的宽。

heightimageData对象的高。

首先在canvas写上某种颜色文字,再去分析像素数据(比如改像素是否有透明度等),然后自己记录下该像素点的位置

下例是通过改变像素的数据而重新写出来的文字。

ctx.font = "bold 40px Arial";
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillText("你好啊", 60, 20);

document.querySelector("#button").addEventListener("click", function(){
    const imgData = ctx.getImageData(0, 0, 120, 40);
    for(let i = 0;i < imgData.data.length; i+=4){
        if(imgData.data[i + 3] == 0) continue;
        imgData.data[i] = 255;
        imgData.data[i + 1] = 0;
        imgData.data[i + 2] = 0;
        // imgData.data[i + 3] = 255;  这个代表的是透明度 透明度不变 255最高 0最低
    }
    ctx.putImageData(imgData,120,0);
});

这段代码只是示例说明一下,实际上才不会有人这么脑残去换颜色吧。

获取点位置

要获取点的位置,首先要将字写在画布上,但是字又不能让别人看到。所以可以动态创建一个画布,这个画布不会append到任何节点上,只会用于写字。

const cache = document.createElement("canvas");

将宽高等与展示的画布设置成一样的。(不贴这部分的代码了)

创建一个对象,用于获取点的位置

const ShapeBuilder = {
    //初始化字的对齐方式等,我认为middle 与 center比较好计算一点
    init(width, height){
        this.width = width;
        this.height = height;
        this.ctx = cache.getContext("2d");
        this.ctx.textBaseline = "middle";
        this.ctx.textAlign = "center";
    },
    //获取位置之前必须先要写入文字。 这里的size=40是默认值
    write(words, x, y, size = 40){
        //清除之前写的字。
        this.ctx.clearRect(0, 0, this.width, this.height);
        this.font = `bold ${size}px Arial`;
        this.ctx.fillText(words, x, y);
        //记录当前文字的位置,方便计算获取像素的区域
        this.x = x;
        this.y = y;
        this.size = size;
        this.length = words.length;
    },
    getPositions(){
        //因为imgData数据非常的大,所以尽可能的缩小获取数据的范围。
        const xStart = this.x - (this.length / 2) * this.size, 
            xEnd = this.x + (this.length / 2) * this.size,
            yStart = this.y - this.size / 2, 
            yEnd = this.y + this.size / 2, 
            
            //getImageData(起点x, 起点y, 宽度, 高度);
            data = this.ctx.getImageData(xStart, yStart, this.size * this.length, this.size).data;
            
        //间隔 (下面有介绍)
        const gap = 4;
        
        let positions = [], x = xStart, y = yStart;
        
        for(var i = 0;i < data.length; i += 4 * gap){
            if(data[i+3] > 0){
                positions.push({x, y});    
            }
            
            x += gap;
            
            if(x >= xEnd){
                x = xStart;
                y += gap;
                i += (gap - 1) * 4 * (xEnd - xStart);
            }
        }
        return positions;
    }
}

ShapeBuilder.init();

关于gap:在循环imgData数组的时候,数据量太大可能会造成卡顿,所以可以使用间隔来获取坐标点的方法。不过可能会造成文字部分地方缺失。就需要个人来权衡利弊,自己来调整了。

gap的值必须能被xEnd-xStart给整除,不然会造成获取坐标点错位的后果。

关于canvasmiddlecenter的规则:

this.ctx.font = "bold 40px Arial";
this.ctx.fillText("你好",40 ,20);

效果如下图所示

fillText设置的坐标点刚好会是整个字的中点,就是图中middlecenter的交点。其实以其它对齐方式也是可以的,看个人喜好。

更多的对齐规则参考HTML 5 Canvas 参考手册的文本。

创建微粒类

微粒应该随机生成,然后移动到指定的位置去。

微粒类的属性:

自身当前位置(x,y), 目标位置:(xEnd,yEnd),自身大小(size),自身颜色(color),移动快慢(e)

方法:go():每一帧都要移动一段距离,render():渲染出微粒(我用心形的形状)

class Particle {
    constructor({x, y, size = 2, color, xEnd, yEnd, e = 60} = {}){
        this.x = x;
        this.y = y;
        this.size = size;
        this.color = color ||  `hsla(${Math.random() * 360}, 90%, 65%, 1)`;
        this.xEnd = xEnd;
        this.yEnd = yEnd;
        
        //经过e帧之后到达目标地点
        this.e = e;
        //计算每一帧走过的距离
        this.dx = (xEnd - x) / e;
        this.dy = (yEnd - y) / e;
    }
    go(){
        //到目的后保持不动 (其实这里也可以搞点事情的)
        if(--this.e <= 0) {
            this.x = this.xEnd;
            this.y = this.yEnd;
            return ;
        }
        this.x += this.dx;
        this.y += this.dy;
    }
    render(ctx){
        this.go();
        //下面是画出心型的贝塞尔曲线
        ctx.beginPath();
        ctx.fillStyle = this.color;
        ctx.moveTo(this.x + 0.5 * this.size, this.y + 0.3 * this.size);
        ctx.bezierCurveTo(this.x + 0.1 * this.size, this.y, this.x, 
                        this.y + 0.6 * this.size, this.x + 0.5 * 
                        this.size, this.y + 0.9 * this.size);
        ctx.bezierCurveTo(this.x + 1 * this.size, this.y + 0.6 * 
                        this.size, this.x + 0.9 * this.size, this.y, 
                        this.x + 0.5 * this.size,
                        this.y + 0.3 * this.size);
        ctx.closePath();
        ctx.fill();
        return true;
    }
}

微粒类最基本的属性与方法就是这些,如果要让粒子更好看一点,或者更生动一点,可以自己添加一些属性与方法。

具体流程
const canvas = {
    init(){
        //设置一些属性
        this.setProperty();
        //创建微粒
        this.createParticles();
        //canvas的循环
        this.loop();
    },
    setProperty(){
        this.ctx = studio.getContext("2d");
        this.width = document.body.clientWidth;
        this.height = document.body.clientHeight;
        this.particles = [];
    },
    createParticles(){
        let dots;
        //ShapeBuilder.write(words, x, y, size)
        ShapeBuilder.write("每个字都是",this.width / 2, this.height / 3, 120);
        dots = ShapeBuilder.getPositions(6);
        ShapeBuilder.write("爱你的模样", this.width / 2, this.height * 2 / 3, 120);
        dots = dots.concat(ShapeBuilder.getPositions(6));
        //dots已经获取到了字的坐标点 
        //每一个微粒的目标地点都是dots的坐标
        //每一个微粒都随机出生在画布的某个位置
        for(let i = 0; i < dots.length; i++){
            this.particles.push(new Particle({
                xEnd:dots[i].x, 
                yEnd:dots[i].y , 
                x: Math.random() * this.width, 
                y: Math.random() * this.height, 
                size:6, 
                color:"hsla(360, 90%, 65%, 1)"
            }));
        }
    },
    loop(){
        //每一帧清除画布,然后再渲染微粒就可以了
        requestAnimationFrame(this.loop.bind(this));
        this.ctx.clearRect(0, 0, this.width, this.height);
        for(var i = 0; i < this.particles.length; i++){
            this.particles[i].render(this.ctx);
        }
    }
}

canvas.init();

如果想要给每个粒子加上小尾巴的话,那么在每一帧的时候,就不要清除画布,而且覆盖一层有透明度的底色。

//修改loop方法
//this.ctx.clearRect(0, 0, this.width, this.height);
this.ctx.fillStyle = "rgba(0,0,0,0.2)";
this.ctx.fillRect(0, 0, this.width, this.height);

这样的话会变成如下效果

最后

在这这篇文章的时候,并没有注意太多细节,比如gap应该是可以被设置的,或者是一个被特殊标注的常量,而不应该随便写在方法中。对于本例的代码,切勿生搬硬套,重要的是要理解原理,以及自己亲自动手尝试

我也是在写这篇文章的过程中,才发现了之前获取position一个不精准的地方。

这里只讲了粒子效果最基础的用法,实际上还可以做出很多非常炫酷的效果

比如在粒子到达目的地后还可以抖动什么的

粒子形状、颜色的变化等等。

这个项目还可以搞很多事情的,大家也可以自己多来尝试弄些更加炫酷的效果。

烟花效果可以看一下我的上一篇,程序员的小浪漫----烟火

完整项目

github项目地址

如果觉得还不错,请star一个吧。

参考项目

github上的一个项目---- shape-shifter

这个项目我觉得非常不错,可惜作者都消失好多年了。

codepen.io 上的一个作品 ---- Love In Hearts

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92920.html

相关文章

  • 序员小浪----烟火

    摘要:多代码,慎读预览完整项目预览预览地址属性设计烟花状态烟花应有三个状态升空等待炸裂炸裂后烟花发射点,爆炸点,升空后等待炸裂时间,炸裂后微粒个数,烟花半径烟花炸裂后微粒自身位置,自身大小,自身速度,最大烟花半径。 多代码,慎读!!! 预览 showImg(https://segmentfault.com/img/remote/1460000013324854?w=349&h=423); 完...

    roundstones 评论0 收藏0
  • Canvas 实现炫丽粒子运动效果粒子生成文字

    摘要:代码实现炫丽的粒子运动效果云库前端散开类型归位随机散开效果对归位有效输入汉字后回车代码不多,只要是几个操作元素。看起来运行顺畅的代码也或多或少有一些瑕疵,日前这个效果还只支持中文。 没有最好,只有更好,如题所示,这篇文章只要是分享一个用 Canvas 来实现的粒子运动效果。感觉有点标题党了,但换个角度,勉勉强强算是炫丽吧,虽然色彩上与炫丽无关,但运动效果上还是算得上有点点炫的。不管怎么...

    icattlecoder 评论0 收藏0
  • 《每周一点canvas动画》—— 文字粒子

    摘要:代码文件每周一点动画系列文章目前已经更新了篇,今天给大家发个福利。粒子的位置为,我们作为参数传入。粒子切换粒子切换的代码在中,很简单,就是绑定了两个事件。 代码文件 每周一点canvas动画系列文章目前已经更新了12篇,今天给大家发个福利。我们使用canvas来制作一个小的效果。这个小效果是我从codePen上看到的,我对其做了些修改增强,添加了一些新的功能。UI界面就如下图中看到的样...

    Riddler 评论0 收藏0

发表评论

0条评论

xiaodao

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<