demo · v130
predicted events forecast
Sister API of getCoalescedEvents(): getPredictedEvents(). The spec change applies to both — predicted events on JS-constructed PointerEvents now keep their own offsetX/Y instead of being recomputed against the parent target. The pad below draws the actual pointer trail in black and the predicted (extrapolated) trail in blue.
Compares the coordinates supplied to JS-constructed sub-events with the values returned after untrusted dispatch.
| list | # | recorded offsetX/Y | replayed offsetX/Y | status |
|---|---|---|---|---|
| No offset comparison has run yet. | ||||
why this matters
Latency-sensitive drawing apps (ink, signature, freehand annotation) use getPredictedEvents() to render ahead of where the user has actually moved, masking the input lag between OS-level pointer dispatch and the next paint. Pre-Chrome 130, dispatching a synthetic PointerEvent with your own predicted list would lose the per-event coordinates — the engine recomputed offsetX/Y against the parent's target. Frameworks that replay or fan out pointer streams (record/replay, accessibility-driven input, cross-document handoff) could not faithfully reconstruct the original geometry.
The spec text now reads: "For untrusted events, the populated entries in the coalesced or predicted events list remain unchanged" — engine doesn't touch your synthetic target or offsetX/Y.
the code
// inside a real pointermove handler
canvas.addEventListener("pointermove", (ev) => {
// 1. trusted coalesced sub-events: smooth ink
for (const c of ev.getCoalescedEvents()) drawDot(c.offsetX, c.offsetY);
// 2. predicted continuation: draw a few frames ahead, then erase
// when the real pointer catches up
if (ev.getPredictedEvents) {
for (const p of ev.getPredictedEvents()) drawForecast(p.offsetX, p.offsetY);
}
});
// later, replay the recorded pointer track as untrusted events
const replayPad = document.getElementById("replay");
for (const sample of recorded) {
const sub = new PointerEvent("pointermove", { ...sample });
// In Chrome 130+, the dispatched event's predicted sub-events keep their
// offsetX/Y. Pre-130, they were recomputed against replayPad's bbox.
const parent = new PointerEvent("pointermove", {
bubbles: true,
coalescedEvents: sample.coalesced,
predictedEvents: sample.predicted,
});
replayPad.dispatchEvent(parent);
}