Last active
February 27, 2026 19:57
-
-
Save mmocny/c2d73317900eac289f397d8e4368baf5 to your computer and use it in GitHub Desktop.
Testing pointerdown + pointercancel relative scheduling
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function block(ms) { | |
| const end = performance.now() + ms; | |
| while (performance.now() < end); | |
| } | |
| async function makeMainBusy() { | |
| for (let i = 0; i < 10; i++) { | |
| setTimeout(() => { | |
| console.log(i); | |
| block(10); | |
| }, 0); | |
| } | |
| } | |
| const eventsInAnimationFrame = []; | |
| for (let type of ['pointerdown','pointercancel','pointerup']) { | |
| document.addEventListener(type, async event => { | |
| const processingStart = performance.now(); | |
| console.log("dispatch: ", type); | |
| eventsInAnimationFrame.push(type); | |
| block(200); | |
| if (type != "pointerdown") return; | |
| makeMainBusy(); | |
| await new Promise(resolve => requestAnimationFrame(resolve)); | |
| const renderStart = performance.now() | |
| console.log("renderStart delay:", renderStart - processingStart); | |
| // Note: small bug, you should look for pointercancel for this pointer, i.e. cancel comes after | |
| // I once hit a case where old pointercancel got grouped with new pointerdown | |
| const shouldBeInteraction = !eventsInAnimationFrame.includes("pointercancel"); | |
| eventsInAnimationFrame.splice(0, eventsInAnimationFrame.length); | |
| console.log("Event:", event.type, "start:", event.timeStamp, "duration", renderStart-event.timeStamp, "id:", shouldBeInteraction); | |
| }, { capture: true }); | |
| } | |
| const observer = new PerformanceObserver(list => { | |
| for (const entry of list.getEntries()) { | |
| if (entry.name == "pointerdown") { | |
| console.log("EventTiming:", entry.name, "start:", entry.startTime, "duration:", entry.duration, "id:", entry.interactionId); | |
| } | |
| } | |
| }); | |
| observer.observe({ type: 'event', durationThreshold: 0 }); |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
If you test this snippet, then interact with a scrollable page with touch, the console logs will tell if you if the pointerdown event timing was entirely measured before scroll start or not.
Option 1: small hesitation before starting a scroll:
The "Event" logs
id: truewhich is the snippet's way of suggesting this should be an interaction.You can see pointercancel is dispatched very late, after next paint (renderStart) and after a bunch of tasks.
The final
EventTiming.durationis only200which matches the "real" jank ofpointerdown.Option 2: rapid swipe to scroll gesture:
The "Event" logs
id: falsewhich is the snippet's way of suggesting this should NOT be an interaction.You can see pointercancel is dispatched very early, before next paint (renderStart) and before a bunch of tasks.
The final
EventTiming.durationis now408which is larger than the "real" jank ofpointerdownand also not something the user experiences, since scroll has started.