@composi/gestures, Cross-browser Gestures for Desktop and Mobile

TruckJS
5 min readDec 4, 2018

--

With the rise of mobile as the primary platform that people use for accessing the web, it has become more important than every to deliver the best possible experience for them. We see this with the rise of Progressive Web Apps (PWAs). Mobile devices are designed around the idea of gestures. And the desktop is anchored in mouse events. If you use gestures on the mobile version and mouse events for the desktop versions, your users are going to be confused about how they are supposed to interact with your site.

To address the problem of different event systems on desktop and mobile, we introduce @composi/gestures. Minified, it’s 3KB, gzipped it’s just 1KB, so it hardly adds anything to your final app size.

On desktop we have mouse events. On iOS we have touch events and gesture events. On Android we have touch events and pointer events. On Windows we have pointer events and basic support for iOS style touch events. To create a web app that supports these events for all platforms is challenging. That’s why we created @composi/gestures. It uses event normalization, timers and event motion tracking to create a consistent experience across browsers and devices.

There are certain situations created by mobile browser teams that are beyond our control. Scroll to the end to read the Summary for full details.

Compared to HammerJS

HammerJS is a widely used alternative for gestures which is 45KB minified. HammerJS has more gestures and features than @composi/gestures., but that comes with the cost of complexity. You have to create new instances of the Hammer class and then provide configuration for that instance. In contrast, @composi/gestures strives to keep gestures simple so you can use them as inline events or with event listeners as you are already accustomed to doing with normal events.

Event Normalization

We erase the differences between mouse events, touch events and pointer events by presenting a set of four events that represent their equivalent for each platform:

  1. eventstart: mousedown, touchstart, pointerdown
  2. eventend: mouseup, touched, pointerup
  3. eventmove: mousemove, touchmove, pointermove
  4. eventcancel: mouseout, touchcancel, pointer cancel

This relieves you from thinking about how to handle a mouse, touch or pointer. You use these just like you would use any other event. Here are some examples using standard event listeners:

Gestures for Deskop and Mobile

The most important thing that @composi/gestures provides are gestures:

  1. tap (150 milliseconds — quicker than a mouse click, slower than a touch)
  2. longtap (750 milliseconds)
  3. dbltap
  4. swipe
  5. swiperight
  6. swipeleft
  7. swipeup
  8. swipedown

Event Object

All gestures get passed the event object as their argument, just like normal events. You can examine this to get the target, currentTarget, or to use preventDefault or stopPropagation.

Below is an example of how to use gestures with regular event listeners. Notice that after importing gestures we need to initialize them by calling the function: gestures(). If you forget to do this, the gestures won’t be registered on the DOM for use. So, if you see that your gestures are not working at all, check that you have gestures() after importing @composi/gestures.

Swipe

Although there are leftswipe, rightswipe, upswipe and downswipe, using the generic swipe can be more useful where you need to track multiple possible swipes. Say you have something like a switch. When the user swipes right, you want to turn it on, and when they swipe left, turn it off. You could attach swipeleft and swiperight events to the switch. But it would be more practially to instead use swipe and test to see if the user swiped left or right. You can do this when using swipe by examining the value of the data attribute of the event:

Dealing with Text Selection

When implementing swipe gestures you may run into the problem where the element’s test gets selected during the swipe. You can make this not happen by using @composi/gestures’ disableTextSelection function. You need to import it first. Then you pass in the element that you are registering the swipe on. Below is an example using React. Notice how we use the componentDidMount lifecycle hook to disable text selection on the button:

You can disable text selection on multiple elements by using a generic element tag or class:

After disabling text selection, you may later want to re-enable it. You can do so by importing enableTextSelection from @composi/gestures:

You can re-enable text selection on multiple elements like this:

Supported Libraries and Frameworks

Using @composi/gestures with standard event listeners is easy. You can also use the gestures as inline events with many libraries and frameworks. This will not be a problem as long as the library does not convert events to synthetic events. If a library does implement its own synthetic events, then you will have to fall back to using standard event listeners to enable gestures.

Different libraries implement inline events in many ways. Some you can just use an inline event like normal JavaScript, some expect inline events to be camel cased, and some use special directives for inline events. As we mentioned before, as long as the library does not create synthetic events, you should be able to use @composi/gestures without issues.

Here are a few libraries where we know @composi/gestures works: Vue, Preact, Svelte, Hyperapp, Superfine, Composi, lit-html, HyperHTML.

Here are examples of using @composi/gestures in these libraries.

Preact

You have to use camel cased inline events for gestures onTap:

Vue

You need to use the directive v-on: followed by the name of the gesture:

JavaScript code for the above template:

Svelte

You use a directive for inline events — on:tap:

Hyperapp

You can use lowercase inline events:

Superfine

Like Hyperapp, you can use lowercase inline events:

Composi

Like Hyperapp and Superfine, you can use lowercase inline events:

lit-html

You have to use a hyphenated directive on-click:

HyperHTML

You can use a normal lowercase inline event ontap:

Inferno

Inline events get converted into synthetic events. Therefore it is not possible to use @composi/gestures with Inferno as inline events. However you can use gestures with event listers. You need to create a ref on the element you wish to attach the gesture to, and then attach the event in the componentDidMount lifecycle hook.

Here’s how you would do this for Inferno:

React

Like Inferno, React inline events get converted into synthetic events. Therefore it is not possible to use @composi/gestures with React as inline events. However you can use gestures with event listers. You need to create a ref on the element you wish to attach the gesture to, and then attach the event in the componentDidMount lifecycle hook.

Other Libraries

If you don’t see your library here, depending on whether it uses inline events, directives or synthetic events, one of the above techniques should work for you.

Summary

Mobile browser teams have strong opinions about what events we developers need. And yes, there have been many long and heated conversations on forums to no avail. We get what they decide we should have, not what we say we need. This means the following:

On mobile Chrome for Android and ChromeOS, a long tap will cause a contextual menu to appear in situ. Same problem with Window touch screen devices. Similarly left and right swipes can occasionally trigger browser history navigation if it’s registered on the page. Similarly, a double tap may trigger a page zoom on some devices: Android, iOS, Windows touchscreens. Using CSS pointer events can sometimes remedy these problems. The only way to ensure you get what you’re expecting is to test on actual devices. This means you’ll need at least an iOS device, and Android device and a Windows 10 tablet or laptop with touchscreen.

--

--