v147 · DOM · Pointer Events
Compatibility Lab
Detects whether pointercancel fires on drag start via a live drag test. Compares Chrome 147+ behaviour (full suppression sequence) against older browsers, and provides the pointercancel guard pattern for safely resetting pointer state in drag libraries.
API probes
Live drag test
Drag the blue chip to the drop zone. Watch for pointercancel after dragstart — that's the Chrome 147 suppression sequence.
Drag chip → drop zone
DRAG ME
drop here
event log…
pointercancel on drag:
not tested yet
Counterfactual replay
These buttons replay labelled event sequences for comparison only. The live drag test above is the real browser observation.
Chrome 147+ drag source receives a cleanup pointer sequence.
Event sequence comparison
| Event | Chrome ≤146 (old) | Chrome 147+ (new) |
|---|---|---|
| pointerdown | ✓ fires | ✓ fires |
| pointermove (×N) | ✓ fires | ✓ fires |
| dragstart | ✓ fires | ✓ fires |
| pointercancel | ✗ missing | ✓ fires |
| pointerout | ✗ missing | ✓ fires |
| pointerleave | ✗ missing | ✓ fires |
| drag / dragover / drop | ✓ fires | ✓ fires |
Fallback pattern
/* Detect whether pointercancel fires on drag start */
function setupDragElement(el) {
let isPointerDown = false;
let isDragging = false;
let sawDragStartCancel = false;
el.addEventListener('pointerdown', (event) => {
isPointerDown = true;
isDragging = false;
sawDragStartCancel = false;
el.setPointerCapture?.(event.pointerId);
});
el.addEventListener('dragstart', () => {
isDragging = true;
});
// Chrome 147+: this fires after dragstart — guaranteed cleanup point.
el.addEventListener('pointercancel', () => {
sawDragStartCancel = isDragging;
isPointerDown = false;
resetCursor(el);
});
// Fallback: older browsers need dragend cleanup because no pointercancel arrives.
el.addEventListener('dragend', () => {
if (!sawDragStartCancel) {
isPointerDown = false;
resetCursor(el);
}
isDragging = false;
});
}
function resetCursor(el) {
el.style.cursor = '';
el.classList.remove('dragging');
}