無限スクロールInfinite scroll
一覧のいちばん下までスクロールすると、次の件数を自動で読み込んで継ぎ足していく仕組み。押して継ぎ足す「もっと見る」ボタン方式もある。
表示例
続きを読み込み中
別名・英語表記
- 無限ロード
- エンドレススクロール
- 自動読み込み
- インフィニットスクロール
- スクロールで追加読み込み
- Infinite scroll
実装例
<div class="x-infinite-scroll">
<ul class="x-infinite-scroll__list" aria-label="新着記事">
<li><a class="x-infinite-scroll__item" href="/posts/101/">朝5分でできる作り置き</a></li>
<li><a class="x-infinite-scroll__item" href="/posts/102/">冬の電気代を下げる設定</a></li>
<li><a class="x-infinite-scroll__item" href="/posts/103/">はじめての家計簿アプリ</a></li>
</ul>
<!-- 一覧の末尾に置く目印。これが画面に入ったら次の10件を読み込む -->
<div class="x-infinite-scroll__sentinel" role="status">
<span class="x-infinite-scroll__spin" aria-hidden="true"></span>
<span>続きを読み込み中</span>
</div>
</div>
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-infinite-scroll,
.x-infinite-scroll *:not([class*="fa-"]),
.x-infinite-scroll *:not([class*="fa-"])::before,
.x-infinite-scroll *:not([class*="fa-"])::after {
box-sizing: border-box;
/* 貼った先の既定の書体に引きずられないようにする。書体が変わると
文字の幅と高さが変わり、枠のサイズまでずれる */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif;
}
.x-infinite-scroll {
width: 280px;
border: 1px solid #e3e6ea;
border-radius: 8px;
background: #fff;
overflow: hidden;
}
.x-infinite-scroll__list {
margin: 0;
padding: 0;
list-style: none;
}
.x-infinite-scroll__list > li + li {
border-top: 1px solid #e3e6ea;
}
.x-infinite-scroll__item {
display: block;
padding: 12px 14px;
color: #1b2028;
font-size: 13px;
text-decoration: none;
}
.x-infinite-scroll__item:hover {
color: #1d4ed8;
text-decoration: underline;
}
.x-infinite-scroll__item:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: -2px;
}
.x-infinite-scroll__sentinel {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 14px;
border-top: 1px solid #e3e6ea;
background: #f6f7f9;
color: #606a7b;
font-size: 12px;
}
.x-infinite-scroll__spin {
width: 16px;
height: 16px;
border: 2px solid #cfd4dc;
border-top-color: #1d4ed8;
border-radius: 50%;
animation: x-infinite-scroll-turn 0.8s linear infinite;
}
@keyframes x-infinite-scroll-turn {
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: reduce) {
.x-infinite-scroll__spin {
animation: none;
}
}
AIへの指示文
「無限スクロール(Infinite scroll)」を実装してください。
## これは何か
一覧のいちばん下までスクロールすると、次の件数を自動で読み込んで継ぎ足していく仕組み。押して継ぎ足す「もっと見る」ボタン方式もある。
## 使いどころ
- SNS のタイムラインや画像の一覧のように、順に眺めること自体が目的で、特定の1件を探し直すことが少ないとき
- スマートフォンで、ページ送りのボタンを押させる手間を減らしたいとき
## 実装の要件
- 一覧の末尾に目印の要素を置き、IntersectionObserver で画面に入ったら次を読み込む。scroll イベントで位置を計算しない
- rootMargin で画面の少し手前(例: 200px)から読み込み始めると、待ち時間が見えにくい
- 読み込み中は二重に要求を出さないよう止める。最後まで読んだら監視をやめ、「すべて表示しました」と出す
- 読み込みに失敗したら自動の再試行を繰り返さず、「再読み込み」ボタンを出す
- フッターに辿り着けなくなる。読み込むたびにフッターが下へ逃げるので、フッターに会社情報や問い合わせを置くサイトでは、数回読み込んだら「もっと見る」ボタンに切り替える
- 詳細を開いてから戻ると、読み込んだ分が消えて先頭に戻されることがある。読み込んだページ番号を history.replaceState で URL に残し、戻ったときに復元する
- 検索エンジンのクローラーはスクロールしないので、継ぎ足した分は見つけてもらえない。?page=2 のような個別の URL でも同じ一覧を出せるようにしておく
- 件数が増えるほど DOM が膨らんで重くなる。数百件を超えるなら、画面外の要素を間引く(仮想スクロール)
- 目的の1件を探す一覧(検索結果・商品の比較)には向かない。何ページ目にあったかを覚えられるページネーションを使う
## アクセシビリティの要件
- キーボードでは一覧の後ろの要素へ Tab で抜けられなくなる。一覧の前に「一覧をとばす」リンクを置くか、「もっと見る」ボタン方式にする
- 読み込み中と読み込みの完了を role="status" の領域で知らせる(「10件を追加しました」など)。フォーカスは動かさない
- 記事が並ぶ一覧なら、WAI-ARIA APG の Feed パターン(role="feed" と article、aria-busy、aria-setsize / aria-posinset)を使うと、読み上げ環境で記事単位に移動できる。Page Down で次の記事へ移るキー操作も実装する
## 参考にする実装
この形を出発点にして、プロジェクトの命名規則・色・余白に合わせて調整してください。
```html
<div class="x-infinite-scroll">
<ul class="x-infinite-scroll__list" aria-label="新着記事">
<li><a class="x-infinite-scroll__item" href="/posts/101/">朝5分でできる作り置き</a></li>
<li><a class="x-infinite-scroll__item" href="/posts/102/">冬の電気代を下げる設定</a></li>
<li><a class="x-infinite-scroll__item" href="/posts/103/">はじめての家計簿アプリ</a></li>
</ul>
<!-- 一覧の末尾に置く目印。これが画面に入ったら次の10件を読み込む -->
<div class="x-infinite-scroll__sentinel" role="status">
<span class="x-infinite-scroll__spin" aria-hidden="true"></span>
<span>続きを読み込み中</span>
</div>
</div>
```
```css
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-infinite-scroll,
.x-infinite-scroll *:not([class*="fa-"]),
.x-infinite-scroll *:not([class*="fa-"])::before,
.x-infinite-scroll *:not([class*="fa-"])::after {
box-sizing: border-box;
/* 貼った先の既定の書体に引きずられないようにする。書体が変わると
文字の幅と高さが変わり、枠のサイズまでずれる */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif;
}
.x-infinite-scroll {
width: 280px;
border: 1px solid #e3e6ea;
border-radius: 8px;
background: #fff;
overflow: hidden;
}
.x-infinite-scroll__list {
margin: 0;
padding: 0;
list-style: none;
}
.x-infinite-scroll__list > li + li {
border-top: 1px solid #e3e6ea;
}
.x-infinite-scroll__item {
display: block;
padding: 12px 14px;
color: #1b2028;
font-size: 13px;
text-decoration: none;
}
.x-infinite-scroll__item:hover {
color: #1d4ed8;
text-decoration: underline;
}
.x-infinite-scroll__item:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: -2px;
}
.x-infinite-scroll__sentinel {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 14px;
border-top: 1px solid #e3e6ea;
background: #f6f7f9;
color: #606a7b;
font-size: 12px;
}
.x-infinite-scroll__spin {
width: 16px;
height: 16px;
border: 2px solid #cfd4dc;
border-top-color: #1d4ed8;
border-radius: 50%;
animation: x-infinite-scroll-turn 0.8s linear infinite;
}
@keyframes x-infinite-scroll-turn {
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: reduce) {
.x-infinite-scroll__spin {
animation: none;
}
}
```
## 出力の約束
- HTML と CSS を分けて出す。JavaScript が要るときは素の JavaScript で書き、UIフレームワークは使わない。
- 参考実装がアイコンに Font Awesome のクラスを使っている場合は、プロジェクトで使っているアイコンかインライン SVG に置き換えてよい。
- 上のアクセシビリティ要件を満たす。満たせない箇所があるときは、理由を添えて先に知らせる。
- 色・角丸・余白は既存のデザイントークンがあればそれを使う。無ければ CSS 変数として切り出す。
使いどころ
- SNS のタイムラインや画像の一覧のように、順に眺めること自体が目的で、特定の1件を探し直すことが少ないとき
- スマートフォンで、ページ送りのボタンを押させる手間を減らしたいとき
注意点
- フッターに辿り着けなくなる。読み込むたびにフッターが下へ逃げるので、フッターに会社情報や問い合わせを置くサイトでは、数回読み込んだら「もっと見る」ボタンに切り替える
- 詳細を開いてから戻ると、読み込んだ分が消えて先頭に戻されることがある。読み込んだページ番号を history.replaceState で URL に残し、戻ったときに復元する
- 検索エンジンのクローラーはスクロールしないので、継ぎ足した分は見つけてもらえない。?page=2 のような個別の URL でも同じ一覧を出せるようにしておく
- 件数が増えるほど DOM が膨らんで重くなる。数百件を超えるなら、画面外の要素を間引く(仮想スクロール)
- 目的の1件を探す一覧(検索結果・商品の比較)には向かない。何ページ目にあったかを覚えられるページネーションを使う
アクセシビリティの要点
- キーボードでは一覧の後ろの要素へ Tab で抜けられなくなる。一覧の前に「一覧をとばす」リンクを置くか、「もっと見る」ボタン方式にする
- 読み込み中と読み込みの完了を role="status" の領域で知らせる(「10件を追加しました」など)。フォーカスは動かさない
- 記事が並ぶ一覧なら、WAI-ARIA APG の Feed パターン(role="feed" と article、aria-busy、aria-setsize / aria-posinset)を使うと、読み上げ環境で記事単位に移動できる。Page Down で次の記事へ移るキー操作も実装する