はじめに
TouchEventなどに対するイベントリスナでは、Event.preventDefault()
をよびだすことができません。もしイベントリスナ内で呼び出そうとすると、次のようなエラーを目にするでしょう。
[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See...
これは、イベントリスナが受動イベントリスナとして扱われていることが原因です。Event.preventDefault()
をよびだすには、イベントリスナが受動的でないことが必要なので、本記事ではReactでイベントリスナを受動的でない形で定義する方法を説明します。
非受動イベントリスナを設定する
(以下、TypeScriptで記述しますがJavaScriptでも同様です。)
次のような、内部にSVGの<Rect>
エレメントを持つReactコンポーネントを考えます。
const MyCircle = () => {
return(
<rect x={0} y={10} width={50} height={100} />
);
};
ここにタッチイベントに対するリスナ(長方形を触るとイベントが発火する)を定義するには、次のようにします。
const MyCircle = () => {
// タッチイベントリスナ
const onTouchStart = (event: Event) => {
e.preventDefault(); // ここでpreventDefault()を呼べる
const touchEvent = event as TouchEvent;
console.log(touchEvent.changedTouches[0].pageX);
};
const onTouchMove = (event: Event) => {...}; // onTouchStartと同様
const onTouchEnd = (event: Event) => {...}; // onTouchStartと同様
const circleRef = useRef<SVGRectElement>(null);
useEffect(() => {
ref.current?.addEventListener("touchstart", onTouchStart, { passive: false });
ref.current?.addEventListener("touchmove", onTouchMove, { passive: false });
ref.current?.addEventListener("touchend", onTouchEnd, { passive: false });
return (() => {
ref.current?.removeEventListener("touchstart", onTouchStart);
ref.current?.removeEventListener("touchmove", onTouchMove);
ref.current?.removeEventListener("touchend", onTouchEnd);
});
});
return(
<rect x={0} y={10} width={50} height={100} ref={circleRef} />
);
};
ポイントは次のとおりです。
- イベントリスナの引数は
Event
型とする。TouchEvent
型のプロパティを利用するときは、キャストする。 - DOM要素には、refを設定してアクセスする
- useEffect内で、
ref.current?.addEventListener()
でリスナを設定する。オプションとして{ passive: false }
を渡す。 - クリーンアップ時に
ref.current?.removeEventListener()
でリスナを除去する。
これで、TouchEventのリスナでもEvent.preventDefault()
が動作するようになります。
おわりに
こんな面倒なことしなくても非受動イベントリスナを設定できるようになるといいですねorz