Pointer Events in JavaScript
Let us understand how JavaScript has evolved with the new age technology
We know that browsers work with the help of JavaScript, i.e. every browser has a JavaScript engine to execute the code to present the final web page. When writing code for the Web, there are a large number of Web APIs available. Today, we will understand how the pointer capture API works. Here is the list of all the APIs you may be able to use while developing a web application.
Introduction to Pointer Events
A decade ago, if you would have asked me to give an example of the pointer, I would have referred to the cursor pointer of a computer that is accessed through a mouse.
Yes, this is the one 😄
However, with the invention of touch-based devices and their support for the web, touch events were introduced as a part of web APIs. These touch events provided a similar behavior as that of the pointer event. Since then, many devices supported other types of pointing input devices, such as pen/stylus and touch surfaces, and extensions to the existing pointing device event models were needed.
According to the official documentation -
Pointer events are DOM events that are fired for a pointing device. They are designed to create a single DOM event model to handle pointing input devices such as a mouse, pen/stylus or touch (such as one or more fingers).
The pointer is a hardware-agnostic device that can target a specific set of screen coordinates. Thus, pointers can simplify creating web applications by providing a good user experience, regardless of the user's hardware.
Types of pointer events
Pointer events contain the usual properties of mouse events (client coordinates, target element, button states, etc.) in addition to new properties for other forms of input: pressure, contact geometry, tilt, etc.
Pointer event | Similar mouse event |
pointerdown | mousedown |
pointerup | mouseup |
pointermove | mousemove |
pointerover | mouseover |
pointerout | mouseout |
pointerenter | mouseenter |
pointerleave | mouseleave |
pointercancel | - |
gotpointercapture | - |
lostpointercapture | - |
In fact, the PointerEvent
interface inherits all of the MouseEvent
properties, thus facilitating the migration of content from mouse events to pointer events.
You can refer to MDN Docs to learn in detail about the above-mentioned events. In this blog, we are going to focus on one of the special features of pointer events.
Pointer Capture Event
Pointer capture allows the events for a pointer to be retargeted to a particular element other than the normal hit test result of the pointer's location.
The idea is very simple but may seem quite odd at first, as nothing like that exists for any other event type. We will learn this concept with a simple example. When I learnt about pointer capture events using this example, I was amazed by the simplicity it provides to the end users! Thus, I wanted to share this with a wider audience😀.
I'm sure most of you would have watched youtube videos on your phone. While watching, if you wanted to seek a particular timestamp, all you need to do is simply drag the red dot
on the timeline and move to the appropriate timestamp value. The next time you perform this activity, observe if you are always touching the red dot
. Most of the time, you would have touched the red dot
but moved away from this target element. That's exactly where "Pointer Capture Event" comes into the picture.
You can test the demo of pointer capture on https://webapis-playground.vercel.app/demo/pointer-capture
I built this feature as one of my early contributions to the open source software (OSS).
Let's understand how the code works!
For the above example, I have used React and Tailwind. I have kept the pointer event
functionality in a separate JavaScript module since the application is mainly focusing on Web APIs.
JavaScript File: https://github.com/atapas/webapis-playground/blob/master/src/modules/apis/pointer-capture/index.ts
React File: https://github.com/atapas/webapis-playground/blob/master/src/modules/demos/pointer-capture/index.tsx
The main elements to focus the React file are:
<div
id="gray-timeline"
className="tw-h-5 tw-w-full tw-bg-gray-400 tw-mb-5 tw-flex tw-items-center tw-relative tw-cursor-pointer"
style={{ touchAction: isCapture ? 'none' : 'auto' }}
onPointerUp={e => run.onPointerUp(e, isCapture)}
onPointerDown={e => run.onPointerDown(e, isCapture)}
onPointerMove={run.onPointerMove}
>
<div
id="red-timeline"
className="tw-relative tw-bg-red-600 tw-h-full tw-rounded-r-full tw-w-7"
>
<div
id="red-dot"
className="tw-absolute tw-bg-red-700 tw-h-7 tw-w-7 tw-rounded-full tw--top-1 tw-left-4"
></div>
</div>
The main div container with the id="gray-timeline"
has 3 events attached:
onPointerDown
to start capturing the pointer eventonPointerMove
to accurately provide the motion of the captured event (red dot
) even though thetouch pointer
is not within the target elementonPointerUp
to stop capturing the pointer event
By adding the pointer events on the parent container, the id="red-timeline"
and id="red-dot"
would work smoothly to capture and end the pointer event's behavior.
I have also added a react state isCapture
. This would ensure that the pointer event is "captured" only when isCapture=true
. You can set this value to false by toggling the switch to "Turn off".
Moving on, let's understand the behavior defined through these attached events.
let flag = false;
function onPointerDown(event: React.PointerEvent<Element>, isCapture: boolean) {
const timeline = document.querySelector('#red-timeline') as HTMLElement;
if (isCapture) {
timeline.setPointerCapture(event.pointerId);
}
flag = true;
onPointerMove(event);
}
I am using a global variable flag
to identify if the pointer is under capture. In the onPointerDown
function, we find the target element i.e. the red-timeline
element which needs to be captured. Based on the isCapture
state value, we start capturing the pointer within this element by invoking the setPointerCapture
.
setPointerCapture
takes one parameter which is the pointerId
of the given pointer event.
function onPointerMove(event: React.PointerEvent<Element>) {
const timeline = document.querySelector('#red-timeline') as HTMLElement;
if (flag) {
const rect = timeline.getBoundingClientRect();
timeline.style.width = `${event.clientX - rect.left}px`;
const childTag = document.querySelector('#dot') as HTMLElement;
childTag.style.left = `${event.clientX - rect.left - 20}px`;
}
}
Once the onPointerDown
is fired and the pointer event is captured, as I start moving the pointer across the screen, the onPointerMove
event is fired. In this function, we are capturing the latest value of the pointer and set the style attributes of the timeline element. It is because of this function you are able to see a smooth transition of the red timeline getting updated along with the movement of the red-dot
.
function onPointerUp(event: React.PointerEvent<Element>, isCapture: boolean) {
const timeline = document.querySelector('#red-timeline') as HTMLElement;
if (isCapture) {
timeline.releasePointerCapture(event.pointerId);
}
flag = false;
}
The onPointerUp
event is fired when we stop moving the "red dot" and release the touch (or click). At this moment, we release the pointer capture event and fire onPointerUp
function. Here, we invoke releasePointerCapture
to stop capturing the event any further.
Link to the demo application
You can test the demo of pointer capture on https://webapis-playground.vercel.app/demo/pointer-capture
Check out the demo of other Web APIs at https://webapis-playground.vercel.app/
If you learnt something new today and found this article exciting, go ahead and give this post a like!
Feel free to reach out to me on [Twitter](twitter.com/supminn) if you have any queries.
Peace ✌️