用粒子动画来忆起你的春节时光 | 支持表情文字

简介: 用粒子动画来忆起你的春节时光 | 支持表情文字

前言


俗话说:过了腊八就是年。腊八节过完,新春已经踩着风火轮追来了!你期待今年的新春吗?


除夕、春节,是一年间最重要的几个节日,也是难得阖家大团圆的美妙之日,回忆起过往的新春佳节,小包就不由思绪万千,有快乐,有开心,有向往,有苦涩,春节的时光短暂却又永恒,深深篆刻在小包的记忆之海。


不知道你记忆中的新春会是什么样子那?接下来首先以几段关键词动画,来领略一下小包的新春回忆吧。


欢迎大佬们在评论区中评论,分享你的新春记忆关键词或关键词动画,我们一起来体悟或者猜测你的新春回忆,点赞+评论最多的掘友小包会送他掘金周边一份


工具


在分享案例玩法和实现之前,先提供几个工具,方便大家定制自己的新春回忆。



关于录屏这里,小包其实本来预想是实现生成动图功能,找了好多方案,实现效果都比较差,后面小包会继续推进这方面的研究,如果有进展会继续更新后续研究。


玩法


体验地址: 定制你的专属新春回忆


玩法非常简单,大致就这几点注意事项:


  • 关键词输入在下划线位置
  • 多个关键词可通过空格或者中英文逗号分隔
  • 支持中英文、表情文字
  • 文字尽量不要太长,五字以内
  • 输入关键词序列后,回车键开启动画


下面先来看看小包的春节回忆吧。


童年


童年时光已经离小包有些遥远了,那个年代物质生活不算丰富,精神生活比较匮乏,但架不住小包当时单纯啊,翻山越岭,爬山跨海,上天入地,给小包留下了无数刻骨铭心的回忆,青山、近邻、小河、伙伴,简单的生活创造无尽的美好。


除夕夜


那时代的也是年味十足,除夕夜小包一家三口,吃着水饺,看着春晚,热情似火,初一零时,月夜小桥,爆竹声声,红包鼓鼓。关键词: 👨‍👩‍👦,🥟,📺,👏,🕛,🌉,🧨,🧧


image.png

春节


春节的日子就更充实了,一家三口,六点起床,大红灯笼,七点拜年,走家串巷,些许人群,糖果花生,快乐逍遥。演化成关键词:👨‍👩‍👦,🕕,🏮,🕖,🤝,👨‍👨‍👦‍👦,🍬,🥜,🥰


image.png

青春


后续部分的表情关键词就不做解读了,大家可以猜一下,发在评论里面,最贴近的会有掘金周边送出


关键词: 👨‍👨‍👧‍👦,🥟,📺,🕛,广场,🧨,🎇,🙏


image.png

疫情


说实话,随着年龄的增长,物质生活和精神生活都迅速发展,社会的浮躁气息愈来愈浓,年味也越来越淡,但疫情那年,会成为小包永恒的记忆,那年年味有可能更淡了,但情味却浓上加浓,没有什么困难可以战胜中华民族。


关键词: 👨‍👨‍👧‍👦,📺,🦠,🛌,🥼,🦸🏻,🏆,致敬


image.png

代码实现


粒子效果其实小包已经实现过多次,所以简单部分小包就不做详细讲解,核心讲解动画切换部分。


创建粒子类


粒子主要包含粒子随机位置、粒子目标位置、粒子颜色和粒子半径。本项目中粒子半径统一设置为2。


class Particle {
  constructor({ x = 0, y = 0, tx = 0, ty = 0, radius = 2, color = "#F00000" }) {
    // 当前坐标为随机生成坐标
    this.x = particle.x;
    this.y = y;
    // 目标点坐标(副画布原有粒子坐标)
    this.tx = tx;
    this.ty = ty;
    this.radius = 2;
    this.color = color;
  }
 // 粒子绘制   
  draw(ctx) {
    ctx.save();
    ctx.translate(this.x, this.y);
    ctx.fillStyle = this.color;
    ctx.beginPath();
    // 绘制圆形
    ctx.arc(0, 0, this.radius, 0, Math.PI * 2, true);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
    return this;
  }
}
复制代码


提取像素信息


创建副画布及绘制文字


将要生成粒子的文字渲染到副画布上,副画布是虚拟画布,只做提取文字像素的载体使用,不会渲染到页面中。


const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const viewWidth = window.innerWidth * 0.5;
const viewHeight = window.innerHeight * 0.5;
canvas.width = viewWidth;
canvas.height = viewHeight;
// 预留处理图片的接口
if (typeof target === "string") {
    // 绘制文字
    // 保证长文字的在PC端及移动端的完整展示
    // 处理的有几分粗糙
    ctx.font = `${
          target.length < 3 ? textWidth : (textWidth * 3) / (1 + target.length)
        }px bold`;
    // 设置文字的基本样式
    ctx.fillStyle = colorList[rand(0, colorList.length)];
    ctx.textBaseline = "middle";
    ctx.textAlign = "center";
    ctx.fillText(target, viewWidth / 2, viewHeight / 2);
}
复制代码


获取像素点数据


重点介绍一下getImageData方法


  • 语法ctx.getImageData(sx, sy, sw, sh);
  • 参数
  • sx, sy: 要提取图像的左上角 x y 坐标
  • sw, sh: 要提取图像的宽高
  • 返回值
    返回 ImageData 对象,该对象拷贝了指定图像区域的像素。对于图像中每个像素点,都分别存放 RGBA 四方面的信息,所有的像素数据以一维数组形式存放在 data 属性中(每个像素点由 rgba 四个值组成,因此每次需要乘以 4 才能跳到下一个像素点)


const { data, width, height } = ctx.getImageData(0, 0, viewWidth, viewHeight);
复制代码


image.png

由上图可见,imageData 中包含四个属性,上面我们使用了 width height datadata 中存放了巨量的像素点数据,以我们这个案例为例子,第一个关键词就包含了 737280 个数字,因此我们需要根据一定的算法来进行像素筛选。


image.png

提取绘制粒子的像素点


这里解释一下 interval 的作用以及为什么可以实现对像素点数量的控制?


getImageData 提取画布的全部像素点,如上图 data 所示,一共提取了 737280 / 4 = 184320 个像素点,如果不经筛选全部绘制成半径为 2 的粒子,一方面粒子会发生重叠;另一方面如果生成 18万 个粒子,为了保证粒子运动的流畅性,使用 requestAnimationFrame 更新粒子位置,如此巨大数量的粒子频繁渲染对电脑的渲染性能要求极高,就比如小包的电脑,就完全无法运行,卡顿感爆棚。


因此我们需要寻找一个办法,即能适配各种各样的显示屏,又能要渲染恰到好处的粒子数,那我们应该怎么处理 getImageData 返回数据那?


大佬们想出一个很好的办法,画布的 width, height 通过 getImageData 同步获得,因此我们可以把画布看作 width * height 个宽高都为 1 的网格构成。我们在这个网格中取数据,interval 为选取间隔,每隔 interval 选取一个像素点,

interval 值越大,间隔越大,所以每一行中选取的像素点就越少。(如果 interval 选取太大,选区的像素点就越少,文字有可能会发生缺失,因此我们要选取合适的 interval)


接下来就是如何定位到这个像素点?getImageData 返回的数据是按行来读取像素点,每个像素点使用四个数组位来存放。如果我们以宽高为基准,那么纵坐标 y * 画布宽度 + 横坐标 x得出像素点位置,然后乘以4计算出在 ImageData 数组的索引位置。横坐标 x 和纵坐标 y 即是粒子的目标位置


// 获取目标对象的像素点,interval 控制像素点数量,值越大返回的像素点越少
  const pixeles = [];
  // 遍历像素数据,用interval减少取到的像素数据
  for (let x = 0; x < width; x += interval) {
    for (let y = 0; y < height; y += interval) {
      const pos = (y * width + x) * 4; 
      // 只提取 rgba 中透明度大于0.5的像素,即aplha > 128
      if (data[pos + 3] > 128) {
        pixeles.push({
          x,
          y,
          rgba: [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]],
        });
      }
    }
  }
  return pixeles;
}
复制代码


绘制粒子


根据合适 interval 选取的像素点对应创建粒子,粒子的目标位置和颜色由像素点提供,当前位置通过随机数生成。


function createParticles({ text, radius, interval }) {
  const pixeles = getWordPxInfo(text, interval);
  return pixeles.map((particle) => {
    return new Particle({
      x: Math.random() * (50 + window.innerWidth * 0.5) - 50,
      y: Math.random() * (50 + window.innerHeight * 0.5) - 50,
      tx: particle.x,
      ty: particle.y,
      radius: particle.raduis,
      color: particle.rgba,
    });
  });
}
复制代码


生成的粒子如下图分布:


image.png

文字切换


每次更新粒子位置后需要去判断是否所有的粒子到达目标,如果所有粒子都完成运动,则进入下一个文字动画。因此文字切换的难点在于如何检测所有的粒子是否完成运动?


每个粒子上有 finished 属性,默认值为 false。规定粒子当前位置距离目标位置小于 0.1 代表当前粒子运动结束,当前粒子运动结束后修改其 finished 值为 true;如果大于 0.1 ,粒子继续运动。(粒子运动采取的缓动系数为0.09)

由于每个粒子上都具备 finished 属性,我们可以通过 filter 过滤出所有 finished 属性为 true 的粒子,如果过滤出粒子数量等于总粒子数量,当前文字动画结束,切换下一个文字。


// 参数分别是粒子数组及下一个文字动画回调
function drawFrame(particles, finished) {
  // 开启粒子渲染动画
  const timer = window.requestAnimationFrame(() => {
    drawFrame(particles, finished);
  });
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // 缓动系数设置为0.09
  const easing = 0.09;
  const finishedParticles = particles.filter((particle) => {
    // 当前坐标和目标点之间的距离
    const dx = particle.tx - particle.x;
    const dy = particle.ty - particle.y;
    // 粒子移动速度
    let vx = dx * easing;
    let vy = dy * easing;
    // 判断当前粒子是否完成动画
    if (Math.abs(dx) < 0.1 && Math.abs(dy) < 0.1) {
      // 完成动画位置不会在改变,并修改 finished 为 true  
      particle.finished = true;
      particle.x = particle.tx;
      particle.y = particle.ty;
    } else {
      // 未完成动画继续更新粒子位置   
      particle.x += vx;
      particle.y += vy;
    }
    // 绘制粒子新位置   
    particle.draw(ctx);
    // 判断返回完成运动的的粒子数
    return particle.finished;
  });
  if (finishedParticles.length === particles.length) {
    // 全部粒子运动结束,结束当前动画
    window.cancelAnimationFrame(timer);
    // 开启下一轮文字回调
    finished && finished();
  }
  return particles;
}
复制代码


代码实现到这里,就可以实现当前文字的动画效果及动画效果结束判断,我们只需要在当前动画效果结束后,执行 finished 回调即可实现切换。


动画切换函数 loop


loop 函数设计为了给 drawFrame 提供下一个文字的渲染。代码逻辑比较简单,不在多言。


function loop(words, i = 0) {
  return drawFrame(
    // 生成粒子
    createParticles({ text: words[i], radius: 2, interval: 5 }),
    // finish函数部分=》下一个文字
    () => {
      i++;
      // hack一下空文字
      if (i < words.length && words[i].length > 0) {
        loop(words, i);
      }
    }
  );
}
复制代码


虎年送大礼


说句掏心窝子的话,自从十月份在掘金开始写文,刚迈入 2022 年,小包就成功登临 LV4 ,小包对自己的认知还是特别清晰的,小包需要快速进步才能配称得上优秀作者。


但真的十分感谢大佬们的支持,在掘金这边相识了很多新的朋友,希望朋友的友谊能地久天长。另外还要感谢掘金社区的可爱运营们,负责,接地气,让小包这个大龄写手重新拥有热情,重拾初心,希望新的一年可以和掘金一起成长,一起进步。


这半年来,零零散散薅了社区不少羊毛,有些送给了朋友,送完朋友还剩一些,就送给掘金的最熟悉的陌生人吧,希望未来的日子能越来越熟悉。


  1. 小包预留俩组关键词,猜测最贴切的掘友,小包会送上掘金周边一份
  2. 分享你的新春回忆(录制动图或者关键词都可),收获到点赞和评论之后最多的掘友,小包同样也会送上掘金周边一份
  3. 别的奖项暂时还没想出,后面如果想出在动态添加上


源码仓库


源码地址: 定制你的专属新春回忆


体验地址:定制你的专属新春回忆


如果感觉有帮助的话,别忘了给小包点个 ⭐ 。




相关文章
|
4月前
|
前端开发
HTML+CSS制作七夕跳动的红心动画效果
HTML+CSS制作七夕跳动的红心动画效果
|
11月前
|
前端开发 JavaScript
2023跨年代码(烟花+背景音乐)
2023跨年代码(烟花+背景音乐)
272 0
|
前端开发 程序员 API
教你实现微信8.0『炸裂』的礼花表情特效
作为一个前端程序员,这就勾起了我的好奇心,虽然我从来没有实现过这样的动画,但是我还是忍不住想要去实现,最终我花了2天时间去看一些库的源码到我自己实现一个类似的效果,在这里我总结一下,并且手把手地教大家怎么学习实现。而🎉有一个自己的名字,叫做五彩纸屑,英文名字叫 confetti。
教你实现微信8.0『炸裂』的礼花表情特效
|
前端开发 JavaScript
2023跨年烟花(浪漫烟花+美妙音乐+雪花飘飘)含前端源码直接下载---系列最终篇
2023跨年烟花(浪漫烟花+美妙音乐+雪花飘飘)含前端源码直接下载---系列最终篇
416 0
2023跨年烟花(浪漫烟花+美妙音乐+雪花飘飘)含前端源码直接下载---系列最终篇
逐帧动画案例(奔跑的小人)
逐帧动画案例(奔跑的小人)
184 0
逐帧动画案例(奔跑的小人)
|
存储 程序员
七夕快到了,用SwiftUI做一个表达爱意的心形动画
传统的七夕快到了,作为一个程序猿,最浪漫的礼物当然是自己写的啦! 思来想去也不知道写什么好,在某天在某音上学习时看到点赞的动画效果还不错,那不如就做一个表达爱意的动画吧。
289 0
七夕快到了,用SwiftUI做一个表达爱意的心形动画
|
JavaScript
💃接着奏乐!接着舞!让文字随着音乐跃动吧!
💃接着奏乐!接着舞!让文字随着音乐跃动吧!
168 0
|
数据可视化 前端开发 小程序
中秋节——我给心爱的她做了一个3d月球动画
前言 大家好,又到了周末了,又到了Fly写文章的时候了, 过几天不是中秋节了,想着之前写过一篇从0- 1 实现3D地球的,反响效果特别好, 这次趁着🎑节给大家写了一个月球绕地球的运转的动画。本篇文章还是偏入门级别,重在把简单的知识讲清楚,如果是资深three爱好者,可以直接划走了,不浪费大家时间。ok👌言归正传,读完本篇文章你可以学到什么?至于心爱的她—— 就是学习 本文阅读估计花费 5 分钟 天空盒子的制作 three.js 中的贴图 一个物体绕另一个物体旋转 初始化 这篇文章我不会在从头详细的介绍three.js 的一些要素了,如果小伙伴你不是很清楚的话,你可以直接看下我这篇文章入
中秋节——我给心爱的她做了一个3d月球动画
|
前端开发
用canvas让美女沉浸在音符的海洋里
用canvas让美女沉浸在音符的海洋里
用canvas让美女沉浸在音符的海洋里
|
存储 前端开发 JavaScript
文字到底能玩出多少花样(一) 炫酷文字粒子效果实现
文字到底能玩出多少花样(一) 炫酷文字粒子效果实现
157 0