JavaScript | DOM 操作:イベント発展 – passive: true

JavaScript
スポンサーリンク

passive: true とは何か

passive: true は、addEventListener のオプションで「このリスナーでは preventDefault を呼ばない」とブラウザに約束する設定です。ここが重要です:スクロールやタッチなど“高頻度で動くイベント”に対して passive を宣言すると、ブラウザが最適化でき、スクロールの引っかかりや入力遅延を減らせます。逆に、スクロールを止めたい(preventDefault したい)場面では passive を付けてはいけません。


どこで使うのか(対象イベントのイメージ)

スクロール関連(scroll / wheel / touchmove)

スクロール・ホイール・タッチムーブはフレームごとに近い頻度で発火します。ここが重要です:画面を滑らかに保ちたいなら、処理を軽くして passive: true を付けます。スクロール停止など特別な制御が必要なケースだけ passive を外します。

タッチ開始・終了(touchstart / touchend)

タッチ開始・終了でも、基本は passive で十分です。ここが重要です:ジェスチャーを自前で組む場合にタッチの既定動作を抑止したいなら、そのハンドラでは passive を外して preventDefault を使います。


基本の使い方(最適化の型)

window のスクロールを軽く監視する

<div style="height:2000px"></div>
<script>
  window.addEventListener("scroll", () => {
    // 軽い処理だけ(クラス切り替えや進捗バーの更新など)
  }, { passive: true });
</script>
HTML

ここが重要です:passive: true にすると、ブラウザは「このハンドラではスクロールを止めない」と確信し、スクロールの応答性を優先できます。重い計算は避け、描画更新は requestAnimationFrame に寄せるとさらに滑らかになります。

ホイールイベントで軽い可視化をする

<script>
  window.addEventListener("wheel", (e) => {
    // e.preventDefault(); // passive: true では呼べない(例外)
    // 軽いログやメトリクス更新のみ
  }, { passive: true });
</script>
HTML

ここが重要です:passive にしたハンドラ内で preventDefault を呼ぶと例外になります。止める必要があるデザインなら { passive: false } で登録します。


スクロールやジェスチャーを“止めたい”ときの正しい設定

特定領域だけスクロールをロックする

<div id="panel" style="height:140px; overflow:auto; border:1px solid #ccc">
  <div style="height:600px"></div>
</div>
<script>
  // この領域では条件によりスクロールを抑止したい
  panel.addEventListener("wheel", (e) => {
    const shouldLock = /* 条件(例えばモーダル中の縦スクロール禁止など) */;
    if (shouldLock) e.preventDefault();
  }, { passive: false });
</script>
HTML

ここが重要です:スクロール抑止には必ず { passive: false } が必要です。抑止は体験を損ねやすいので、必要な場面(モーダル中、水平スクロール専用領域)に限定します。広域で一律に止める設計は避けます。

タッチスクロールをジェスチャーに置き換える

<div id="pad" style="height:160px; border:1px solid #ccc"></div>
<script>
  let startY = 0;
  pad.addEventListener("touchstart", (e) => {
    startY = e.touches[0].clientY;
  }, { passive: true }); // ここは止めない

  pad.addEventListener("touchmove", (e) => {
    const dy = e.touches[0].clientY - startY;
    const isGesture = Math.abs(dy) < 8; // 仮の判定
    if (isGesture) {
      e.preventDefault();              // 既定スクロールを止める
      // 独自ジェスチャー処理
    }
  }, { passive: false }); // 止める可能性があるので false
</script>
HTML

ここが重要です:止める可能性のあるハンドラでは passive: false を選択し、他は passive: true を付けて最適化する“使い分け”が鍵です。


パフォーマンス設計(passive と間引きの組み合わせ)

requestAnimationFrame と組み合わせる

<script>
  let ticking = false;
  function onScrollFrame() {
    ticking = false;
    // 軽い描画更新(進捗バーやヘッダ影の切り替えなど)
  }
  window.addEventListener("scroll", () => {
    if (!ticking) {
      ticking = true;
      requestAnimationFrame(onScrollFrame);
    }
  }, { passive: true });
</script>
HTML

ここが重要です:passive でスクロールの応答性を守りつつ、rAF で更新を“1フレームに1回”へまとめると、チラつきや無駄な再計算を減らせます。

throttle の簡易例(一定間隔でのみ処理)

<script>
  let last = 0;
  const wait = 80; // ms
  window.addEventListener("scroll", () => {
    const now = performance.now();
    if (now - last < wait) return;
    last = now;
    // 軽い処理のみ
  }, { passive: true });
</script>
HTML

ここが重要です:スクロールは高頻度なので、間引きはほぼ必須です。UIの許容遅延に合わせて wait を調整します。


よくある落とし穴(誤設定・誤用の回避)

preventDefault と矛盾する設定

passive: true のハンドラで preventDefault を呼ぶと例外が発生します。ここが重要です:止めたいなら { passive: false } を使い、止めない監視は { passive: true } を徹底します。設定の整合性を常に確認しましょう。

“とりあえず全部 passive”の乱用

すべてを passive にすると、止めるべき場面で止められなくなります。ここが重要です:用途に応じて使い分け、スクロールロックやジェスチャー制御のハンドラは明示的に passive: false にします。

重い処理の直書き

passive にしても、ハンドラの中身が重ければカクつきます。ここが重要です:計算は最小限に、描画更新はクラス切り替えや rAF でまとめ、必要なら throttle/debounce を組み合わせます。

スクロール停止の過剰設計

広域でスクロールを止めるとユーザーの期待と衝突します。ここが重要です:まず CSS(body に overflow: hidden など)で安全に制御できないか検討し、JS で止めるのは最後の手段にします。


実践例(ヘッダ影、進捗バー、モーダルのロック)

ヘッダの影をスクロール開始時だけ付与

<header id="head" style="position:sticky; top:0; background:white">ヘッダ</header>
<script>
  function syncShadow() {
    head.classList.toggle("shadow", window.scrollY > 0);
  }
  window.addEventListener("scroll", syncShadow, { passive: true });
  syncShadow();
</script>
HTML

ここが重要です:軽い分岐と最小のスタイル変更に留めると、passive の恩恵がそのまま効きます。

ページ上部の進捗バーを滑らかに更新

<div id="bar" style="position:fixed;top:0;left:0;height:3px;background:#09f;width:0"></div>
<script>
  function updateBar() {
    const h = document.documentElement.scrollHeight;
    const vh = window.innerHeight;
    const max = Math.max(h - vh, 1);
    const w = Math.min(window.scrollY / max, 1) * 100;
    bar.style.width = w + "%";
  }
  window.addEventListener("scroll", () => requestAnimationFrame(updateBar), { passive: true });
  updateBar();
</script>
HTML

ここが重要です:計算は最小限、反映は rAF 内で一発。スクロール体感を損なわずに視覚フィードバックを与えられます。

モーダル中は背景スクロールをロック

<div id="modal" class="open">…</div>
<script>
  function lockScroll(lock) {
    document.body.style.overflow = lock ? "hidden" : "";
  }
  // 開閉時に lockScroll(true/false) を呼ぶ
</script>
HTML

ここが重要です:スクロール抑止を JS の preventDefault で頑張るより、CSS overflow 切り替えで“安全に広域ロック”するほうが副作用が少なく、passive 設計とも整合します。


まとめ

passive: true は「このリスナーでは preventDefault しない」という約束で、スクロールやタッチなど高頻度イベントの応答性を高める最適化です。止めたい場面は { passive: false } にし、設定の整合性を保つ。処理は軽く、requestAnimationFrame や throttle で間引き、CSS で制御できるものは JS の抑止より優先する。これらを守れば、初心者でも滑らかで安定したスクロール・タッチ体験を設計できます。

タイトルとURLをコピーしました