Touch and Click, We're Gganbu


Let's imagine a day without touch and click. How would it be? I can't imagine a day without these two actions now. When I was a user, I used it unconsciously and never thought deeply about the relationship between these two actions. It wasn't until I became a front-end developer and encountered issues caused by the difference between touch and click that I started to think about the relationship between the two actions.

In this article, I would like to share the common point and differences between touch and click, along with potential issues and solutions, based on my experience as someone who has just jumped into front-end development.

Beginning of the Issue

While maintaining the TOAST UI open source project, I saw a function similar to the one below.

function getConvertedEventType(type) {
  if (isMobile()) {
    if (type === 'mousedown') {
      type = 'touchstart';
    } else if (type === 'click') {
      type = 'touchend';
    }
  }

  return type;
}

This function converts the event type so that a touch event can be used instead of a mouse event for a mobile device. It changes the mousedown event type to the touchstart event type and the click event type to the touchend event type. Therefore, the event listener for click is changed to an event listener for touch.

The method using the event type conversion function has worked without any problem so far. But an unexpected issue occurred.

If we use a touch event listener using the event type conversion function in TOAST UI DatePicker, the drop-down element disappears when you touchend the date. But as shown in the figure above, we encountered an issue where, after the drop-down element disappears, the element beneath it is clicked. What caused this to happen?

Touch and Click

You said they're Gganbu!

To find the cause of this issue, let's talk about touch and click first. So far, we've used the event type conversion function without any problem. What do touch and click have in common so that the event type can be converted 1:1, and what are the differences between them that require the conversion of event types?

Common Point

Touch and click have one simple but crucial point in common: pressing and releasing an element on the screen to interact with the screen. That is, both actions are a sequence of a pressing action and a releasing action. For this reason, we can change touch and click and support them simply by converting the event type as with the event type conversion function.

Differences

If the two actions are so similar, why do we need an event type conversion function?

  1. This is because the mouse is always floating on the screen, whereas the touch is not. Consider the case of clicking or touching two separate elements within the same screen. To click with the mouse, we must first move the cursor over the first element and click, and then move the cursor over the second element and click. In other words, there is no choice but to perform the mousemove action that connects the two click behaviors. For touch, on the other hand, we move the finger over the first element and touch, and then move any finger over the second element and touch. The screen cannot figure out the action that connects the two touch behaviors.
  2. Clicks interact with the screen using only one pointer, but touches can interact with the screen using two or more touch pointers. This lets us use multi-touch gestures such as zoom-in, zoom-out, and rotation when using a mobile device.
  3. The mouse has the limitation of a single pointer. To overcome this, it has auxiliary components such as a right-click button and a wheel, and supports auxiliary actions through combination with the keyboard. For touch, however, all actions must be performed using only a pressing action, a moving action, and a releasing action. The biggest difference occurs here is the action of pressing and moving and then releasing. This action typically lets us perform drag and drop with the mouse, whereas it lets us perform scrolling with touch.

In the early days when mobile devices started becoming popular, it wasn't hard to find OSs or applications with little consideration of the aforementioned common point and differences. This made UI inconvenient and awkward for users, and the developers could not help hear complaints. Eventually these problems were soon fixed in the way we are now familiar with.

The Real Cause

Do you really believe they're still the same?

But I ran into a problem. To see what's causing the problem, let's look at the difference between click and touch in JavaScript.

Continuous Actions

Click has click as an event type for this, but touch does not. So instead, let's perform touch and click using the touchstart event type, which is an event that occurs when a touch starts.

Open Chrome DevTools and click the Toggle device toolbar () to perform a touch action as well. The code executed in the example above is as follows.
function createParagraph(text) {
  const el = document.createElement('p');
  el.innerText = text;

  return el
}

const printEl = document.getElementById('print');

document.addEventListener('click', () => {
  printEl.appendChild(createParagraph('click'));
});

document.addEventListener('touchstart', () => {
  printEl.appendChild(createParagraph('touch'));
});

When clicking with a mouse, only a click event occurs. When performing a touch, however, a click event also occurs after the touchstart event. If so, does the click event always occur when a touch is performed?

We mentioned that touch and click are a sequence of pressing and releasing actions. In the JavaScript event, the event for each separate action is fired separately. Event types related to touch include touchstart, touchemove, and touchend, and event types related to click include mousedown, mousemove, mouseup, and click.

Now, let's check the order in which each event occurs.

Let's perform a touch action again this time.

The code executed in the example above outputs the generated event types as follows.

function createParagraph(text) {
  const el = document.createElement('p');
  el.innerText = text;

  return el
}

const printEl = document.getElementById('print');

['touchstart', 'touchmove', 'touchend', 'mousedown', 'mousemove', 'mouseup', 'click'].forEach(eventType => {
  document.addEventListener(eventType, () => {
    printEl.appendChild(createParagraph(eventType));

    // Move the scroll to the bottom.
    window.scrollTo(0, document.body.scrollHeight);
  });
})

After performing the test, we found out that the events occur in the order shown above. An interesting point is that the click event occurs along with the touch events only when a simple touch action is performed, and even the mouse events occur together. (This is a general order for occurrence of events, and the order might vary by browser.) This means that we can perform the desired action without using the event type conversion function, because mouse events also occur when we perform a simple touch.

Touch-Click Delay

But why use the event type conversion function to change the event listener for click to the event listener for touch? This is because the click event generally does not occur immediately after the touch event occurs.

Let's see if that's actually the case.

/*
 * To check the delay, console.time and console.timeEnd have been used. Check out the following link for more detailed explanation.
 * https://developer.mozilla.org/en/docs/Web/API/Console/time
 * You can also measure the delay using performance.now.
 * https://developer.mozilla.org/en/docs/Web/API/Performance/now
 */

document.addEventListener('touchstart', () => {
  console.time('touch-click delay');
});

document.addEventListener('click', () => {
  console.timeEnd('touch-click delay');
});

The code above is used to check the delay between the occurrence of the touchstart event and the occurrence of the click event. If we run this code, we can see that there is a delay of approximately 300ms from the occurrence of the touchstart event until the occurrence of the click event. But the maximum delay for having the user feel that the system is reacting instantaneously is 100ms. If so, why has the delay been added, which is long enough to make the user feel that the response is slow?

Unlike mouse actions, touch actions have various gestures, and double-tap zoom is one of them. Suppose you want to double-tap zoom and there is a button in that position.

As shown above, if there is no delay, we will click the button on the first touch. If the button doesn't affect the action we're trying to do, that's fine. Otherwise, we won't be able to perform the gesture.

What if there is a delay? Look at the picture above. The button is touched on the first touch, but has not been clicked yet. In the meantime, if we touch again, the click is not performed and we can perform the gesture as intended.

But delays can cause problems as in my previous experience. When the date of TOAST UI DatePicker is touched, a touchend event occurs and the drop-down element disappears. However, a click event occurs at that position after 300ms. But the drop-down element we intended to click is no longer there, so the button beneath it is clicked.

Solving the Issue

Shall we be Gganbu?

Now that we've examined the problem, let's fix it.

Removing Delay

In general, pages to be displayed on mobile devices are created to be optimized for mobile devices. In this case, the size of the element on the page might be large enough that a gesture such as double-tap zoom might not be necessary. From Chrome 32 released in 2014, there is no delay generated for sites optimized for mobile pages. (Most other browsers work in the same way now.) If the width of the viewport is the same as the width of the device screen, it is called a mobile-optimized site.

Now, let's add the following syntax to the <head> tag of the delay example page.

<meta name="viewport" content="width=device-width">

We simply specified the width of the viewport, but we can see that the delay of 300ms has been removed. You can also use the following style to remove the touch-click delay for that element, even though it is not a recommended method.

* {
  touch-action: manipulation;
}

Removing Mouse Events

The method above is not effective if you create components to be used inside the page rather than the entire page, like TOAST UI. So, let's use the event type conversion function to change a mouse event listener to a touch event listener. Even in this case, mouse events will still occur, so it can cause unintended side effects as in my experience. If mouse events are not fired like other touch actions shown in the event occurrence sequence we saw earlier, we don't have to care about this problem.

This can be resolved by calling preventDefault() on touchstart or touchend.

Let's also perform a touch action again this time.

The code executed in the example above is as follows.

function createParagraph(text) {
  const el = document.createElement('p');
  el.innerText = text;

  return el
}

const printEl = document.getElementById('print');

document.addEventListener('click', () => {
  printEl.appendChild(createParagraph('click'));
});


document.addEventListener('touchend', (ev) => {
  ev.preventDefault();
  printEl.appendChild(createParagraph('touch'));
});

The test shows that only the click event occurs when the mouse is clicked, but unlike the previous example, only the touch event occurs when touch is performed. The example above is quite similar to the first example, but a listener is assigned to the touchend event instead of touchstart and preventDefault() is called internally to prevent the occurrence of mouse event.

Cautions

We knew that the purpose of calling preventDefault() in "Removing Mouse Events" is to suppress mouse events, but why did we change touchstart to touchend? It is related to one of the options of the third argument of addEventListener, passive. According to the spec, the passive option defaults to false, but in some browsers (especially Chrome and Firefox) the default value is set to true for touchstart and touchmove events to improve scroll performance. If the touchstart event is used, when the passive option is set to true, even if preventDefault() is called inside the callback function, only a console warning is output and preventDefault() does not work properly.

In addition, the event object passed as the first argument to the listener is also different, which is MouseEvent and TouchEvent Therefore, to use the property used in MouseEvent, you need to select an appropriate property among touches, targetTouches, and changedTouches of TouchEvent, and then find and use the appropriate Touch object in the property.

Conclusion

Touch and click, we're Gganbu.

In this article, we examined the issue from my experience and how to use the event type conversion function properly to solve this issue, while minimizing code duplication and supporting touch and click without any problem. Make sure that you know the order of occurrence of touch or click events, and don't forget to use removal of delay and mouse events appropriately.

Since the market share of the mobile has overtaken that of desktop for the first time, the importance of mobile web browsing has continued to increase. But the market share of desktop browsing is still fairly large, so we cannot focus solely on mobile browsing. Therefore, it is critical to clearly distinguish between touch and click, which are the most basic elements of user interaction in web browsing, and to use them correctly.

I hope that many developers who started jumping into front-end development can use this article as a cornerstone to support more features in mobile web browsing.

Daeyeon Kim2022.01.06
Back to list