You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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

解答

这个现象的核心原因在于浏览器事件循环与渲染调度的机制:

  1. 输入事件的独立宏任务处理:mousedown和mouseup属于用户输入事件,浏览器会将它们作为独立的宏任务依次放入事件队列。用户点击按钮时,mousedown事件会先被触发并执行回调。
  2. requestAnimationFrame的调度时机:requestAnimationFrame的回调会被安排到当前渲染帧的渲染前阶段执行。在mousedown事件回调中调用schedule,会注册一个RAF回调到当前帧。
  3. 输入事件后的强制渲染:浏览器为保证用户操作的视觉反馈及时性(比如按钮按下的状态变化),会在处理完一个输入事件的宏任务后,立即触发一次渲染流程。因此mousedown事件处理完成后,浏览器会执行之前注册的RAF回调(输出running synchronization),然后完成渲染。
  4. mouseup事件的后续处理:当mousedown对应的渲染完成后,浏览器才会从事件队列中取出mouseup事件执行其回调。此时调用schedule注册的RAF回调,会被安排到下一个渲染帧,因此mouseup处理完成后,又会触发一次RAF回调输出running synchronization。

简单来说,浏览器会在每个输入事件处理后优先完成渲染更新,确保用户能看到即时反馈,这就导致mousedown和mouseup各自触发了独立的渲染帧,进而让它们的RAF回调分属不同的帧执行。

内容的提问来源于stack exchange,提问作者Matthieu Riegler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 00:27:06