20
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

touchやwheelのイベントリスナーにpassive: trueを明示的に指定する必要があるのか調べてみた

Last updated at Posted at 2022-02-24

これは何

EventTarget.addEventListener() #パッシブリスナーによるスクロールの性能改善 - Web API | MDN にあるように、以下のようなイベントに対してイベントリスナーを設定すると、スクロールの処理性能が大幅に低下する可能性があります。

  • touchstart
  • touchmove
  • wheel
  • mousewheel

それに対して、passiveオプションをtrueで設定するとこの問題を防ぐことができるようですが、一部のブラウザではデフォルトでpassive: trueになっているということで、毎回明示的に設定する必要があるのかどうか調べてみました。

結論

という前提で必要なときに指定する

passive: trueについて

touchやwheelイベントはイベントリスナーを設定すると、スクロールをする前にスクロールをブロックする処理がないかを確認するため、非同期のスクロールがブロックされてスクロールがカクつくという、いわゆるScroll Jankが発生します。1

それを防ぐために、スクロールをブロックする処理 (e.preventDefailt()) がない場合はpassive: trueというオプションをイベントリスナー設定時に付与してあげることでScroll Jankを防ぎ、スクロールをスムーズに行うことができるようになります。

ブラウザデフォルトの挙動について

実はこのtouchやwheelの一部イベントのpassive: trueオプションですが、ChromeやFirefoxではデフォルトでpassive: trueになっています。2 (対象はwindow, document, document.body3)

しかしMDNの互換性の表を見てみると、Safariはデフォルトがtrueになっていないようです。

Safariでの挙動について

macOS Safari version: 15.3

実際に以下のようなコードを実装し、Safariで動作確認をしてみます。

document.addEventListener('wheel', (e) => {
  e.preventDefault()
  console.log('handler called')
});

確認をしてみると、スクロールは無効化されず、普通にスクロールができてしまいました。

次に明示的にpassive: falseを渡してみます。

document.addEventListener('wheel', (e) => {
  e.preventDefault()
  console.log('handler called')
}, { passive: false });

すると予想通りスクロールを無効化することができました。

実はSafariの「実験的な機能」の中に、Wheel Event listeners on the root made passiveがあり、オンになっていることでこのような挙動になっていたようでした。これはSafari15.3ではデフォルトでオンのようです。 (いつからこうなっているのかまでは調べきれませんでした)

試しにオフにしてみると、最初のコードでもスクロールが無効化されました。

また、touchイベントも、Safari 11.1からデフォルトでpassive: trueになっているようです。4

Reactでの挙動

React 17ではこちらのissueでデフォルトの挙動について議論がされた後、passive: trueがデフォルトになったようです。
対象のイベントハンドラは、onTouchStart, onTouchMove, onWheelです。5

ただ、passive: trueになったのはいいのですが、現時点(react@17.0.2)ではこのpassiveを操作できるオプションが存在していないようで、逆にpassive: trueにすることができない問題もあるようです。これにより、onTouchStart, onTouchMove, onWheelのイベントハンドラ内ではe.preventDefault()でイベントをブロックできないようです。

const handler = (e) => {
  e.preventDefault() // スクロールを止めることはできない
  console.log('handler called')
}

const App = () => {
  return (
    <div onWheel={e => handler(e)}>
      <Contents />
    </div>
  );
}

これについては、issueも存在している(React 18 not passive wheel / touch event listeners support · Issue #22794 · facebook/react)ようなので、今後対応されるかもしれません。

まとめ

という前提で必要なときに指定する

参考

  1. https://dom.spec.whatwg.org/#observing-event-listeners

  2. https://developer.mozilla.org/ja/docs/Web/API/EventTarget/addEventListener#%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%83%BC%E3%81%AE%E4%BA%92%E6%8F%9B%E6%80%A7

  3. https://developers.google.com/web/updates/2017/01/scrolling-intervention

  4. https://developer.apple.com/library/archive/releasenotes/General/WhatsNewInSafari/Articles/Safari_11_1.html

  5. https://github.com/facebook/react/pull/19654/files

20
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?