【JavaScript】click と touchend の両方のイベントリスナーのうち touchend だけを実行する

デスクトップ向けのブラウザーとスマートフォン向けのブラウザーどちらにも対応する場合、クリックとタップの両方のイベントリスナーをセットすることが良くあります。もちろん、デスクトップであればクリックのイベントリスナーのみを起動し、スマートフォンであれば タップのイベントリスナーのみを起動したいはずです。

しかし、スマートフォンではクリックとタップの両方のイベントリスナーが呼び出されてしまい困る場合があります。今回は、スマートフォンでタップのイベントリスナーだけを起動する方法について解説します。

イベントの発出の順番

マウスのクリックとスマートフォンのタップに関連するイベントには、click, mouseup, mousedown, touchstart, touchend イベントがあります。例えば、これらすべてのイベントに対してイベントリスナーをセットしたとしましょう。ここでは、body 要素に対して各種イベントリスナーをセットし、pre 要素の中に発生したイベントの名前を追記します。

<pre id="console"></pre>
document.body.addEventListener('click', showEvent);
document.body.addEventListener('mousedown', showEvent);
document.body.addEventListener('mouseup', showEvent);
document.body.addEventListener('touchstart', showEvent);
document.body.addEventListener('touchend', showEvent);

const console_el = document.getElementById('console');

function showEvent(event) {
  let text_node = document.createTextNode(event.type + '\n');
  console_el.appendChild(text_node);
}

デスクトップ向けのブラウザーでマウスをクリックすると、次の順番でイベントが発生します。

mousedown
mouseup
click

タップ関連のイベントは発生しません。一方、スマトフォン向けブラウザーで画面をタッチすると、次の順番でイベントが発生します。

touchstart
touchend
mousedown
mouseup
click

スマートフォンでは、マウス関連のイベントも発生してしまいます。これが困るわけです。

event.preventDefault() メソッドが解決

前述の問題は、Event オブジェクトの preventDefault() メソッドで解決できます。イベントリスナー関数を次のように変更します。

function showEvent(event) {
  event.preventDefault();
  let text_node = document.createTextNode(event.type + '\n');
  console_el.appendChild(text_node);
}

こうすることで、発生するイベントとその順番は次のようになります。

touchstart
touchend

preventDefault() メソッドは、イベントが発生したときの規定の挙動(デフォルトアクション)を止めます。タップなどのタッチ関連のイベントの仕様は、W3C の Touch Events という仕様書に規定されています。そして、touchend のデフォルトアクションとして mousedown, mouseup, click イベントが挙げられています。つまり、touchend イベントが発生したら、mousedown, mouseup, click イベントを発出することになっています。

クリック系イベントだけではダメなのか?

スマートフォンではタップしたらクリック系のイベントも発生して困るなら、touchstarttouchend を使わずにクリック系のイベントのみを使えばいいじゃないか、と思われるのではないでしょうか。もちろん、それで困らないならそれで良いですし、実際に筆者も click イベントだけで何とかすることのほうが多いと言えます。

では、タッチ系イベントを使わないといけない場合とは、どんなときでしょうか。考えられる理由としては 2 つあります。

  • タッチ系イベントで得られる情報が必要な場合
  • パフォーマンスにシビアな場合

では、それぞれを詳しく見ていきましょう。

タッチ系イベントから得られる情報

touchstarttouchend イベントから得られる情報には、マウス関連のイベントから得られないタッチ特有の情報もあります。もしタッチ特有の情報が必要な場合は、touchstarttouchend イベントを使わざるを得ません。

タッチ関連の情報とは具体的には、他のタッチの情報です。近年のスマートフォンは、タッチは一点ではなく複数のタッチを同時に認識できます。touchstarttouchend イベントからは、それら他の指が触れている情報も得られます。詳細は MDN の TouchEvent の説明を読むと良いでしょう。

パフォーマンス向上

スマートフォンでは、タップすると、まずタッチ系のイベントが発生してからマウス系のイベントが発生します。もし、できる限りパフォーマンス良く反応したいなら、タッチ系のイベントを使ったほうが良いでしょう。

では、タッチ系のイベントとマウス系のイベントの発生タイミングの差どれくらいなのでしょうか。ほとんど差はないだろうと思うかもしれませんが、いちおう次のコードで確かめてみました。このコードでは、touchstart から click イベントが発生するまでの差を計測しています。

document.body.addEventListener('touchstart', showEvent);
document.body.addEventListener('click', showEvent);

let start = 0;
function showEvent(event) {
  if (event.type === 'touchstart') {
    start = Date.now();
  } else if (event.type === 'click') {
    window.alert((Date.now() - start) + ' ミリ秒');
  }
}

ちょっと古い端末で申し訳ないのですが、iPhone 11 Pro の Safari で 100 ミリ秒前後の差がありました。Google Pixel 4 の Chrome では 50 ミリ秒前後の差でした。意外に Safari のほうが差が大きいです。

この 100 ミリ秒の差をどう考えるかにもよりますが、利用シーンによっては、その違いを感じる場合もあるでしょう。

まとめ

正直なところ、筆者はほとんどのシーンで click イベントだけで何とかなっており、あまりタッチ系のイベントを扱うことがありません。ところが、稀にタッチ系のイベントを駆使しなければならないときがあり、いつも touchend イベントのデフォルトアクションのことを忘れてしまいます。

今回の記事はその忘備録ですが、みなさんにもお役に立てば幸いです。 今回は以上で終わりです。最後まで読んでくださりありがとうございました。それでは次回の記事までごきげんよう。

Share