为何mouseDown与mouseUp事件无法调度同一个animationFrame?
问题:为何简单点击时mousedown与mouseup会触发不同的requestAnimationFrame帧?
以下代码显示,即使执行简单点击(非长按)操作,mousedown和mouseup事件也会分别触发不同的requestAnimationFrame帧:
const d = document.createElement('div'); const b = document.createElement('button'); b.textContent = 'Click me!'; b.addEventListener('mousedown', (e) => { schedule(() => console.log('schedule button mousedown')); console.log('button mousedown'); }); b.addEventListener('click', () => { schedule(() => console.log('schedule button click')); console.log('button click'); }); b.addEventListener('mouseup', () => { schedule(() => console.log('schedule button mouseup')); console.log('button mouseup'); }); document.body.appendChild(d); d.appendChild(b); let timerId; function schedule(cb) { if (timerId === undefined) { timerId = requestAnimationFrame(() => { console.log('running synchronization'); timerId = undefined; }); } }
运行日志如下:
button mousedown running synchronization button mouseup button click running synchronization
解答
这个现象的核心原因在于浏览器事件循环与渲染调度的机制:
- 输入事件的独立宏任务处理:mousedown和mouseup属于用户输入事件,浏览器会将它们作为独立的宏任务依次放入事件队列。用户点击按钮时,mousedown事件会先被触发并执行回调。
- requestAnimationFrame的调度时机:
requestAnimationFrame的回调会被安排到当前渲染帧的渲染前阶段执行。在mousedown事件回调中调用schedule,会注册一个RAF回调到当前帧。 - 输入事件后的强制渲染:浏览器为保证用户操作的视觉反馈及时性(比如按钮按下的状态变化),会在处理完一个输入事件的宏任务后,立即触发一次渲染流程。因此mousedown事件处理完成后,浏览器会执行之前注册的RAF回调(输出
running synchronization),然后完成渲染。 - mouseup事件的后续处理:当mousedown对应的渲染完成后,浏览器才会从事件队列中取出mouseup事件执行其回调。此时调用
schedule注册的RAF回调,会被安排到下一个渲染帧,因此mouseup处理完成后,又会触发一次RAF回调输出running synchronization。
简单来说,浏览器会在每个输入事件处理后优先完成渲染更新,确保用户能看到即时反馈,这就导致mousedown和mouseup各自触发了独立的渲染帧,进而让它们的RAF回调分属不同的帧执行。
内容的提问来源于stack exchange,提问作者Matthieu Riegler
相关产品推荐
相关产品推荐

