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 の抑止より優先する。これらを守れば、初心者でも滑らかで安定したスクロール・タッチ体験を設計できます。
