前言
前几天在整理书签时,发现收藏了好多宝藏网站,宝藏项目,就光说文字效果,就让诸位大佬玩出了一万个花样,所以阿包准备跟大家一起来分享和学习这些异彩纷呈的创意和写法。
该文作为《文字到底能玩出多少花样》的第一篇文章,首先来带大家学习 文字粒子 的实现。
文字粒子这个创意最先源自于哪位大佬我也不知道,但我最早发现这个效果是 kennethcachia
写的巨炫酷的文字粒子效果,每次我打开这个 demo
,我都感觉我是个审美笨比,大佬做的真炫酷,真漂亮。
下面是 shape-shifter
效果的 github
地址和 demo
演示界面:
不多说了,一起进入阿包的文字粒子效果吧。
基础知识
文章最开始之前,先带大家了解两个 canvas
函数
fillText
语法
ctx.fillText(text, x, y, [maxWidth]); 复制代码
参数
text
: 规定在画布上输出的文本。x
: 文本起点的x
轴坐标。y
: 文本起点的y
轴坐标。maxWidth
: 可选,绘制的最大宽度。
可以通过 font
属性来定义字体和字号,fillStyle
属性来定义字体颜色。
getImageData
语法
ctx.getImageData(sx, sy, sw, sh); 复制代码
参数
sx, sy
: 要提取图像的左上角x y
坐标sw, sh
: 要提取图像的宽高
返回值
- 返回
ImageData
对象,该对象拷贝了指定图像区域的像素 - 对于图像中每个像素点,都分别存放
RGBA
四方面的信息,所有的像素数据以一维数组形式存放在data
属性中
绘制思路
在基础知识方面,我首先介绍了两个方法: fillText
和 getImageData
。 fillText
可以在画布上绘制文字,getImageData
可以提取画布上一定矩形区域的像素点。也就是说,通过这两个方法就可以提取到我们绘制的文字的像素数据。那剩下的工作就是怎么处理这些像素数据,绘制对应运动粒子就可以。
下面来分析一下具体实现思路:
- 绘制主画布: 作为文字粒子的渲染画布
- 生成副画布: 作为绘制文字的画布,不需要添加到页面中,作为提取绘制文字像素的载体
- 生成文字粒子: 根据获取到的像素点,渲染粒子
- 文字粒子运动轨迹: 计算每个粒子的移动轨迹
- 启动动画
初始化工作
设立配置项 options
和 textOptions
,分别存放画布的宽高、速度等信息和副画布文字的配置。
const options = { width: 400, height: 400, speed: 10 }; const textOptions = { words: "战场小包", font: "200px fangsong", }; 复制代码
绘制主画布
主画布已经通过 标签添加至页面中,因此只需要配置它的 width
与 height
,为了方便展示,将 canvas
宽高设置为浏览器宽高
function pointCanvas(canvas, { width, height }) { canvas.width = width; canvas.height = height; ctx = canvas.getContext("2d"); return ctx; } // pointCavas(canvas, options) 复制代码
创建副画布
副画布的创建也非常简单,只需要通过 JavaScript
创建一个 canvas
节点,配置副画布的宽高(与主画布相同),不需要添加到 dom
中。
副画布与主画布的宽高保持一致,这样副画布中的像素点,就可以对应到主画布中的像素位置,无需进行转换
function createVitualCvs({ width, height }) { const vitualCvs = document.createElement("canvas"); vitualCvs.width = width; vitualCvs.height = height; let vitualCxt = vitualCvs.getContext("2d"); initCanvas(vitualCxt, options, textOptions); return getFontInfo(vitualCxt, options); } 复制代码
初始化副画布
设置文字属性,通过 fillText
绘制文字。通过 (canvas.width - measure.width) / 2
保证在画布中间绘制文字。
measureText
可以获取到文字的宽高,更详细的参考链接: measureText
function initCanvas(ctx, { width, height }, { font, words }) { ctx.font = font; const measure = ctx.measureText(words); ctx.fillText(words, (width - measure.width) / 2, height / 2); } 复制代码
构建粒子类
每个粒子有以下属性:
x
: 粒子最终停留的位置x
坐标y
: 粒子最终停留的位置y
坐标mx
: 初始化时粒子的x
坐标位置my
: 初始化时粒子的y
坐标位置radius
: 粒子大小speed
: 粒子移动速度
同时还具备两个方法:
draw
: 绘制粒子本身的方法update
: 更新粒子位置的方法
这里给出 draw
和 update
方法代码
draw() { ctx.beginPath(); ctx.fillStyle = this.color; ctx.arc(this.mx, this.my, this.radius, 0, Math.PI * 2, false); ctx.fill(); this.update(); } // 我这里的 update 比较简单,如果想要更复杂更炫酷的效果,可以使用贝塞尔曲线 update() { this.mx = this.mx + (this.x - this.mx) / this.speed; this.my = this.my + (this.y - this.my) / this.speed; } 复制代码
获取文字位置信息,绘制粒子
通过 getImageData
获取副画布的像素信息,getImageData
返回值中对每个像素点存储 RGBA
四方面信息,因此每四个数组元素代表一个像素点,可以通过 Alpha
来判断该像素点是否存在文字。
function getWordPxInfo(ctx, { width, height }) { 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 pxAlphaIndex = (x + y * width) * 4 + 3; if (imageData[pxAlphaIndex] > 0) { particles.push( new Particle({ x, y, }) ); } } } return particles; } 复制代码
启动动画
动画是通过 requestAnimationFrame
实现,每一帧清空画布,重新绘制所有的粒子。
requestAnimationFrame学习: 【今天你更博学了么】一个神奇的前端动画 API requestAnimationFrame
function init(points, { width, height }) { ctx.clearRect(0, 0, width, height); points.forEach((value) => { value.draw(); }); const timer = window.requestAnimationFrame(function () { init(points, options); }); } 复制代码
大功告成
大功告成,摸鱼成功。
源码仓库
传送门: 文字粒子