第一次在别人的项目里看到文字瀑布流,我盯着屏幕愣了好几秒——满屏的字符像下雨一样往下落,落到底部又消失,整个页面仿佛活了过来。后来我自己用Canvas做高级动画实战复刻了一遍才发现,这个效果看起来高级,本质却非常简单:一列一列的字符,每隔十几毫秒向下移动一个字符位,仅此而已。但把它从"能跑"做到"好看",中间隔着的,是字体、颜色、速度、密度、交互、性能这一系列细节。
这篇文章我会从最基础的原理讲起,带着你从空白页开始,一步步实现一个完整的文字瀑布流。第一版只求"跑起来",第二版加入色彩和拖尾质感,第三版加鼠标交互和点击反馈,最后还会聊一聊高性能适配和踩过的坑。适合已经会一点JavaScript、想真正把Canvas动画吃透的同学,也适合想做博客背景效果、活动页开屏的开发者参考。
1. 先把原理讲透:文字瀑布流动的是什么
1.1 视觉本质:一列一列的字符在下移
文字瀑布流看上去千变万化,拆开看就是几件事:
- 把画面按字符宽度分成若干垂直列。
- 每一列有一个"当前字符位置",这个位置不断向下推进。
- 在推进的过程中,每帧在这一列画一个新的字符。
- 当字符位置超出画布底部时,这一列从顶部重新开始。
这个循环,就是一个文字瀑布流最底层的行为。
类比一下:它和真正的"雨"没有区别。雨是无数条水线从天上往下落,文字瀑布流是每一列字符按自己的速度往下掉。每一列互不干扰,掉到"地面"(画布底部)之后,雨滴消失,这一列从头再下一场。
为什么这个效果看起来很"高级"?我觉得有两点。第一,字符是随机的,所以整个画面永远没有重复的瞬间,人眼的注意力会被持续吸引。第二,叠加了拖尾效果,上一秒的字符不会立刻消失,而是逐渐变暗,产生一种类似"视觉残留"的流动感。这两点做到,效果就自然出来了。
1.2 为什么必须用Canvas:DOM方案会卡到怀疑人生
你可能会想,这个效果用DOM能不能做?每个字符一个span,然后用CSS动画或者JavaScript更新transform?理论上可以,但实际上不可行——一个常见的文字瀑布流,至少有30~60列,每列10~15个可见字符,加起来就是500~1000个DOM节点,而且这些节点每帧都在移动、新增、删除。浏览器操作DOM的代价很大,到500个节点时,FPS就会明显往下掉;到了上千个,基本就成幻灯片了。
Canvas的方案就优雅得多:整个画面是一张位图,画布上只有几个状态(列数组、位置、颜色),每帧只需要几十次fillText调用。DOM操作变成了纯粹的位图绘制,性能差距是两个数量级。
这也解释了为什么做视觉类动画,首选Canvas或者WebGL。Canvas适合这种"高频率、多对象、无序"的绘制场景,而DOM适合"少对象、事件驱动、语义化"的场景。实测下来(一台普通笔记本):
| 方案 | 可见字符数(大约) | 帧率表现 |
|---|---|---|
| DOM + CSS动画 | 300~500 | 40~50fps,偶发掉帧 |
| Canvas fillText | 1500+ | 60fps 稳定 |
| WebGL | 5000+ | 60fps 稳定 |
这个对比不是说DOM一定不能用,而是说在字符密集、高频更新的场景下,直接操作位图的成本远低于操作DOM树。WebGL当然也可以做,而且性能上限更高,但对于文字瀑布流这个具体效果,Canvas的fillText已经够用,杀鸡不必用牛刀。以后如果要做几万个粒子的效果,再考虑WebGL也不迟。
1.3 动画的引擎:requestAnimationFrame不是定时器
写Canvas动画,首先要理解一个东西:requestAnimationFrame(rAF)。很多人第一次写动画会用setInterval,比如每16毫秒执行一次绘制逻辑。这个方案能跑,但它有两个问题。
setInterval不关心浏览器是否在渲染这一帧,它只知道到点执行。如果浏览器正在处理别的任务,定时器回调会被延迟,动画会出现明显的"卡顿"。另外,标签页被切到后台时,浏览器会降低定时器频率甚至暂停,动画会变得不一致。
requestAnimationFrame是浏览器专门为动画设计的API。它和屏幕的刷新率同步(通常是60Hz,也就是每帧约16.7ms),浏览器会在每帧绘制之前调用你传入的回调。好处是:帧率与显示器同步,不会多画也不会少画;页面不可见时自动暂停,不浪费CPU;多个rAF回调会在同一帧内合并,性能更优。
所以,所有Canvas动画的循环,都应该用rAF来驱动。后面所有代码里我都会用rAF,不用setInterval。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动手实现第一版:从空画布到第一场雨
2.1 初始化画布:尺寸、高清屏适配、字体设定
第一步,拿到canvas元素,设置画布的实际绘图缓冲区大小。
这里有一个很常见的坑:canvas.width和CSS里的width是两个概念。canvas.width是画布位图的像素宽度(实际分辨率),CSS width是元素在页面上显示的宽度。如果不做任何处理,在普通屏幕上两者一致;但在高分屏(dpr > 1)上,画布位图分辨率不够,浏览器会把它拉伸放大,结果就是文字发虚、模糊。
解决办法是
