您现在的位置是:首页 > 文章列表 > JavaScript > 100行代码实现canvas文字粒子效果,简单易懂。

100行代码实现canvas文字粒子效果,简单易懂。

linds 2020-08-08 16:46 247人围观
简介 ​canvas是使用JavaScript程序绘图(动态生成),相比于css,可以更加简单方便的绘制细节的样式。其中最强大的功能莫过去像素的处理。一个像素一个像素去绘制任何想要的展示效果
v2-cce139fdc8fb57be7d90144f87b212a9_b.gif

canvas是使用JavaScript程序绘图(动态生成),相比于css,可以更加简单方便的绘制细节的样式。其中最强大的功能莫过去像素的处理。一个像素一个像素去绘制任何想要的展示效果。接下来,要为各位去介绍一下文字动态粒子效果,当然是一些比较简单。

如何绘制文字粒子动态效果?

1.了解一下基本的canvas的Api,像画点,画圆,以及填充颜色等等。2.文字打碎,记录每个文字所在画布中的位置,本文的重点。3.生成随机粒子,并且设置每个粒子的运动轨迹。4.移动到步骤二记录下来位置。5.使用requestAnimationFrame来绘制每一帧的画布

就这么简单,只要100行代码,就能学会简单的文字动态效果

源码解析

主生成画布

获取文字位置信息,还不想让用户看到,这就需要用到两个画布了,下面是创建主画布,设置画布的大小。

   let WIDTH,HEIGHT,cxt,raf,points;
   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(); // 创建副画布,写出想展示的文字,并且获取文字的位置信息。       init()
   }

生成副画布

创建一个副画布,里面写入想要展示的文字,获取到文字粒子的位置。这里要注意了,主画布和副画布大小要一样,这样副画布里面的点位,才能正确的在主画布中展示。副画布创建好后,无需添加到dom中。这里写入了文字 www

function createViceCanvas() {
    const viceCanvas = document.createElement('canvas')
    viceCanvas.width = WIDTH;
    viceCanvas.height = HEIGHT;
    let viceCxt = viceCanvas.getContext('2d')
    initCanvas(viceCxt)
    return getFontInfo(viceCxt); // 获取文字粒子的位置信息}function initCanvas(ctx){ //ctx 是副画布    const font = 'www'
    ctx.font = '200px Arial';
    const measure =  ctx.measureText(font)
    ctx.fillText(font, (WIDTH - measure.width) / 2, HEIGHT / 2);}


这里使用了方法measureText,获取文字的宽度,为了能够在画布中间绘制文字。高度居中,感兴趣的可以自行尝试。。

获取文字位置信息

如何获取文字的位置?上课了,划重点。

function getFontInfo(ctx) { //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;}

data属性返回一个 Uint8ClampedArray,它可以被使用作为查看初始像素数据。每个像素用4个1bytes值(按照红,绿,蓝和透明值的顺序; 这就是"RGBA"格式) 来代表。每个颜色值部份用0至255来代表。每个部份被分配到一个在数组内连续的索引,左上角像素的红色部份在数组的索引0位置。像素从左到右被处理,然后往下,遍历整个数组

我这里使用的画布大小是 1080 * 768, 用坐标系来表示就是x轴1080,y轴768

其实就是RGBA(255,255,255,0) 这四个类似的数字表示一个像素,那1080*768这个画布用Uint8ClampedArray数组表示,总共由多少个元素呢?就是1080 * 768 * 4 个元素

下面画了一张简陋的坐标图。

比如x轴(1,1)这个位置,需要用Uint8ClampedArray数组的前四位表示. x轴(2,1)这个位置,需要用Uint8ClampedArray索引4-7的元素表示。 那坐标(1,2)第一位对应表示Uint8ClampedArray索引就是(1080*(2-1) + (1-1)) * 4 -1 . 坐标(m,n)首位索引对应的就是(width*(n-1) + m-1)) * 4 -1。 不懂的观众姥爷可以慢慢品一下。~~~~~

这里还有一个小技巧,rgba表示的颜色,第四个元素表示透明度,当我们画布上并未绘制内容时,第四个元素位0。所以,源码中const fontIndex = (x + y * WIDTH) * 4 + 3 取到透明度不为0时候,则证明当前像素是有内容的,即可获取到文字在画布中的位置。

每个粒子的移动轨迹

上面一步获取了文字粒子在画布中的位置,我们想要的效果,是粒子动画, 则我们需要在随机生成一个粒子, 然后移动到对应的获取到的文字位置。

   class Particle {
       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坐标       }
       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="red"
           ctx.fill();
           ctx.closePath();
           this.speed(); // 点位下次tick绘制时的坐标       }
       speed() { // 每个点位绘制后的坐标           this.initX +=this.vx;
           this.initY +=this.vy;
           this.item += 0.005;
       }
   }

这里,需要一个随机粒子, 用来做移动,并且最后要组成一个文字,文字的最终位置我们已经获取到了,就是constructor的参数center。那粒子该怎么运动呢?我这里使用的贝塞尔曲线,并且封装成了一个方法。

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,
       }
   }

简单的对参数说明

t:贝塞尔曲线系数,0-1之前。p1: 轨迹移动的起点。p2: 轨迹移动的终点。cp1: 第一个控制点。cp2: 第二个控制点。第四个参数和第五个参数可以瞎鸡儿传,主要是控制运动的轨迹,但是p1,p2这俩参数不能乱,尤其是p2。p2要是瞎鸡儿传,还想组成文字吗?

什么,不懂贝塞尔曲线?还不戳这里?)

扩展:如果文字想要五颜六色,可以在获取文字坐标时,

particles.push(new Particle({
           x,
           y,
       }))

随机朝构造器中加入一个颜色, 在Particle类中draw方法绘制时,赋值传入的颜色。

启动动画

文字位置,粒子运动轨迹也确定好了,下面该如何开启动画?如何暂停动画?

function init() {
    ctx.clearRect(0,0,WIDTH,HEIGHT)
    points.forEach((value) => { //        value.draw();
    })
    raf = window.requestAnimationFrame(init)
    if(points[0].item>=1){
        window.cancelAnimationFrame(raf)
    }}

requestAnimationFrame小记:cancelAnimationFrame取消动画,要跟在requestAnimationFrame后面。保证在递归调用init方法之前去执行cancelAnimationFrame。别忘了requestAnimationFrame是个异步~~~~

阅读排行

  • Vue 的最佳使用方法

    Vue框架,是我国内最受欢迎的前端框架之一,也许你的项目正在使用,也许你还在了解正准备上手...

  • FastClick是什么?一文搞懂原理解析​

    FastClick是什么?一文搞懂原理解析​ 为什么要用FastClick在移动端H5开发过程中,关于点触可能会遇到如下两个问题:手动点击与真正...

  • 提升CSS技能的20个小技巧

    提升CSS技能的20个小技巧 随着前端开发越来越关注效率:通过选择器的使用和简化代码来快速加载渲染。像Less、SCSS这样...

  • uni-app介绍

    uni-app介绍 uni-app是一个使用Vue.js开发跨平台应用的前端框架,开发者编写一套代码,可编译到iOS、Andr...

  • 一文彻底弄懂Event Loop!

    一文彻底弄懂Event Loop! 我们在学习浏览器和NodeJS的EventLoop时,往往是阅读大量文章,多篇文章凑在一起综合来看,才...

  • 生活中常用正则表达式

    生活中常用正则表达式 很多不太懂正则的朋友,在遇到需要用正则校验数据时,往往是在网上去找很久,结果找来的还是...

  • vue组件通信你了解多少

    本文会介绍常见的通信方式,并分析每种方式的使用场景和注意点。

  • 10 种跨域解决方案

    10 种跨域解决方案 说到跨域了,这是一个老生常谈的话题,以前我觉得这种基础文章没有什么好写的,会想着你去了...

本栏推荐

官方微信