应改用 requestAnimationFrame 主动驱动,禁用 timeupdate 触发绘制,每帧读取 video.currentTime 插值计算弹幕位置,并监听 pause/seeked 事件冻结动画;同时校准 Canvas 与 DOM 坐标系、避免 layout thrashing、隔离合成层以解决同步问题。

Canvas 与 video 的 timeupdate 事件不同步怎么办
根本问题不是 Canvas 刷得太慢,而是 timeupdate 触发频率不可控(典型为 200–500ms 一次),而 Canvas 动画需要稳定 60fps(约 16.7ms/帧)。两者节奏错开,弹幕就“卡顿”或“漂移”。
必须把时间驱动逻辑从 timeupdate 移出,改用 requestAnimationFrame 主动推进,并在每一帧内读取 video.currentTime 做插值判断:
- 禁用
timeupdate作为绘制触发源,它只用于初始化和校准(比如加载时同步首帧位置) - 在
requestAnimationFrame回调中,用video.currentTime查当前毫秒级时间,再查表匹配该时刻应显示的弹幕列表 - 对跨帧弹幕(如起始时间在上一帧、结束在下一帧),用线性插值计算其当前
x坐标,避免跳跃 - 若视频暂停或拖拽,
requestAnimationFrame仍运行,但需监听pause和seeked事件,临时冻结动画逻辑
Canvas 层和 DOM 层滚动/缩放后坐标偏移失准
常见现象:Canvas 上画的热区、标注点,跟着页面滚动或 zoom 缩放后,鼠标点击位置和实际绘制位置对不上。这不是 Canvas 画错了,是坐标系没对齐。
关键动作是每次重绘前重新计算 Canvas 的视觉尺寸与 DOM 容器的映射关系:
立即学习“前端免费学习笔记(深入)”;
- 监听
window的resize、scroll和zoom(通过document.body.style.zoom或 CSStransform: scale()检测) - 用
canvas.getBoundingClientRect()获取当前可视区域坐标,而非依赖canvas.width/canvas.height静态值 - 鼠标事件坐标要先减去
getBoundingClientRect().left/top,再除以window.devicePixelRatio(防高清屏偏移) - 若容器有 CSS
transform,需用getScreenCTM()(SVG)或手动逆推矩阵——Canvas 无原生支持,必须自己算
Canvas 绘制和 DOM 更新混排导致 layout thrashing
典型错误:在 requestAnimationFrame 中一边调用 ctx.fillText(),一边又读取某个 DOM 元素的 offsetHeight 或 getComputedStyle。浏览器被迫同步回流,直接吃掉 5–10ms,帧率立刻跌破 30fps。
所有 DOM 查询必须前置并缓存,绘制阶段只读不查:
- 把
element.getBoundingClientRect()、getComputedStyle(el).fontSize等操作放在 rAF 外部,或上一帧末尾批量采集 - Canvas 内文字大小、颜色、位置等参数,全部转成数值缓存,避免每帧重复解析 CSS 字符串
- DOM 层仅做容器定位与事件代理,内容(如标签、数值)尽量由 Canvas 绘制,减少样式计算压力
- 用
will-change: transform提前提示浏览器该元素将动画,避免 rAF 中首次触发合成层提升的开销
局部刷新时 Canvas 与 DOM 图层 Z-index 错位闪烁
当 Canvas 只清空脏区(clearRect(x, y, w, h))而 DOM 层有半透明遮罩或阴影时,会出现 Canvas 区域“透出底层”或“叠影残留”,本质是混合模式未对齐。
此时不能只靠 clearRect,得引入合成层隔离:
- 给 Canvas 容器加
style="isolation: isolate",确保其独立于 DOM 层的混合上下文 - 若 DOM 层有
filter: blur()或opacity,Canvas 必须用离屏缓存:先画到offscreenCanvas,再drawImage(offscreenCtx.canvas, ...)合成 - 脏区坐标必须用
Math.floor()对齐像素边界,否则 sub-pixel 清除会触发抗锯齿,与 DOM 层渲染结果不一致 - 避免在 Canvas 中使用
globalCompositeOperation = 'destination-out'等破坏性混合模式,它无法与 DOM 的层叠顺序协同



















