How to create a load more button ロードモアボタンの作り方
Reveals the next batch of items with a fade-slide-up animation. Shows remaining count and a loading spinner. クリックで次バッチをフェードインで追加表示するロードモアボタン。残件数表示とスピナー付き。
ライブデモ Live Demo
すべて表示しました All items loaded
概要・用途・特徴Overview, Usage & Features
何ができるかWhat it does
Reveals the next batch of items with a fade-slide-up animation. Shows remaining count and a loading spinner.
クリックで次バッチをフェードインで追加表示するロードモアボタン。残件数表示とスピナー付き。
どこで使うかWhere to use
user engagement, data entry, content management, settings panel
ブログ記事一覧、商品カタログ、ソーシャルフィード、検索結果
特徴Key features
Load More button pattern for progressive content loading. Appends items to existing list without full page reload. Tracks current page/offset state. Shows loading spinner during fetch. Hides button when all content is exhausted.
段階的コンテンツ読み込みのためのLoad Moreボタンパターン。ページ全体をリロードせず既存リストにアイテムを追加。現在のページ/オフセット状態を追跡。フェッチ中にローディングスピナーを表示。全コンテンツを読み込み済みの場合にボタンを非表示。
調整可能パラメータ Adjustable Parameters
| Parameter | Default | Description |
|---|
実装コード Implementation Code
// react/I-011.jsx
import { useState, useRef } from "react";
import "./I-011.css";
// Sample data — replace with your own array of items
const ALL_ITEMS = Array.from({ length: 12 }, (_, i) => ({
id: i + 1,
title: `Item ${i + 1}`,
description: "Short description goes here.",
}));
export default function LoadMoreList({
items = ALL_ITEMS,
initialCount = 4,
batchSize = 4,
loadDelay = 500,
}) {
const [visible, setVisible] = useState(initialCount);
const [loading, setLoading] = useState(false);
const newRefs = useRef([]);
const shownItems = items.slice(0, visible);
const remaining = items.length - visible;
const allLoaded = remaining <= 0;
function handleLoadMore() {
if (loading) return;
setLoading(true);
setTimeout(() => {
setLoading(false);
setVisible((v) => Math.min(v + batchSize, items.length));
}, loadDelay);
}
return (
<div>
<div className="items-grid">
{shownItems.map((item, i) => (
<div
key={item.id}
className={`item-card${i >= visible - batchSize ? " is-new" : ""}`}
>
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
))}
</div>
<div className="load-more-wrap">
{!allLoaded ? (
<button
className={`load-more-btn${loading ? " loading" : ""}`}
onClick={handleLoadMore}
>
{loading && <span className="spinner" />}
Load More ({remaining})
</button>
) : (
<p className="loaded-msg">All items loaded</p>
)}
</div>
</div>
);
}
/* react/I-011.css */
:root {
--load-btn-bg: #111827;
--load-btn-color: #ffffff;
--load-btn-radius: 8px;
--item-fade-duration: 0.4s;
}
.items-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px;
}
.item-card {
background: #1e1e2e;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
}
.item-card.is-new {
animation: fadeSlideUp var(--item-fade-duration) ease both;
}
@keyframes fadeSlideUp {
from { opacity: 0; transform: translateY(14px); }
to { opacity: 1; transform: translateY(0); }
}
.load-more-wrap {
display: flex;
justify-content: center;
padding: 32px 0 8px;
}
.load-more-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 32px;
background: var(--load-btn-bg);
color: var(--load-btn-color);
border: none;
border-radius: var(--load-btn-radius);
font-size: 15px;
font-weight: 600;
font-family: inherit;
cursor: pointer;
min-width: 180px;
transition: opacity 0.2s, transform 0.15s;
}
.load-more-btn:hover { opacity: 0.82; }
.load-more-btn:active { transform: scale(0.97); }
.load-more-btn.loading { opacity: 0.65; cursor: wait; }
.spinner {
display: inline-block;
width: 14px; height: 14px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top-color: #fff;
border-radius: 50%;
animation: spin 0.6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.loaded-msg { text-align: center; color: #6b7280; font-size: 13px; }
import { useState, useRef } from "react";
import "./I-011.css";
// Sample data — replace with your own array of items
const ALL_ITEMS = Array.from({ length: 12 }, (_, i) => ({
id: i + 1,
title: `Item ${i + 1}`,
description: "Short description goes here.",
}));
export default function LoadMoreList({
items = ALL_ITEMS,
initialCount = 4,
batchSize = 4,
loadDelay = 500,
}) {
const [visible, setVisible] = useState(initialCount);
const [loading, setLoading] = useState(false);
const newRefs = useRef([]);
const shownItems = items.slice(0, visible);
const remaining = items.length - visible;
const allLoaded = remaining <= 0;
function handleLoadMore() {
if (loading) return;
setLoading(true);
setTimeout(() => {
setLoading(false);
setVisible((v) => Math.min(v + batchSize, items.length));
}, loadDelay);
}
return (
<div>
<div className="items-grid">
{shownItems.map((item, i) => (
<div
key={item.id}
className={`item-card${i >= visible - batchSize ? " is-new" : ""}`}
>
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
))}
</div>
<div className="load-more-wrap">
{!allLoaded ? (
<button
className={`load-more-btn${loading ? " loading" : ""}`}
onClick={handleLoadMore}
>
{loading && <span className="spinner" />}
Load More ({remaining})
</button>
) : (
<p className="loaded-msg">All items loaded</p>
)}
</div>
</div>
);
}
:root {
--load-btn-bg: #111827;
--load-btn-color: #ffffff;
--load-btn-radius: 8px;
--item-fade-duration: 0.4s;
}
.items-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px;
}
.item-card {
background: #1e1e2e;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
}
.item-card h3 {
margin: 0 0 8px;
font-size: 14px;
font-weight: 600;
color: #f0f0f5;
}
.item-card p {
margin: 0;
font-size: 12px;
color: rgba(255, 255, 255, 0.5);
line-height: 1.5;
}
.item-card.is-new {
animation: fadeSlideUp var(--item-fade-duration) ease both;
}
@keyframes fadeSlideUp {
from { opacity: 0; transform: translateY(14px); }
to { opacity: 1; transform: translateY(0); }
}
.load-more-wrap {
display: flex;
justify-content: center;
padding: 32px 0 8px;
}
.load-more-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 32px;
background: var(--load-btn-bg);
color: var(--load-btn-color);
border: none;
border-radius: var(--load-btn-radius);
font-size: 15px;
font-weight: 600;
font-family: inherit;
cursor: pointer;
min-width: 180px;
transition: opacity 0.2s, transform 0.15s;
}
.load-more-btn:hover { opacity: 0.82; }
.load-more-btn:active { transform: scale(0.97); }
.load-more-btn.loading { opacity: 0.65; cursor: wait; }
.spinner {
display: inline-block;
width: 14px;
height: 14px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top-color: #fff;
border-radius: 50%;
animation: spin 0.6s linear infinite;
flex-shrink: 0;
}
@keyframes spin { to { transform: rotate(360deg); } }
.loaded-msg {
text-align: center;
color: #6b7280;
font-size: 13px;
}
仕組みとカスタマイズHow It Works & Customization
仕組みHow it works
The button click handler increments a page counter and fetches the next batch of items from an API (or static dataset). Fetched items are appended to the container using insertAdjacentHTML or DOM API. A loading state disables the button and shows a spinner. When the API response indicates no more items, the button is removed or disabled permanently.
ボタンクリックハンドラがページカウンターをインクリメントしてAPIまたは静的データセットから次のバッチを取得。取得したアイテムをinsertAdjacentHTMLまたはDOM APIでコンテナに追加。ローディング状態がボタンを無効化してスピナーを表示。APIレスポンスにアイテムがない旨が示されたとき、ボタンを削除または永続的に無効化。
カスタマイズ方法Customization
Replace the Load More button with an IntersectionObserver on a sentinel element at the bottom for infinite scroll. Add a count badge ("Showing X of Y") above the button. Animate newly appended items with a fade-in class for smoother appearance.
Load Moreボタンをボトムのセンチネル要素上のIntersectionObserverに置き換えて無限スクロールに。ボタン上に件数バッジ("X / Y件表示中")を追加。スムーズな表示のために新しく追加されたアイテムにフェードインクラスをアニメーション。
注意点Caveats
After loading more items, move focus to the first newly added item so keyboard users are aware of the new content. Use aria-live="polite" to announce the number of new items loaded. Preserve the scroll position after items are appended.
アイテム読み込み後、キーボードユーザーが新しいコンテンツを認識できるよう最初の新しく追加されたアイテムにフォーカスを移動してください。aria-live="polite"を使用して読み込まれた新しいアイテム数をアナウンス。アイテム追加後のスクロール位置を維持してください。
よくある質問 Frequently Asked Questions
How to customize the load more button? Load More Buttonをカスタマイズするには?
Modify the CSS custom properties and class styles defined in the code section. Key adjustable values include colors, sizes, durations, and spacing. See the Adjustable Parameters section for specific variables.
コードセクションで定義されているCSSカスタムプロパティとクラススタイルを変更してください。色、サイズ、時間、間隔が主な調整可能値です。具体的な変数は調整可能パラメータセクションを参照してください。
How to use the load more button in React? ReactでLoad More Buttonを使うには?
Import the provided React component and its CSS file. The component accepts props for customization. Check the React code section for the full implementation and available props.
提供されているReactコンポーネントとCSSファイルをインポートしてください。コンポーネントのpropsでカスタマイズできます。完全な実装と利用可能なpropsはReactコードセクションを参照してください。
What are the performance implications of load more button? Load More Buttonのパフォーマンスへの影響は?
This implementation uses CSS transforms and opacity for animations, which are GPU-accelerated. It's lightweight and doesn't cause layout thrashing. Consider using prefers-reduced-motion for accessibility.
この実装はCSSのtransformとopacityを使用しており、GPUアクセラレーションされます。軽量でレイアウトスラッシングを引き起こしません。アクセシビリティのためにprefers-reduced-motionの使用を検討してください。
AIへの指示テンプレート AI Prompt Template
以下をAIに貼り付けると実装を依頼できます。 Paste the following into your AI assistant to request implementation.