mousedown 只能说明按键刚刚被按下。拖拽、绘图这类连续交互更关心的是另一件事:执行到某个 mousemove 时,主键到底还按没按着。
早期常见的办法是自己维护变量,在 mousedown 时加一,在 mouseup 时减一。问题是鼠标可能在浏览器里按下、移到窗口外再松开,这次 mouseup 不一定会被页面收到,变量便会一直停在“按下”状态。
从事件对象读取当前状态
现代浏览器的 MouseEvent.buttons 会告诉你事件发生时哪些按键正处于按下状态。主键对应最低位,可以这样检查:
let primaryButtonDown = false;
function isPrimaryButtonDown(event) {
const pressedButtons = event.buttons !== undefined
? event.buttons
: event.which;
return Boolean(pressedButtons & 1);
}
function refreshButtonState(event) {
primaryButtonDown = isPrimaryButtonDown(event);
}
["mousedown", "mousemove", "mouseup"].forEach((eventName) => {
document.addEventListener(eventName, refreshButtonState);
});
代码里仍然有一个状态变量,但它不是靠 mousedown、mouseup 次数做加减,而是每次都从事件对象重新读取。监听放在 document 上,而不是某个局部元素上。鼠标在窗口外松开后,只要重新移回页面,新的 mousemove 就会带回当前按键状态,代码也会随之修正 primaryButtonDown。
老 Safari 需要回退
这段代码保留 event.which,是为了兼容较老的 Safari。buttons 在 macOS Safari 11.1 及以上、iOS Safari 11.3 及以上可用;更早的版本里可能是 undefined。
这里只判断主键,所以 (flags & 1) === 1 足够。如果还要区分右键、中键或多个按键同时按下,不能直接照搬这一条判断,因为 which 与 buttons 对右键和中键使用的数字并不相同,需要分别处理。