閑古鳥

オールドプログラマの日記。プログラミングとか病気(透析)の話とか。

任意の要素の下でスワイプしたときだけスクロールを無効にしたい

スマホで動くお絵かきツールみたいなのを書いている時に、Canvasの上をスワイプでなぞった箇所に線を引くようにしたら、線を引きながらページがスクロールしてしまう現象にはまりました。

f:id:wata_d:20200609093236g:plain

これでは使い物にならないので、Canvasの上でスワイプしたときはスクロールさせないようにしたい。という時はCSStouch-action: none;を指定してあげると良いようです。最初試しもせずに、これを設定するとonTouchMoveイベントも発生しなくなるのでは…と思って無駄に遠回りしてしまいました。


せっかくなので以下遠回りした記録。onTouchMoveでpreventDefault()を呼べばスクロールを無効化できるよね、と思って、当初以下のように書きました。React使ってます。

function App() {
  const handleTouchMove = (event: React.TouchEvent) => {
    event.preventDefault(); // これは効かない
    // => [Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive.
  }

  return (
    <div className="App">
      <div className="scroll-off" onTouchMove={handleTouchMove}>
        {/* この要素の中だけスクロールを無効にしたい */}
      </div>
    </div>
  );
}

しかし、コメントにあるとおりこれは動作しません。

Reactの要素にはonTouchMoveCaptureというイベントも別にありますが、これを使っても挙動は変わらないので、どうしてもpreventDefaultを使いたい場合は以下のようにする必要がありそうです。

function App() {
  const divRef = React.useRef<HTMLDivElement>(null);
  React.useEffect(() => {
    const elem = divRef.current!
    elem.addEventListener('touchmove', (e: any) => handleTouchMove(e), { passive: false });
    return () => elem.removeEventListener('touchmove', (e: any) => handleTouchMove(e));
  }, []);

  const handleTouchMove = (event: React.TouchEvent) => {
    event.preventDefault();
  }

  return (
    <div className="App">
      <div className="scroll-off" ref={divRef}>
        {/* この要素の中だけスクロールを無効にしたい */}
      </div>
    </div>
  );
}

さすがにこれはいまいちですよねぇ。というわけで最初に戻って

function App() {
  const handleTouchMove = (event: React.TouchEvent) => {
    // ...
  }

  return (
    <div className="App">
      <div className="scroll-off" style={{ touchAction: 'none' }} onTouchMove={handleTouchMove}>
        {/* この要素の中だけスクロールを無効にしたい */}
      </div>
    </div>
  );
}

でいいかなということに。