Pointer Events in JavaScript

Pointer Events in JavaScript

Let us understand how JavaScript has evolved with the new age technology

·

6 min read

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 eventSimilar mouse event
pointerdownmousedown
pointerupmouseup
pointermovemousemove
pointerovermouseover
pointeroutmouseout
pointerentermouseenter
pointerleavemouseleave
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:

  1. onPointerDown to start capturing the pointer event

  2. onPointerMove to accurately provide the motion of the captured event (red dot) even though the touch pointer is not within the target element

  3. onPointerUp 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.

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 ✌️