絞り込み・並び替えできるカードUIを手軽に作る。Shuffle.jsの基本と実践パターン

はじめに

写真ギャラリーや制作実績一覧、商品カードのようなUIでは、「カテゴリで絞り込む」「新しい順に並び替える」「カードをきれいに再配置する」といった動きがよく必要になります。
この処理をすべて自前で作ると、DOM操作、アニメーション、レスポンシブ時のレイアウト調整が意外と面倒です。

そんなときに使いやすいのが Shuffle.js です。
Shuffle.jsは、グリッド状の要素をカテゴリでフィルタリングしたり、条件に応じてソートしたりできるJavaScriptライブラリです。

この記事では、npmでの導入から基本的な使い方、実案件で使いやすい応用パターンまで紹介します。

Shuffle.jsとは

Shuffle.jsは、カード型の要素を対象にして、次のような操作を行えるライブラリです。

  • カテゴリによる絞り込み
  • 複数カテゴリの絞り込み
  • タイトル順・日付順などの並び替え
  • ランダム表示
  • 要素の追加・削除
  • レスポンシブなグリッド再配置

公式サイトでも、Shuffle.jsは「responsive grid of items」をカテゴリ分け、ソート、フィルタリングするためのライブラリとして紹介されています。

向いているUIは、たとえば次のようなものです。

  • 写真ギャラリー
  • 制作実績一覧
  • スタッフ紹介
  • 店舗一覧
  • ブログ記事一覧
  • 商品・プラン一覧
  • FAQや資料カードの絞り込み

CSS GridやFlexboxだけでも見た目は作れますが、「状態に応じて表示・非表示を切り替えながら、自然に詰めて並べ直す」ところまで考えると、Shuffle.jsを使うメリットが出てきます。

基本の使い方

インストール

npmで使う場合は、公式ドキュメントどおり shufflejs をインストールします。

PowerShell
npm install shufflejs

ViteやWebpackなどのバンドラーを使っているプロジェクトでは、ES Modulesとして読み込めます。

JavaScript
import Shuffle from 'shufflejs';

HTMLを用意する

Shuffle.jsでは、グリッドの親要素と、その中にあるアイテム要素を指定します。
カテゴリ情報は data-groups にJSON配列として持たせるのが基本です。

HTML
<div class="work-filter">
  <button type="button" data-filter="all">すべて</button>
  <button type="button" data-filter="website">Webサイト</button>
  <button type="button" data-filter="branding">ブランディング</button>
  <button type="button" data-filter="photo">写真</button>
</div>

<div class="work-grid js-work-grid">
  <article class="work-card js-work-item" data-groups='["website","branding"]' data-title="Ichiryu Website" data-date="2026-05-10">
    <img src="/images/work-01.jpg" alt="Ichiryu Website">
    <h2>Ichiryu Website</h2>
  </article>

  <article class="work-card js-work-item" data-groups='["photo"]' data-title="Season Photo" data-date="2026-04-18">
    <img src="/images/work-02.jpg" alt="Season Photo">
    <h2>Season Photo</h2>
  </article>

  <article class="work-card js-work-item" data-groups='["website"]' data-title="Recruit Page" data-date="2026-03-02">
    <img src="/images/work-03.jpg" alt="Recruit Page">
    <h2>Recruit Page</h2>
  </article>
</div>

data-groups は、公式ドキュメントでも配列形式が基本として紹介されています。
1つのカードに複数カテゴリを持たせられるので、「Webサイト」かつ「ブランディング」のような分類にも対応できます。

CSSでカードを並べる

Shuffle.jsはアイテムの位置を制御しますが、カード自体の見た目や幅はCSSで作ります。

CSS
.work-grid {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
}

.work-card {
  width: calc((100% - 48px) / 3);
  margin: 0;
}

.work-card img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

@media (max-width: 767px) {
  .work-grid {
    gap: 16px;
  }

  .work-card {
    width: 100%;
  }
}

実案件では、カード幅をCSSで明示しておくとレイアウトが安定しやすくなります。

JavaScriptで初期化する

親要素を取得し、new Shuffle() で初期化します。

JavaScript
import Shuffle from 'shufflejs';

const grid = document.querySelector('.js-work-grid');

if (grid) {
  const shuffle = new Shuffle(grid, {
    itemSelector: '.js-work-item',
    speed: 300
  });

  document.querySelectorAll('[data-filter]').forEach(button => {
    button.addEventListener('click', () => {
      const filter = button.dataset.filter;

      if (filter === 'all') {
        shuffle.filter(Shuffle.ALL_ITEMS);
        return;
      }

      shuffle.filter(filter);
    });
  });
}

itemSelector には、Shuffle.jsの対象にしたいカード要素のセレクタを指定します。
ボタンをクリックしたら shuffle.filter() を呼び出すだけで、表示対象が切り替わります。

便利な使いどころ

制作実績一覧のカテゴリ切り替え

もっとも使いやすいのは、制作実績やポートフォリオの一覧です。
「Webサイト」「LP」「ロゴ」「写真」などのカテゴリでカードを切り替えられます。

WordPressのカスタム投稿と組み合わせる場合は、PHP側でカテゴリを data-groups に出力して、フロント側ではShuffle.jsで絞り込む構成にすると扱いやすいです。

HTML
<article
  class="work-card js-work-item"
  data-groups='<?php echo esc_attr( wp_json_encode( $category_slugs ) ); ?>'
>
  ...
</article>

一覧全体をページ遷移させずに絞り込めるため、ユーザーが複数のカテゴリを比較しやすくなります。

写真ギャラリーの見せ方を切り替える

写真ギャラリーでは、「イベント」「日常」「施設」「食事」などで分類できます。
カードの高さが画像ごとに変わる場合でも、Shuffle.jsで再配置すると自然に詰まったグリッドにできます。

画像読み込み後に高さが変わる場合は、読み込み完了後に layout() を呼ぶとズレを補正できます。

JavaScript
grid.querySelectorAll('img').forEach(image => {
  if (image.complete) {
    return;
  }

  image.addEventListener('load', () => {
    shuffle.layout();
  });
});

公式APIでも、画像読み込みなどで計算がずれる場合には layout() が使えると説明されています。

商品一覧や資料一覧のソート

Shuffle.jsはフィルタリングだけでなく、ソートにも対応しています。
data-titledata-date のような属性を持たせておくと、タイトル順・日付順の並び替えを実装できます。

HTML
<select class="js-sort">
  <option value="default">標準</option>
  <option value="title">タイトル順</option>
  <option value="date">新しい順</option>
</select>
JavaScript
const sortSelect = document.querySelector('.js-sort');

sortSelect?.addEventListener('change', event => {
  const value = event.target.value;

  if (value === 'title') {
    shuffle.sort({
      by: element => element.dataset.title.toLowerCase()
    });
    return;
  }

  if (value === 'date') {
    shuffle.sort({
      reverse: true,
      by: element => element.dataset.date
    });
    return;
  }

  shuffle.sort({});
});

sort({}) を呼ぶとDOM順に戻せます。
管理画面やCMSから出力される順番を標準順として使いたい場合にも便利です。

応用コード

ここでは、カテゴリフィルターとソートを組み合わせた実践的なコードを紹介します。
制作実績一覧やアルバム一覧でそのまま使いやすい形です。

HTML
<div class="archive-toolbar">
  <div class="archive-filter" aria-label="カテゴリで絞り込み">
    <button type="button" class="is-active" data-filter="all">すべて</button>
    <button type="button" data-filter="website">Webサイト</button>
    <button type="button" data-filter="design">デザイン</button>
    <button type="button" data-filter="photo">写真</button>
  </div>

  <select class="js-archive-sort" aria-label="並び替え">
    <option value="default">標準順</option>
    <option value="newest">新しい順</option>
    <option value="title">タイトル順</option>
  </select>
</div>

<div class="archive-grid js-archive-grid">
  <article class="archive-card js-archive-item" data-groups='["website","design"]' data-title="Corporate Site" data-date="2026-06-01">
    <img src="/images/archive-01.jpg" alt="Corporate Site">
    <h2>Corporate Site</h2>
  </article>

  <article class="archive-card js-archive-item" data-groups='["photo"]' data-title="Photo Album" data-date="2026-05-20">
    <img src="/images/archive-02.jpg" alt="Photo Album">
    <h2>Photo Album</h2>
  </article>
</div>
JavaScript
import Shuffle from 'shufflejs';

const grid = document.querySelector('.js-archive-grid');
const filterButtons = document.querySelectorAll('[data-filter]');
const sortSelect = document.querySelector('.js-archive-sort');

if (grid) {
  const shuffle = new Shuffle(grid, {
    itemSelector: '.js-archive-item',
    speed: 300,
    staggerAmount: 20,
    staggerAmountMax: 120
  });

  let currentFilter = Shuffle.ALL_ITEMS;
  let currentSort = {};

  const applyView = () => {
    shuffle.filter(currentFilter, currentSort);
  };

  filterButtons.forEach(button => {
    button.addEventListener('click', () => {
      filterButtons.forEach(item => item.classList.remove('is-active'));
      button.classList.add('is-active');

      currentFilter = button.dataset.filter === 'all'
        ? Shuffle.ALL_ITEMS
        : button.dataset.filter;

      applyView();
    });
  });

  sortSelect?.addEventListener('change', event => {
    const value = event.target.value;

    if (value === 'newest') {
      currentSort = {
        reverse: true,
        by: element => element.dataset.date
      };
    } else if (value === 'title') {
      currentSort = {
        by: element => element.dataset.title.toLowerCase()
      };
    } else {
      currentSort = {};
    }

    applyView();
  });
}

ポイントは、現在のフィルター状態とソート状態を変数で持っておくことです。
Shuffle.jsの filter() は第2引数にソート設定を渡せるため、「カテゴリで絞り込んだうえで新しい順に並べる」といったUIを作れます。

Ajaxや追加読み込みと組み合わせる

一覧に「もっと見る」ボタンがある場合は、新しいHTMLをDOMに追加したあと、追加した要素を shuffle.add() に渡します。

JavaScript
async function appendItems() {
  const response = await fetch('/api/works?page=2');
  const html = await response.text();

  const template = document.createElement('template');
  template.innerHTML = html.trim();

  const newItems = [...template.content.querySelectorAll('.js-archive-item')];

  newItems.forEach(item => {
    grid.appendChild(item);
  });

  shuffle.add(newItems);
}

公式ドキュメントでも、要素の追加後に add() を使ってShuffle.jsへ知らせる形が紹介されています。
無限スクロールや「もっと見る」UIとの相性もよいです。

注意点

data-groups は正しいJSONにする

data-groups='["website","photo"]' のように、JSON配列として正しく出力する必要があります。
CMSから出力する場合は、文字列連結ではなくJSONエンコードを使うほうが安全です。

WordPressなら wp_json_encode() を使うと扱いやすくなります。

PHP
data-groups='<?php echo esc_attr( wp_json_encode( $groups ) ); ?>'

画像の読み込みタイミングに注意する

カード内の画像があとから読み込まれると、初期計算時と実際の高さが変わることがあります。
画像サイズが固定できるなら、CSSの aspect-ratio を指定しておくのが簡単です。

CSS
.archive-card img {
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

画像サイズが可変の場合は、画像読み込み後に shuffle.layout() を呼びます。

フィルター対象が多すぎる場合は設計を見直す

Shuffle.jsは手軽に使えますが、数百件以上のカードを一度にDOMへ出すような画面では、描画や画像読み込みが重くなります。
その場合は、サーバー側の絞り込み、ページネーション、仮想スクロールなども検討したほうがよいです。

「表示中の一覧を気持ちよく切り替える」用途には向いていますが、「大量データを検索する」用途では別の設計が必要です。

破棄処理を忘れない

SPAやモーダル内でShuffle.jsを使う場合は、画面を離れるタイミングで destroy() を呼ぶと不要なイベントや参照を片付けられます。

JavaScript
shuffle.destroy();

通常の静的ページでは意識しなくても問題になりにくいですが、ReactやVueなどでコンポーネント化する場合は重要です。

まとめ

Shuffle.jsは、カード型UIに「絞り込み」「並び替え」「自然な再配置」を加えたいときに便利なライブラリです。

CSSだけでは難しい表示切り替えやソートを、比較的少ないコードで実装できます。
特に、写真ギャラリー、制作実績一覧、商品一覧、記事一覧のように、ユーザーがカテゴリを行き来しながら探す画面と相性がよいです。

導入時は、まず次の流れで考えるとスムーズです。

  1. HTMLに data-groups を持たせる
  2. CSSでカード幅と画像比率を安定させる
  3. new Shuffle() で初期化する
  4. ボタンやセレクトから filter() / sort() を呼ぶ
  5. 画像読み込みや追加要素がある場合は layout() / add() を使う

小さなギャラリーから始めて、必要に応じてソートや追加読み込みを足していくと、実案件でも扱いやすい構成になります。

ポイント

  • Shuffle.jsは、カード型グリッドの絞り込み・並び替え・再配置に使いやすい
  • npmでは npm install shufflejs で導入できる
  • data-groups にカテゴリ情報を持たせ、filter() で表示を切り替える
  • sort() を使うと、日付順・タイトル順・ランダム表示にも対応できる
  • 画像の高さが変わる場合は aspect-ratiolayout() でズレを防ぐ
  • 大量データの検索用途では、サーバー側の絞り込みやページネーションも検討する

参考リンク

read next