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 Pointer Event types

Below is a new pointerrawupdate event type as well as a modification to the pointermove event in the existing pointer event types.

The pointermove event

A user agent MUST fire a pointer event named pointermove when a pointer changes button state. Additionally one pointermove MUST be fired when pointer changes coordinates, pressure, tangential pressure, tilt, twist, or contact geometry (e.g. width and height) and the circumstances produce no other pointer events defined in this specification. These events may be coalesced or aligned to animation frame callbacks based on UA decision. The coalesced events information will be exposed via getCoalescedEvents API for the single dispatched pointermove event. The final coordinates of such events should be used for finding the target of the event.

The pointerrawupdate event

A user agent MUST fire a pointer event named pointerrawupdate when a pointing device attribute (i.e. button state, coordinates, pressure, tangential pressure, tilt, twist, or contact geometry) is changed. As oppose to pointermove which might be aligned to animation callbacks, user agents SHOULD dispatch pointerrawupdate events as soon as possible and as frequent as the javascript can handle the events. The target of pointerrawupdate events might be different from the pointermove events due to the fact that pointermove events might get aligned with animation frame callbacks and get coalesced and the final position of the event which is used for finding the target could be different from its coalesced events. Note that if there is already another pointerrawupdate with the same pointerId that hasn't been dispatched in the task queue user agent MAY coalesce the new pointerrawupdate with that event instead of creating a new task. So this may cause pointerrawupdate to have coalesced events and they will all be delivered as coalesced events of one pointerrawupdate event as soon as the event's turn to get processed reaches in the task queue. See getCoalescedEvents for more information. In terms of ordering of pointerrawupdate and pointermove, if the UA received an update from the platform that causes both pointerrawupdate and pointermove events then the user agent MUST dispatch pointerrawupdate event before the correponding pointermove for it. Other than the target, the concatenation of coalesced events of all dispatched pointerrawupdate events since the last pointermove event is the same as coalesced events of the next pointermove event in terms of the other attributes. The attributes of pointerrawupdate are mostly the same as pointermove with the exception of cancelable which MUST be false for pointerrawupdate. User agent SHOULD not fire compatibility mouse events for pointerrawupdate.

Adding listener for this type of the event might impact the performance of the web page negatively depending on the implementation of user agent. For most of the use cases the other pointerevent types should suffice. A pointerrawupdate listener should only be added if javascript needs high frequency events and can handle them just as fast. In that case there is probably no need to listen to other types of pointer events for most of the use cases.

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 or pointerrawupdate 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 and pointerrawupdate 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 only happens for pointermove and pointerrawupdate events. Note that user agents don't need to hit-test all the coalesced events as all of them will have the same target as the dispatched event.

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>