canvas实现弹幕滚动因避免dom操作而高效:10条以上弹幕若用dom逐个定位动画会卡顿,因重排重绘开销大;应维护弹幕对象数组,每帧清空画布、遍历调用filltext绘制并更新x坐标模拟滚动,预设font、用measuretext测宽确保边界准确,且仅用requestanimationframe驱动主循环,通过时间戳差值控制帧率。

用 canvas 实现弹幕滚动,为什么不能直接操作 DOM 元素
DOM 节点逐个创建、定位、动画,10 条以上弹幕就会明显卡顿;浏览器重排重绘开销大,requestAnimationFrame 也压不住。真实场景下(比如 B 站式高密度弹幕),必须用 canvas 批量绘制——所有弹幕文本只是画布上的像素,不产生任何 DOM 节点。
关键点:不是“把文字塞进 canvas”,而是维护一个弹幕对象数组,每帧清空画布、遍历数组、调用 ctx.fillText() 绘制,再统一更新 x 坐标模拟滚动。
- 弹幕对象至少含:
text、x(当前横坐标)、y(固定行高位置)、speed(像素/帧)、life(存活帧数) - 避免在
draw循环里反复调用ctx.font = '16px sans-serif',应提前设好 - 中文字符宽度不等,用
ctx.measureText(text).width计算实际占位,否则右边界判断会偏移
requestAnimationFrame 的正确节流方式:别用 setTimeout 模拟
常见错误是用 setTimeout 每 40ms 触发一次绘制,这会导致帧率漂移、与屏幕刷新不同步,尤其在页面切后台再切回时大量丢帧。
正确做法是只用 requestAnimationFrame 驱动主循环,并在内部做时间差控制——比如限制最小绘制间隔为 16ms(≈60fps),超出则跳过本次绘制,而非强行补帧。
- 记录上一帧时间戳:
let lastTime = 0 - 在回调中计算差值:
const delta = now - lastTime,若delta 就直接 <code>return - 更新弹幕
x -= speed * (delta / 16),实现匀速(而非帧数依赖的跳跃)
弹幕行高冲突怎么解:动态分配 y 位置不重叠
如果所有弹幕都从随机 y 开始,很快会出现上下堆叠、遮挡。真实方案是维护一个「可用行高池」,每条新弹幕入队时,扫描当前正在显示的弹幕,找出垂直方向无重叠的最小 y 值。
简化但够用的做法:预设固定行高(如 32px),按 0, 32, 64… 分配;但需实时检测该行是否「已被占用」——即是否存在 currentY === targetY 且未移出画布的弹幕。
- 用 Set 存储当前占用的行号:
const occupiedRows = new Set() - 插入前遍历预设行列表(如
[0, 32, 64, 96, 128]),找第一个不在occupiedRows中的y - 弹幕
x移出左边界后,记得从occupiedRows删除对应行号
Canvas 弹幕性能崩了?检查这三处硬伤
即使用了 canvas,仍可能卡顿:本质是高频重绘 + 内存泄漏 + 无效计算。最常被忽略的是字体加载和文本测量。
-
ctx.font切换频繁(比如每条弹幕设不同字号)会触发字体回退、重排布局,必须统一字体配置 - 每次调用
ctx.measureText()都有开销,对相同文本重复测要缓存结果,比如用Map存text → width - 忘记调用
ctx.clearRect(0, 0, canvas.width, canvas.height),旧帧残留导致 GPU 填充压力陡增;若用canvas.width = canvas.width重置,注意它会清空所有状态(包括 font、fillStyle)
复杂点在于弹幕生命周期管理——不是“播完就删”,而是要精确到帧的销毁时机;容易被忽略的是 canvas 大小未随窗口缩放重置,导致高清屏下模糊或错位。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











