高频渲染 canvas 时应避免在 raf 中使用微任务,因其会抢占调度时机导致卡顿;正确做法是预加载资源、同步绘制、用 raf 节流交互,并排查第三方库隐式微任务。

高频渲染 Canvas 时,微任务(如 Promise.then、queueMicrotask)若被误用于控制帧节奏或资源加载回调,极易引发动画卡顿。根本原因在于:微任务会在当前 JS 执行栈清空后立即执行,抢占 requestAnimationFrame 的调度时机,打乱 60FPS 的稳定节奏,造成帧丢弃或延迟绘制。
避免在 rAF 回调中触发微任务链
常见错误是把图片加载、状态更新等异步逻辑塞进 requestAnimationFrame 内部,并依赖 .then() 继续绘图:
- ❌ 错误写法:在 rAF 中调用
getImageInfo().then(...),再在 then 里调用ctx.draw()—— 这会导致绘制被推迟到下一个宏任务甚至更晚,帧率骤降 - ✅ 正确做法:确保所有绘图操作(
ctx.drawImage、ctx.fill等)和最终的ctx.draw()都在同一个 rAF 回调内同步完成;异步资源必须提前加载就绪
预加载资源 + 状态标记,杜绝“边加载边画”
动画过程中不应等待网络或解码。所有图像、字体、路径数据必须在动画启动前完成加载并缓存:
- 使用
Promise.all([wx.getImageInfo(), ...])在页面初始化或动画准备阶段统一加载,成功后再启动 rAF 循环 - 为每个资源设置就绪标志(如
this.imgReady = true),rAF 回调中只做判断:if (this.imgReady) { ctx.drawImage(...) },不包含任何异步分支 - 对未就绪资源,可跳过绘制或显示占位图形,而非挂起或重试
用节流 + 帧锁定代替微任务调度
若需响应用户交互(如拖拽、缩放)并触发动画,不要用微任务“排队”绘制请求:
- ❌ 避免:
element.addEventListener('touchmove', () => queueMicrotask(updateAndDraw)) - ✅ 推荐:用
requestAnimationFrame节流 + 标志位控制,例如记录最后一次 touch 坐标,在下一帧统一计算并绘制 - 关键点:所有输入采集与绘制分离,输入只改状态,绘制只在 rAF 中读状态——中间不插入任何微任务桥接
检查第三方库是否隐式注入微任务
部分工具函数(如某些 debounce、lottie-miniprogram、图表库的 update 方法)内部可能使用 Promise 或 setTimeout(Promise.resolve()) 实现异步,导致不可见的微任务堆积:
- 在开发者工具“性能”面板录制动画过程,查看 Flame Chart 中是否有密集的 Microtask 任务块紧贴 rAF 后出现
- 临时替换可疑方法为同步实现,观察卡顿是否消失
- 优先选用明确声明“rAF-native”或“zero-microtask”的轻量库











