見出し画像

(JS) pointer-events 移行ガイド

JS(ブラウザ)で仕えるポインター イベントは、マウス、ペン/スタイラス、タッチスクリーンなど、さまざまなポインティング デバイスからの入力を処理する最新の方法です。
現在、ポインター イベント レベル 2仕様はすべての主要なブラウザーでサポートされています。Internet Explorer 10 などの古いブラウザや Safari 12 以下向けに開発するのでなければ、マウス イベントやタッチ イベントを使用する意味はもうありません。

と、いうことで既存の mouse-events, touch-events の2系統を1本化できるpointer-events への置き換えをしたらちょいちょいあったので備忘録NOTEしましす。


移行時のポイント

① 移行前に使っている mouse-events / touch-events 処理を整理

移行後に万一バグがでると大変ですので、今の処理をキレイに整理、動作確認をしておきます。(すでに出来ていれば次へ)

  • イベントの関数と利用変数

  • addEventListener と removeEvent Listener の処理

が、キレイにかけていれば以降は簡単です。

mouse-events/touch-events の分岐・判定処理などは pointer-eventsでは1本かされるため不要となりますので、上記が整っていることは移行の副作用をなくす上で重要です。

以下のようにイベントは対応しているので、特に問題なくこれまでの処理にイベントをかけ替えることで動作すると思います。

mousedown === touchstart === pointerdown
mousemove === touchmove === pointermove
mouseup === touchend ===  pointerup


② ピンチ処理など複数タッチの対応

マウスでのドラッグと違い、タッチデバイスでのドラッグはブラウザのデフォルト制御でスクロール処理が動作して処理がぶつかってしまいます。またピンチ処理など複数タッチの処理も同様にぶつかってしまいます。
touch-events では  event.targetTouches.length でタッチ数を確認するなどして、mouse-events とは違う処理を書く場合がありますが、pointer-events に統一される事で、この処理の分岐問題が出てきます。

touch-events 処理にだけ書かれていたであろう、複数タッチによるUI処理の対応は  pointer-events では

 event.isPrimary

の判定で行うことが出来ます。

マウスとタッチで同じようにドラッグ処理をする場合に、ブラウザでのピンチやドラッグスクロールと競合しますので適宜、条件をつけて処理を行う必要があります。

// シングルタッチのみを許可する 
if (activePointerId === null && event.isPrimary) {
        isDragging = true;
        activePointerId = event.pointerId;
        offsetX = event.clientX - draggable.offsetLeft;
        offsetY = event.clientY - draggable.offsetTop;
        draggable.setPointerCapture(event.pointerId);
}


③ touch-actionの制御をする

タッチ制御のときの別の問題に、preventDefault() を呼んでもブラウザデフォルトのタッチ処理が動いてしまう問題があります。

ドラッグ処理は上記だけですとタブレットやスマホのブラウザでは画面ごとスクロールしたり正常に動かない場合があり、これを回避するには対応する要素のCSS設定 touch-action で制御することが可能です。
要素のCSS定義に直接 touch-action:none; を書くことも可能ですが、touch-eventsからの移行など既存のものがある場合は思わぬ副作用を避けるために、動的に設定して影響範囲を小さくすることが出来ます。

具体的には

  1. pointerdownイベントでドラッグを開始する際に、touch-action: none;を動的に設定します。これにより、ドラッグ中にスクロールやズームを防止します。

  2. pointerupイベントでドラッグが終了した際、touch-action: auto;に戻し、通常のタッチ操作(スクロールやズーム)を再度有効にします。

動的に行うメリット:

• 柔軟性:ドラッグ中のみtouch-action: none;を適用するため、通常の操作時にブラウザのスクロールやピンチズーム機能を維持できます。
• 最小限の影響:他の部分でのタッチ操作に影響を与えないため、ユーザーエクスペリエンスが向上します。

この方法で、必要な時にだけスクロールを無効化し、他の時は通常通りの操作を維持できるようにします。

コード例:(掲載用にChatGPTさんが書きましたが、ちゃんと動きました)

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Dynamic Touch Action Drag</title>
 <style>
   #draggable {
     width: 100px;
     height: 100px;
     background-color: red;
     position: absolute;
     /* 初期状態では touch-action は設定しない */
   }
 </style>
</head>
<body>
 <div id="draggable"></div>

 <script>
   const draggable = document.getElementById('draggable');
   let isDragging = false;
   let activePointerId = null;
   let offsetX = 0;
   let offsetY = 0;

   draggable.addEventListener('pointerdown', (event) => {
     // シングルタッチのみを許可
     if (activePointerId === null && event.isPrimary) {
       // touch-action をドラッグ開始時に無効化
       draggable.style.touchAction = 'none';
       
       isDragging = true;
       activePointerId = event.pointerId;
       offsetX = event.clientX - draggable.offsetLeft;
       offsetY = event.clientY - draggable.offsetTop;
       draggable.setPointerCapture(event.pointerId); // ポインターキャプチャを設定
     }
   });

   document.addEventListener('pointermove', (event) => {
     // シングルタッチかつドラッグ中であることを確認
     if (isDragging && event.pointerId === activePointerId) {
       draggable.style.left = `${event.clientX - offsetX}px`;
       draggable.style.top = `${event.clientY - offsetY}px`;
       event.preventDefault(); // ブラウザのデフォルト動作を防ぐ
     }
   });

   document.addEventListener('pointerup', (event) => {
     // ドラッグ操作終了
     if (event.pointerId === activePointerId) {
       isDragging = false;
       activePointerId = null;
       draggable.releasePointerCapture(event.pointerId); // ポインターキャプチャを解放
       
       // touch-action を元に戻す(auto に戻すことで、通常のスクロールやズームが再度有効化)
       draggable.style.touchAction = 'auto';
     }
   });
 </script>
</body>
</html>


作業の注意:

全体としてはpointer-eventsはとてもよく出来ていて、これまでのmouse&touch イベント郡を並列で使うカオスに平穏をもたらし、 置き換えは難なく出来ると思います。

Chromeのスマホエミューレーションで上記のコードは期待した動きをしてくれますが、デザイン上の調整で動的にスマホか判定して表示を調整する場合などでは期待通りに動作しないケースがありそうです。

また既存のmouse,touchとも併用できてしまうので別のモジュールなどで知らないうちに併用になってないかもご注意ください。js,nodeはどうしてもこの「不特定多数の開発者が増改築多すぎ沼」なのに責任者不在で、広告業界に牛耳られており、隠れ開発コストになりやすいところです。

最後に、あなたが仕事のために仕方なく調べているうちにこの記事にたどり着いたのなら「お疲れ様です!」と励ましたい。 上記が少しでも役に立てれば嬉しいし、間違いや分かりづらいところがあったためにあなたの貴重な時間を無駄にさせてしまって無いことを祈ります。


付録:

pointer events の詳細はこちら

touch-action に付いて解説している動画


いいなと思ったら応援しよう!

そんちゃー君
サポートありがとうございます😊 ベトナムにお越しの際はお声がけくださいね🌻