ライブデモ Live Demo

「もっと見る」ボタンをクリックして次のバッチをフェードインで追加表示します。

Click "Load More" to reveal the next batch of items with a fade-in animation.

すべて表示しました All items loaded

AI向け説明 AI Description

`I-011` はリスト / グリッドに「もっと見る」ボタンを追加し、クリックで次バッチをフェードインで追加表示するUIパターンです。全アイテムを最初から DOM に配置し、`visibleCount` 変数で表示数を管理します。ボタンクリック時は `loading` クラスでスピナーを表示してフェッチを模倣し、一定時間後に次バッチのアイテムを表示して `is-new` クラスで `fadeSlideUp` アニメーションを適用します。残件数をボタンテキストに表示し、全件表示後はボタンを非表示にして「すべて表示しました」メッセージを出します。`--item-fade-duration` CSS変数でアニメーション速度、`--load-btn-bg` / `--load-btn-radius` でボタン外観を制御します。

`I-011` adds a "Load More" button to a list or grid, revealing the next batch with a fade-slide-up animation on each click. All items live in the DOM from the start; `visibleCount` tracks how many are displayed. On click, a `loading` class triggers a spinner to simulate a fetch, then newly revealed items receive the `is-new` class which applies a `fadeSlideUp` keyframe animation. The remaining count is shown in the button text, and once all items are visible the button is replaced by an "All items loaded" message. Button appearance is controlled via `--load-btn-bg`, `--load-btn-color`, `--load-btn-radius`, and animation speed via `--item-fade-duration`.

調整可能パラメータ Adjustable Parameters

実装 Implementation

HTML + CSS + JS

<!-- (1) Items container: place all items here, hidden by default -->
<div class="items-grid" id="items_grid">
  <div class="item-card">Item 1</div>
  <div class="item-card">Item 2</div>
  <div class="item-card">Item 3</div>
  <!-- ... more items ... -->
</div>

<!-- (2) Load More button -->
<div class="load-more-wrap" id="load_wrap">
  <button class="load-more-btn" id="load_btn" type="button">
    <span class="spinner"></span>
    Load More (<span id="rem_count">0</span>)
  </button>
</div>
<p class="loaded-msg" id="loaded_msg">All items loaded</p>

<style>
:root {
  --load-btn-bg: #111827;
  --load-btn-color: #ffffff;
  --load-btn-radius: 8px;
  --item-fade-duration: 0.4s;
}

/* Grid */
.items-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 16px;
}

.item-card {
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: 10px;
  padding: 20px;
}

/* Fade-in for newly revealed items */
.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 button */
.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;
  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 */
.spinner {
  display: none;
  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;
}
.load-more-btn.loading .spinner { display: block; }
@keyframes spin { to { transform: rotate(360deg); } }

/* All loaded message */
.loaded-msg { display: none; text-align: center; color: #6b7280; }
</style>

<script>
(function () {
  var INITIAL_COUNT = 4;  // items shown on load
  var BATCH_SIZE    = 4;  // items added per click
  var LOAD_DELAY    = 500; // spinner duration (ms)

  var grid    = document.getElementById('items_grid');
  var wrap    = document.getElementById('load_wrap');
  var btn     = document.getElementById('load_btn');
  var remSpan = document.getElementById('rem_count');
  var doneMsg = document.getElementById('loaded_msg');

  var items = Array.from(grid.querySelectorAll('.item-card'));
  var visible = 0;

  // Hide all, then show initial batch
  items.forEach(function (el) { el.style.display = 'none'; });
  showBatch(INITIAL_COUNT, false);

  function showBatch(count, animate) {
    var shown = 0;
    for (var i = visible; i < items.length && shown < count; i++, shown++) {
      items[i].style.display = '';
      if (animate) {
        items[i].classList.remove('is-new');
        // Force reflow to restart animation
        void items[i].offsetWidth;
        items[i].classList.add('is-new');
      }
    }
    visible += shown;
    updateUI();
  }

  function updateUI() {
    var remaining = items.length - visible;
    if (remaining > 0) {
      wrap.style.display = '';
      remSpan.textContent = remaining;
      doneMsg.style.display = 'none';
    } else {
      wrap.style.display = 'none';
      doneMsg.style.display = 'block';
    }
  }

  btn.addEventListener('click', function () {
    if (btn.classList.contains('loading')) return;
    btn.classList.add('loading');
    setTimeout(function () {
      btn.classList.remove('loading');
      showBatch(BATCH_SIZE, true);
    }, LOAD_DELAY);
  });
})();
</script>

React (JSX + CSS)

// 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; }

AIへの指示テンプレート AI Prompt Template

以下のテンプレートをコピーしてAIアシスタントに貼り付けると、このパターンの実装を依頼できます。 Copy the template below and paste it into your AI assistant to request an implementation of this pattern.

注意とバリエーション Notes & Variations