yourmystar tech blog
著者: matsuyama 公開日:

ポインターキャプチャの活用

この記事はユアマイスター アドベントカレンダー 2022の 5 日目の記事です。

はじめに

PointerEventの便利なメソッドの一つ、ポインターキャプチャをご存じでしょうか。

ポインターキャプチャは、ポインターキャプチャリリースが発生するまでの PointerEvent が、常にキャプチャを行なった要素上で行われているかのような挙動を実現するものです。それを活用して簡単にドラッグスクロール等の動きを実装することができます。

このポインターキャプチャを実装する上でいくつか躓きがありましたので、原因と解決例を共有したいと思います。

使用例

ポインターキャプチャを使用してのドラッグスクロールの実装方法例が MDN に記載されています。

HTML

<div id="slider">SLIDE ME</div>

JavaScript

function beginSliding(e) {
  slider.onpointermove = slide
  slider.setPointerCapture(e.pointerId)
}

function stopSliding(e) {
  slider.onpointermove = null
  slider.releasePointerCapture(e.pointerId)
}

function slide(e) {
  slider.style.transform = `translate(${e.clientX - 70}px)`
}

const slider = document.getElementById('slider')

slider.onpointerdown = beginSliding
slider.onpointerup = stopSliding

Element.setPointerCapture() - Web API | MDNより

slider 要素上でドラッグし始めると、pointerdown が発生し setPointerCapture()にてポインターキャプチャが行われます。それによりその後の slider 要素外で行われる pointermove が slider 要素上で行われていると認識される状態になり、ドラッグに伴い slider 要素がスライドします。ドラッグを離すと、pointerup が発生しキャプチャのリリースが行なわれるため通常状態に戻り、スライドが止まります。

releasePointerCapture()を行わなくても、pointerup の際にはリリースが発生します。

以下ではこの例を使用して発生した問題を紹介していきます。

問題1:スライダーにボタンを乗せるとクリックができない

商品に対して複数タグ付けを行いたい・タグをドラッグスクロール可能にしたい・タグをクリックして遷移させたい、という要件の元開発を進めていたところ、クリックしても遷移が発生しないという状況に陥ったのが経緯です。ボタンに限らず、スライダーの子要素にクリックイベントで動作させたいものを設置してもクリックを行えません。

<div id="slider">
  <button id="inner">SLIDE ME</button>
</div>
const inner = document.getElementById('inner')
inner.onclick = doSomething

function doSomething(e) {
  // 任意の処理
}

原因

子要素の click イベントが発生していないためです。

ドラッグを離した時、 pointerupclick → キャプチャリリース の順にイベントが発生します。そのため、click(=PointerEvent の一つ) は slider 要素のキャプチャ中に行われているために slider 要素上で発生し、バブリングの原理に則り子要素であるボタンには伝播しません。

解決例

pointermove が発生した時にキャプチャするようにします。そうすると pointermove しない場合にはキャプチャが行われず、子要素で click を発生させることができます。

function beginSliding(e) {
  slider.onpointermove = slide
}

function slide(e) {
  slider.style.transform = `translate(${e.clientX - 70}px)`
  slider.setPointerCapture(e.pointerId) // 移動
}

問題2:Safari ではドラッグを離すとボタンをクリックしてしまう

上記問題とは反対に、Safariだとドラッグした後にボタン上でクリックを離すとボタンをクリックしてしまうという事象が起きました。

※Safari バージョン 15.2 (2022/11/30 時点) にて確認

原因

キャプチャのリリースが click イベントよりも早いためです。

他ブラウザでは pointerupclick → リリース の順だったのに対して、Safari では pointerup → リリース → click の順でイベントが発生するため、click が子要素で発生してしまいます。

解決例

click 時に行いたかった処理を pointerup 時に行うようにします。キャプチャ時は子要素で pointerup は発生せず、キャプチャしていない時は発生するため、pointermove しない時のみ処理を行うなど上述の他ブラウザの場合と同じような挙動が期待できます。

inner.onpointerup = doSomething

問題3:ドラッグを離してもスライドしてしまう時がある

ドラッグを離しても、カーソルを動かすとスライドが行われる現象が発生する場合があります。

原因

意図しない pointercancel が発生しているためです。

キャプチャがリリースされるタイミングは、pointerup 時と releasePointerCapture() を行なった時に加えてもう一つあり、それはpointercancel イベントの発生時です。pointercancel が起こるとリリースが行われ、slider 要素外でドラッグを離しても slider 要素の pointerup とは認識されなくなるため、pointerup 時のメソッドに記述している slide 関数の解除が行われません。(もちろん要素内でドラッグを離した場合はキャプチャ関係なく slider 要素の pointerup が発生します)

解決例

pointercancel 時、あるいはキャプチャリリース時に関数の解除を行うようにします。

slider.onpointercancel = stopSliding

// あるいは
slider.onlostpointercapture = stopSliding

まとめ

  • キャプチャが行われているときはその子要素でPointerEventを発生させられない
  • pointerup時のイベントの発生順はpointerupclick → リリース 、Safari では pointerup → リリース → click
  • pointerup又はpointercancel又はreleasePointerCapture()の時にキャプチャのリリースが発生する
ポストするはてなブックマークに追加シェアする