タッチイベントと干渉するスクロールへの対処
JavaScriptでaddEventListenerを使用しタッチイベントの発生を検知、そのイベントをもとに要素を移動させる(いわゆる、ドラッグアンドドロップ)ことができるページを開発している際に、その動作をiPadで試したところ要素を動かそうとするとそのままページがスクロールしてしまい、意図通りに操作できない事象に直面した。
要素を長押しした際にはその要素を移動させ、スクロールは通常通り動作させるのが理想だが、、課題として放っていたが手を付け、結局いい感じに対処できたので途中試した方法も含めメモ。
うまくいかなかったやつら
1. touch-action: none
初めに試したのがこれ。
タッチイベント発生時にドラッグアンドドロップかスクロールかを判定し、ドラッグアンドドロップならbodyにtouch-action: none
を指定するというもの。
しかし、タッチイベント発生後に要素にtouch-action: none
をしてもうまくいかないのでダメ。
2. scrollByで自力でスクロールを実装
かなりぎこちない動作になった。
内容としては、要素をドラッグした場合には連続したタッチイベントの座標の差分をとり、それに適当な係数をかけた値分scrollBy
で動かすという感じ。途中まではこれを塩梅に調整しようとしていたが断念。
3. position: fixedとtopを指定し固定させる
bodyにposition: fixed
とページのスクロール量をtopに指定することで、表示位置を固定させようとするもの。いろいろ調べてるときに紹介されていた。うまくいきそうな感じはしたが、なんかうまくいかなかった(意図せずtop: 0になったりした、、アラート表示をさせたりほかのイベントとの関係とかだと思うが詳しくは調べていない)。
うまくいった方法
スクロールを発生させたくないイベントにpreventDefault()を呼び出す。
document.querySelectorAll('li').forEach(function(element) {
element.addEventListener('touchmove', function(event) {
if (isDragging) {
// スクロールさせない場合
event.preventDefault();
}
// ドラッグ&ドロップ処理
});
});
自分の場合はtouchmoveなどのイベントからタッチ、プレスなどの動作の判定、またタッチデバイスとマウスの場合などにイベントの扱いが異なることから、hammer.jsを使用し実装しており以下のような形になった。
document.querySelectorAll('li').forEach(function (element) {
const events = [
'mousedown', 'mouseup', 'mousemove',
'pointerdown', 'pointermove', 'pointerout', 'pointercancel',
'touchstart', 'touchmove', 'touchend', 'touchcancel'
];
events.forEach(function(eventType) {
element.addEventListener(eventType, handleEvent);
});
function handleEvent(event) {
if (isDragging) {
event.preventDefault();
}
}
const hammer = new Hammer(element);
hammer.on('pan tap press', function (event) {
// ドラッグ&ドロップ処理
});
});
所感
MDNのtouch-actionのページに
preventDefault() を呼び出すことでブラウザーがジェスチャーを扱うのを無効にすることができます
との記載があることに気づいたことで、このように落ち着いた。初めにtouch-action: none
を試したときに気づけばよかった。
最初にタッチイベントの無効化を試したときは適当な技術系の紹介サイトをみてコピペしたと思うけど、やっぱり公式や大御所のドキュメントに目を通すべきですね。
Discussion