The features in this specification extend or modify those found in Pointer Events Level 2 that describes events and related interfaces for handling hardware agnostic pointer input from devices including a mouse, pen, touchscreen, etc.

This proposal intends to add new functionalities to a future version of the Pointer Events Level 2.

Introduction

This document reflects incubation for APIs which are intended to be part of a future version of the Pointer Events specification. They are not yet broadly supported by implementations.

The getCoalescedEvents API (similar to Android getHistorical APIs and iOS GetCoalescedTouches API) exposes all the events that were coalesced into a single event. Coalescing events into a single event enables user agents to prevent processing of the events that don’t get a chance to update the frame content visually. Basically if the javascript handlers process the events and update the screen contents faster than refresh rate only the last update will be rendered and shown in the next frame. However, some applications like drawing apps may want to take advantage of the precise history of events as user moves the pointer on the screen to be able to draw smoother and better looking curves. This API can be used in those scenarios to get all the coalesced events from the event that was dispatched to the javascript.

Extensions to the PointerEvent interface

The following section describes extensions to the existing PointerEvent interface.

partial dictionary PointerEventInit {
    sequence<PointerEvent> coalescedEvents = [];
    sequence<PointerEvent> predictedEvents = [];
};

partial interface PointerEvent {
    sequence<PointerEvent> getCoalescedEvents();
    sequence<PointerEvent> getPredictedEvents();
};
          
getCoalescedEvents

Returns a sequence of all PointerEvents that were coalesced into the dispatched pointermove event. When the event is created by the user agent the following attributes of the coalesced events will always have the same value as the dispatched event: pointerId, pointerType, isPrimary, isTrusted, target. Also since these coalesced events are not going to be dispatched by themselves their cancelable and bubbles attributes are false. In addition to that, the other attibutes related to the event dispatch algorithm (e.g. currentTarget, eventPhase) will have their default value and the related functions (e.g. stopPropagation, stopImmediatePropagation) will do nothing. Note that this rule doesn't apply to target and it should still be the same as the dispatched event's target. This guarantees the attributes like offsetX, offsetY ([[!CSSOM-VIEW]]) which are commonly used by drawing applications to be calculated correctly for the coalesced events. The rest of the attributes might be different from the dispatched event. The events in the sequence will have increasing timeStamps ([[!WHATWG-DOM]]). So the first event will have the smallest timeStamp. The dispatched event's attributes will be initalized in a way that is best representative of all the coalesced events. For example its movementX and movementY ([[!POINTERLOCK]]) COULD be the sum of those of all the coalesced events. None of the events in the sequence will have (nested) coalesced events, so getCoalescedEvents returns an empty sequence for them. This API always returns at least one coalesced event for pointermove events and an empty list for other types of PointerEvents.

getPredictedEvents

Returns a sequence of PointerEvents the user agent predicts that will follow the sequence of coalesced events in pointermove event in the future. The number of events returned by this API and how far they are from the current timestamp are determined by the user agent and the prediction algorithm it uses. This API always returns an empty list for pointerevent types other than pointermove. Similar to the coalesced events the following attributes of the predicted events will always have the same value as the dispatched event: pointerId, pointerType, isPrimary, isTrusted, target. Also cancelable and bubbles attributes will be false and other attibutes related to the event dispatch algorithm (e.g. currentTarget, eventPhase) will have their default value and the related functions (e.g. stopPropagation, stopImmediatePropagation) will do nothing. Note that since the target of the predicted events is the same as the dispatched event's target, the attributes like offsetX, offsetY ([[!CSSOM-VIEW]]) which are commonly used by drawing applications will be calculated correctly for the predicted events. The rest of the attributes might be different from the dispatched event based on the prediction algorithm in user agent. The events in the sequence will have increasing timeStamps ([[!WHATWG-DOM]]). So the first event will have the smallest timeStamp which should still be greater than the last event in the coalesced events.

Timing of coalesced event firing

Coalescing events only happens for pointermove events. All the coalesced events should be fired before the next frame or before any pointerup, pointerdown, pointercancel events. Note that user agents don't need to hit-test all the coalesced events as none of them will have null targets.

When the user agent dispatches any PointerEvent it MUST dispatch all the events held back so far for coalescing, regardless of their pointerIds. The ordering of all these dispatched events should be in a way that resemebles the most with the actual events' ordering. For example if a pointerdown event causes the dispatch for the coalesced pointermove events the user agent SHOULD first dispatch one pointermove event with all those coalesced events of a pointerId followed by the pointerdown event.

Here is an example of the actual events happening in one frame with increasing timestamps and the events dispatched by the user agent:

Actual eventsDispatched events
pointermove with pointerId=2
pointermove with pointerId=1
pointermove with pointerId=2
pointermove with pointerId=2
pointermove with pointerId=1
pointermove with pointerId=2
pointerdown with pointerId=1 pointermove with pointerId=1 and two coalesced events
pointermove with pointerId=2 and four coalesced events
pointerdown with pointerId=1 and zero coalesced events
pointermove with pointerId=2
pointermove with pointerId=2
pointerup with pointerId=1 pointermove with pointerId=2 and two coalesced events
pointerup with pointerId=1 and zero coalesced events

Additional touch-action values

This section augments the definition of touch-action from [[!pointerevents2]] to add the pan-left, pan-left, pan-up, pan-down values.

Name:touch-action
Value:auto | none | [ [ pan-x | pan-left | pan-right ] || [ pan-y | pan-up | pan-down ] ] | manipulation
Initial:auto
Applies to:all elements except: non-replaced inline elements, table rows, row groups, table columns, and column groups.
Inherited:no
Percentages:N/A
Media:visual
Computed value:Same as specified value.

The user agent MAY consider touches that begin on the element only for the purposes of scrolling that starts in any of the directions specified by all of the listed values. Once scrolling is started, the direction may be reversed by the user even if scrolls that start in the reversed direction are disallowed. In contrast, when scrolling is restricted to starting along a single axis (eg. pan-y), the axis cannot be changed during the scroll. In the case of pan-left, pan-right, pan-up and pan-down, the direction is interpreted as the opposite of the physical movement in the local coordinate space. For example, pan-up corresponds to input event sequences where typically screenY is increasing (i.e. an interaction where the user moves a touch point down the screen) when the element has no effective CSS transforms. If the element is effectively rotated by 90 degrees counter-clockwise, pan-up would then correspond to input event sequences where screenX is increasing.

The direction-specific pan values are useful for customizing overscroll behavior. For example, to implement a simple pull-to-refresh effect the document's touch-action can be set to pan-x pan-down whenever the scroll position is 0 and pan-x pan-y otherwise. This allows pointer event handlers to define the behavior for upward scrolls that start from the top of the document.

The direction-specific pan values can also be used for composing a component that implements custom panning with pointer event handling within an element that scrolls natively (or vice-versa). For example, an image carousel may use pan-y to ensure it receives pointer events for any horizontal pan operations without interfering with vertical scrolling of the document. When the carousel reaches its right-most extent, it may change its touch-action to pan-y pan-right so that a subsequent pan operation beyond it's extent can scroll the document within the viewport if possible. It's not possible to change the behavior of a pan in the middle of an operation.

Examples

The following are examples that demonstrate how the APIs in this specification might be used.


var pointerEventInitDict =
{
  bubbles: true,
  cancelable: true,
  composed: true,
  pointerId: 42,
  pointerType: "pen",
  clientX: 300,
  clientY: 500,
};

var p1 = new PointerEvent("pointermove", pointerEventInitDict);
pointerEventInitDict.clientX += 10;
var p2 = new PointerEvent("pointermove", pointerEventInitDict);
pointerEventInitDict.coalescedEvents = [p1, p2];
var event = new PointerEvent("pointermove", pointerEventInitDict);

window.addEventListener("pointermove", function(event) {
  for (let e of event.getCoalescedEvents()) {
    drawPoint(e.pageX, e.pageY);
  }
});


var predicted_points = [];
window.addEventListener("pointermove", function(event) {
  for (let e of predicted_points.reverse())
    clearPoint(e.pageX, e.pageY);
  for (let e of event.getCoalescedEvents())
    drawPoint(e.pageX, e.pageY);
  predicted_points = event.getPredictedevents();
  for (let e of predicted_points)
    drawPoint(e.pageX, e.pageY);
});

<div style="overflow: auto;">
    <div style="touch-action: pan-y pan-left;">
        <div style="touch-action: pan-x;">
            This element receives pointer events when not panning to the left.
        </div>
    </div>
</div>