本文へスキップ
  1. Web BENTO JS
  2. カテゴリ一覧
  3. スクロールUI
07 / SCROLL 12 パーツ

スクロール連動UIのJS実装 12選

コピペで動くスクロール連動UIのサンプル集です。プレビューはミニスクロール枠を実際にスクロールして試せます。HTML / CSS / JS タブでコードを確認し、全部コピーを押せば1回の貼り付けで完成します。すべて依存ライブラリなしのバニラJSで、data-bnto-* 属性による自動初期化式です。scrollリスナーは passive: true + requestAnimationFrame で軽量です。

該当するパーツが見つかりませんでした。

↓ この枠内をスクロール

読み進めた量が上部のバーにリアルタイムで反映されます。ブログや長文記事の「読了率」表示の定番パーツです。

あとどれくらいで読み終わるかがひと目で分かるため、離脱防止にも効果があると言われています。

バーの色は --c1、太さは --h で自由に変更できます。

— 完 —

読了プログレスバー

<!-- bodyの直下などに1つ設置(data-target="#box" で特定ボックス連動も可) -->
<div class="bnto-sprog" data-bnto-sprog>
  <i class="bar"></i>
</div>
使い方のコツ

固定ヘッダーがあるサイトでは top: 0 をヘッダーの高さに変えると重なりを防げます。pointer-events: none を付けているので、バーの下の要素のクリックを邪魔しません。

↓ この枠内をスクロール

少しスクロールすると、右下にボタンがふわっと出現します。

クリック(タップ)すると、なめらかに一番上まで戻ります。

長いページの定番。出現のしきい値は data-show 属性で調整できます。

— 完 —

トップへ戻るボタン

<!-- data-show:出現するスクロール量(px) -->
<button type="button" class="bnto-totop" data-bnto-totop data-show="300" aria-label="ページ上部へ戻る">
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>
</button>
使い方のコツ

デモは data-show="60"、実ページでは 300 前後(1画面弱)がおすすめです。visibility も切り替えているので、非表示中に誤タップされる心配がありません。

↓ この枠内をスクロール

下にスクロールするとヘッダーがコンパクトに縮み、コンテンツの表示領域が広がります。

一番上まで戻ると、元の大きさにふわっと戻ります。

ロゴのサイズと上下の余白をCSSのtransitionで変化させています。

— 完 —

SITE LOGO MENU

スクロールで縮むヘッダー

<!-- data-at:縮み始めるスクロール量(px)。固定ヘッダーの高さぶん body に padding-top を -->
<header class="bnto-shrinkhead" data-bnto-shrinkhead data-at="80">
  <span class="lg">SITE LOGO</span>
  <span class="mn">MENU</span>
</header>
使い方のコツ

position: fixed のヘッダーは本文に重なるため、body { padding-top: 64px; } のように最大時の高さぶんの余白を確保してください。縮小時に背景色を半透明+backdrop-filter: blur() にするのも人気です。

↓ 下へスクロールで隠れ、上で再表示

読み進めている間(下スクロール中)はヘッダーが引っ込み、画面を広く使えます。

少しでも上に戻すと、すぐにヘッダーが降りてきます。「メニューに戻りたい」瞬間を逃しません。

スマホサイトのヘッダーで特によく使われるパターンです。

— 完 —

SITE LOGO MENU

スクロールで隠れるヘッダー

<!-- 固定ヘッダーの高さぶん body に padding-top を確保 -->
<header class="bnto-hidehead" data-bnto-hidehead>
  <span class="lg">SITE LOGO</span>
  <span class="mn">MENU</span>
</header>
使い方のコツ

判定の遊び(6px)を入れることで、スクロールの微妙な揺れでヘッダーがちらつくのを防いでいます。y > 50 は「ページ最上部付近では隠さない」ための下限です。数値はサイトに合わせて調整してOKです。

はじめに

↓ スクロールすると目次の現在地が動きます

読んでいる位置に合わせて、左の目次がハイライトされます。

使い方

目次のリンクをクリックすると、該当セクションへスムースに移動します。

まとめ

長いドキュメントやマニュアルページの定番UIです。

目次スクロールスパイ

<!-- href の先(id="sec1" など)が本文側にあればOK -->
<nav class="bnto-spy" data-bnto-spy aria-label="目次">
  <a href="#sec1">はじめに</a>
  <a href="#sec2">使い方</a>
  <a href="#sec3">まとめ</a>
</nav>

<!-- 本文側(見出しに id を振る) -->
<h2 id="sec1">はじめに</h2> …
<h2 id="sec2">使い方</h2> …
<h2 id="sec3">まとめ</h2> …
使い方のコツ

判定オフセットの 60 は「固定ヘッダーの高さ+少し」に合わせるとズレません。2カラムレイアウトのサイド目次に置き、position: sticky で追従させるのが定番の使い方です。

セクション 1

↓ 右端のドットが現在地を示します

スクロールに合わせて、対応するドットが点灯します。

セクション 2

ドットをクリックすると、そのセクションへスムースに移動できます。

セクション 3

1画面ずつ見せる縦長LPと相性抜群のナビゲーションです。

セクション進捗ドット

<!-- href の先(id="sec1" など)=各セクション。ドットは自動でハイライト -->
<nav class="bnto-sdots" data-bnto-sdots aria-label="セクション移動">
  <a href="#sec1" aria-label="セクション1"></a>
  <a href="#sec2" aria-label="セクション2"></a>
  <a href="#sec3" aria-label="セクション3"></a>
</nav>
使い方のコツ

ドットは中身が空のリンクなので、aria-label でセクション名を必ず伝えましょう。目次スパイと同じ仕組みなので、ドットの代わりに短いラベル付きにするアレンジも簡単です。

↓ この枠内をスクロール

少し読み進めたタイミングで、下からCTAバーがスライドインします。

「興味を持ち始めた頃に申し込み導線を出す」動きで、コンバージョン施策の定番です。

ボタンの文言は「今すぐ試す」「無料で始める」など、行動がイメージできる言葉が効果的です。

出しっぱなしにせず、フッター付近では消す設計にすると邪魔になりません。

一番上まで戻ると、CTAはまた引っ込みます。

— 完 —

初回限定 20%OFF!

下部固定CTA

<!-- data-show:出現するスクロール量(px) -->
<div class="bnto-scta" data-bnto-scta data-show="300">
  <span class="tx">初回限定 20%OFF!</span>
  <button type="button" class="bt">詳しく見る</button>
</div>
使い方のコツ

CTAがフッターのリンクなどを隠さないよう、body { padding-bottom: 70px; } の確保を忘れないでください。×ボタンを付けて cta.remove() する「閉じられるCTA」へのアレンジも簡単です。

↓ この枠内をスクロール

右下の円形ゲージに、スクロール率が%数字とリングで表示されます。

進捗バー(横棒)より省スペースで、デザインのアクセントにもなります。

SVGの pathLength="100" を使うと、%の値をそのまま描画に使えて計算いらずです。

— 完 —

0%

スクロール率ゲージ

<!-- pathLength="100" = 円周を100として扱える(%がそのまま使える) -->
<div class="bnto-spct" data-bnto-spct>
  <svg viewBox="0 0 36 36" aria-hidden="true">
    <circle class="tr" cx="18" cy="18" r="16" pathLength="100"/>
    <circle class="pg" cx="18" cy="18" r="16" pathLength="100"/>
  </svg>
  <b class="pc">0%</b>
</div>
使い方のコツ

「トップへ戻るボタン」と合体させて、ゲージ付き戻るボタンにするのが人気のアレンジです。100%になったら色を変える・チェックマークを出すなどの演出も、p === 100 の分岐を足すだけで作れます。

↓ この枠内をスクロール

最上部にいる間だけ、下部で「SCROLL ↓」が明滅して下への誘導を促します。

少しスクロールするとフェードアウトし、一番上まで戻ると再び表示されます。

ファーストビューが1枚絵のヒーローで「この下にコンテンツがある」ことを伝える定番パーツです。

— 完 —

スクロールダウン誘導

<!-- ヒーロー下部などに1つ設置(data-hide:消えるスクロール量px) -->
<div class="bnto-sdown" data-bnto-sdown data-hide="80" aria-hidden="true">
  <span class="tx">SCROLL</span>
  <svg class="ar" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
</div>
使い方のコツ

画面全体ではなくヒーローセクション内の下端に出したいときは、親に position: relative を指定して position: fixedabsolute に変えてください。pointer-events: none なので下の要素のクリックを邪魔せず、装飾扱いのため aria-hidden="true" を付けています。data-hide はファーストビュー高さの1〜2割(80〜150px)程度が目安です。

↓ この枠内をスクロール

  • 記事 1:無限スクロールのデモです
  • 記事 2:下へ進むと自動で増えます
  • 記事 3:読み込みはもう少し先です
  • 記事 4:さらにスクロール…

↓ さらにスクロール

無限スクロール

<!-- data-max:自動追加の上限件数。末尾の .more(番兵)が見えるたびに追加 -->
<div class="bnto-infscroll" data-bnto-infinitescroll data-max="30">
  <ul class="list">
    <li>記事 1</li>
    <li>記事 2</li>
    <li>記事 3</li>
    <li>記事 4</li>
  </ul>
  <p class="more" role="status">読み込み中…</p>
</div>
使い方のコツ

監視するのは番兵1要素だけなので、scrollイベント方式より軽量です。実運用では「3件のダミー追加」の部分を fetch() での記事取得に置き換えてください。追加しすぎるとDOMが重くなるため、data-max の上限を設けるか、ある程度で「もっと見る」ボタンに切り替える設計がおすすめです。

↓ 半分ほど読むとカードが出ます

記事を読み進めたユーザーにだけ「次のおすすめ」を提案するパーツです。

読了率が data-at(%)を超えると、右下からカードがスライドインします。

×で閉じると、それ以降は再表示されません。

— 完 —

次記事スライドイン

<!-- data-at:カードが出る読了率(%)。リンク先は次に読んでほしい記事へ -->
<div class="bnto-nextcard" data-bnto-nextcard data-at="50">
  <span class="tag">NEXT</span>
  <a class="tt" href="next-article.html">次のおすすめ:記事タイトル</a>
  <button type="button" class="cl" aria-label="閉じる">×</button>
</div>
使い方のコツ

data-at は50〜70%(記事に興味を持ったと判断できるあたり)が目安です。×で閉じた状態はページ内の変数で保持しているためリロードで戻ります。「1日出さない」等にしたい場合は closed の代わりに localStorage を使うアレンジで対応できます。

↓ この枠内をスクロール

ここはまだ範囲外です。下のセクションに入るとCTAが追従を始めます。

対象セクション

この範囲の間だけ、右のCTAがスクロールについてきます。

範囲の終わりに近づくと…

CTAは下端で止まり、それ以上はついてきません。

ご相談は無料です

範囲を出ました。CTAは範囲内に置いたままになります。

— 完 —

範囲内追従サイドCTA

<!-- 追従させたい範囲(親要素)に position: relative とCTAぶんの余白を確保 -->
<!-- 範囲を別要素にしたいときは data-range="#id" で指定 -->
<section style="position: relative; padding-right: 110px;">
  <h2>料金プラン</h2>
  <p>…このセクションの間だけ右のCTAが追従します…</p>
  <div class="bnto-rangesticky" data-bnto-rangesticky data-offset="16">
    ご相談は無料です
    <button type="button" class="bt">申し込む</button>
  </div>
</section>
使い方のコツ

親要素に position: relative と、CTAの幅ぶんの余白(padding-right)が必要です。data-offset は追従中の上端からの距離です。単純な縦追従だけならCSSの position: sticky でも実現できますが、この方式はJSなのでミニ枠内デモや「追従開始をずらす」等の細かい制御がしやすいのが利点です。

スクロール連動UIの基礎知識

スクロール連動UIは、ページのスクロール位置に合わせて「今どこにいるか」「あとどれくらいか」をユーザーに返すためのパーツ群です。読了プログレスバー・目次ハイライト・トップへ戻るボタンなどはどれも、長いページの迷子を防ぐ定番装置です。このページのサンプルはすべて依存ライブラリなしのバニラJavaScriptで、jQueryもフレームワークも不要です。

使い方は、HTML / CSS / JSの3タブをそれぞれ貼り付けるか、「全部コピー」ボタンでひとまとめのコードを1回貼り付けるだけです。初期化は data-bnto-* 属性の自動検出式なので、複数設置しても二重にコードを貼っても壊れません。デモは data-target 属性でミニ枠のスクロールに接続していますが、コードは属性を省略すればそのままページ全体(window)基準で動きます

現在地フィードバックのUX効果

ユーザーは「終わりが見えない縦長ページ」で離脱しやすくなります。進捗バーやスクロール率ゲージは残り量を可視化して読み切りを後押しし、目次スパイやドットナビは現在地を示して回遊のストレスを減らします。隠れるヘッダーや下部CTAのような「必要な時だけ出る」UIも、画面の狭いスマホで本文への集中とアクションの両立に効きます。

passiveリスナーとrequestAnimationFrameでの負荷対策

scrollイベントは1回のスクロールで何十回も発火するため、素朴に書くとページがカクつきます。このページのサンプルは全て、addEventListener('scroll', fn, { passive: true })ブラウザのスクロール処理をブロックしないことを宣言しつつ、requestAnimationFrame更新を描画1回につき1回に間引きしています。さらに、変更するプロパティを transformwidth などレイアウトへの影響が小さいものに限定するのがコツです。

IntersectionObserverで代替できるケース

「要素が見えたかどうか」だけが知りたい場合(目次ハイライト・セクション判定・出現アニメなど)は、IntersectionObserver を使うとscrollイベント自体が不要になり、さらに軽量です。一方、進捗バーやスクロール率のような連続値スクロール方向の判定(隠れるヘッダー)はスクロール位置そのものが必要なので、scrollイベント+間引きが適任です。要素の出現だけが目的ならスクロール出現カテゴリのIntersectionObserverサンプルも参考にしてください。

「全部コピー」と「分けて貼る」の使い分け

「全部コピー」を使うと、HTML・CSS・JavaScriptがひとつの自己完結ブロックとしてコピーされるので、1回の貼り付けだけですぐ動きを確認できます。動作のお試しや、1ページ完結のLPに組み込むときはこの方法が手軽です。

一方、本番サイトへ本格的に組み込む場合は、CSSはスタイルシート(.cssファイル)へ、JavaScriptは</body>直前や共通の.jsファイルへ、HTMLは使いたい場所へと分けて貼るのが一般的です。スタイルとスクリプトを1か所に集約できるため、複数ページ・複数パーツで使い回すときの管理がしやすく、ブラウザのキャッシュも効きやすくなります。なお、同じパーツをページ内で何度も使う場合でも、CSSとJSを貼るのは1回だけで大丈夫です(初期化スクリプトが該当要素すべてに自動で効きます)。

よくある質問

コピーしたコードはそのまま動きますか?
はい。「全部コピー」を押すとHTML・CSS・JSがひとつの自己完結ブロックになってコピーされ、ページに貼り付けるだけでページ全体(window)のスクロールに連動して動きます。ライブラリの読み込みは一切不要です。
デモは小さな枠の中で動いていますが、コードと同じものですか?
同一です。デモは data-target 属性でスクロール元をミニ枠(コンテナ)に切り替えているだけで、JS本体は1文字も変えていません。data-target を省略すればそのままページ全体基準で動作します。
スクロールイベントでページが重くなりませんか?
スクロールイベントを使うサンプルでは addEventListenerpassive: true を指定してスクロールを妨げず、requestAnimationFrame で更新処理を描画1回につき1回に間引いています。更新するのも widthtransform など軽いプロパティだけなので、スマホでもなめらかに動きます。
IntersectionObserver は使わないのですか?
目次ハイライトのような「見えたかどうか」の判定は IntersectionObserver でも実装できます。一方、進捗バーやスクロール率のような連続的な値はスクロール位置そのものが必要なため、scroll イベントを使っています。用途に応じて使い分けてください。
このページのパーツはAIコーディングツールでも使えますか?
はい。Claude CodeやCursorのようにWebページを読めるAIには、このページのURLとパーツ名(例:「トップへ戻るボタン」)を伝えるだけで実装を指示できます。AIがWebを読めない場合は、「全部コピー」で取得した自己完結ブロックをチャットに貼り付けて調整を頼むのが確実です。改変後のコードは、公開前に動作確認することをおすすめします。

関連カテゴリ

スクロール連動UIと組み合わせて使いやすいパーツはこちらです。