【jQuery】フィルター機能の実装完全ガイド|テキスト絞り込み・カテゴリボタン・複数条件・アニメーションまで

「リストを入力キーワードで絞り込みたい」「ボタンでカテゴリを切り替えたい」といったフィルター機能はjQueryで手軽に実装できます。本記事ではテキスト絞り込み・カテゴリボタン・複数条件AND絞り込み・テーブル対応・アニメーション・URL連動まで、実務で使えるパターンを網羅します。

この記事でわかること

  • テキスト入力でリストをリアルタイム絞り込む基本実装
  • 検索結果0件のときに「該当なし」メッセージを表示する方法
  • データ属性(data-*)を使ったカテゴリボタン型フィルター
  • テキスト検索+カテゴリの複数条件AND絞り込み
  • テーブル行のフィルタリング(ヘッダー行を除外)
  • fadeIn/fadeOutアニメーション付きフィルター
  • フィルター状態をURLパラメーターに保存・復元する方法
  • debounceによるパフォーマンス最適化
スポンサーリンク

1. テキスト入力でリスト絞り込み(基本実装)

テキストボックスに文字を入力するたびに、マッチしない項目を非表示にする最もシンプルな実装です。

<input type="text" id="filter-input" placeholder="キーワードを入力...">
<ul id="item-list">
  <li>JavaScript入門</li>
  <li>jQuery基礎</li>
  <li>CSSアニメーション</li>
  <li>HTML5フォーム</li>
  <li>jQueryプラグイン</li>
</ul>
$(function () {
  $('#filter-input').on('keyup', function () {
    var keyword = $(this).val().toLowerCase();  // 小文字に統一

    $('#item-list li').each(function () {
      var text    = $(this).text().toLowerCase();
      var matched = text.includes(keyword);   // キーワードを含むか
      $(this).toggle(matched);                // true=表示 / false=非表示
    });
  });
});
toLowerCase()で大文字小文字を無視
キーワードと比較対象テキストの両方を toLowerCase() で小文字に変換することで、「jquery」と入力しても「jQuery」がヒットします。全角・半角の違いも吸収したい場合は String.prototype.normalize("NFKC") を組み合わせてください。

2. 検索結果0件のときに「該当なし」を表示する

絞り込み結果が0件のとき、ユーザーに何も表示されないと不親切です。「該当なし」メッセージを出すことで離脱を防げます。

<input type="text" id="filter-input2" placeholder="キーワードを入力...">
<ul id="item-list2">
  <li>JavaScript入門</li>
  <li>jQuery基礎</li>
  <li>CSSアニメーション</li>
</ul>
<p id="no-results" style="display:none; color:#999;">該当する項目がありません。</p>
$(function () {
  $('#filter-input2').on('keyup', function () {
    var keyword = $(this).val().toLowerCase();
    var count   = 0;  // マッチ件数

    $('#item-list2 li').each(function () {
      var matched = $(this).text().toLowerCase().includes(keyword);
      $(this).toggle(matched);
      if (matched) count++;
    });

    // 0件のときだけ「該当なし」を表示
    $('#no-results').toggle(count === 0 && keyword !== '');
  });
});

3. カテゴリボタンで絞り込む(data属性活用)

ポートフォリオや製品一覧でよく見られる「ボタンを押してカテゴリ切り替え」のフィルターです。data-category 属性を使って絞り込み対象を指定します。

<div id="filter-buttons">
  <button class="filter-btn is-active" data-filter="all">すべて</button>
  <button class="filter-btn" data-filter="js">JavaScript</button>
  <button class="filter-btn" data-filter="css">CSS</button>
  <button class="filter-btn" data-filter="html">HTML</button>
</div>

<ul id="cat-list">
  <li data-category="js">jQueryの使い方</li>
  <li data-category="css">Flexboxレイアウト</li>
  <li data-category="js">非同期通信(Ajax)</li>
  <li data-category="html">フォームの作り方</li>
  <li data-category="css">CSSアニメーション</li>
</ul>
$(function () {
  $('.filter-btn').on('click', function () {
    var filter = $(this).data('filter');  // 'all' / 'js' / 'css' / 'html'

    // アクティブボタンの切り替え
    $('.filter-btn').removeClass('is-active');
    $(this).addClass('is-active');

    // 絞り込み
    if (filter === 'all') {
      $('#cat-list li').show();
    } else {
      $('#cat-list li').each(function () {
        var category = $(this).data('category');
        $(this).toggle(category === filter);
      });
    }
  });
});
is-activeクラスでボタンのスタイルを切り替える
クリックされたボタンに is-active クラスを付与し、CSSで選択中のスタイルを当てます。classの追加・削除・切り替えで詳しく解説しています。

4. テキスト+カテゴリの複数条件AND絞り込み

テキスト入力とカテゴリボタンを組み合わせ、両方の条件を満たす項目だけ表示するパターンです。ECサイトの絞り込み検索などで使えます。

HTML構造はセクション3と同様に data-category 属性を持つリストと、data-filter 属性を持つボタン群、加えてテキスト入力 id="search-word" を用意します。

$(function () {
  var currentFilter = 'all';  // 現在のカテゴリフィルター

  function applyFilter() {
    var keyword = $('#search-word').val().toLowerCase();

    $('#product-list li').each(function () {
      var text     = $(this).text().toLowerCase();
      var category = $(this).data('category');

      var textOk = text.includes(keyword);
      var catOk  = currentFilter === 'all' || category === currentFilter;

      $(this).toggle(textOk && catOk);  // AND条件
    });
  }

  // テキスト入力時
  $('#search-word').on('keyup', applyFilter);

  // カテゴリボタンクリック時
  $('.cat-btn').on('click', function () {
    currentFilter = $(this).data('filter');
    $('.cat-btn').removeClass('is-active');
    $(this).addClass('is-active');
    applyFilter();
  });
});
共通の絞り込み関数を切り出す
複数のイベント(keyup・click)から同じ処理を呼び出すため、applyFilter() という関数に切り出しています。フィルター条件が増えても applyFilter() 内だけ修正すればよいため保守性が高まります。

5. テーブル行のフィルタリング(ヘッダー行を除外)

リストではなくテーブルを絞り込む場合、thead の行を対象外にすることがポイントです。

<input type="text" id="table-filter" placeholder="名前・部署で検索...">
<table id="member-table">
  <thead><tr><th>名前</th><th>部署</th><th>役職</th></tr></thead>
  <tbody>
    <tr><td>山田 太郎</td><td>開発部</td><td>エンジニア</td></tr>
    <tr><td>鈴木 花子</td><td>営業部</td><td>マネージャー</td></tr>
    <tr><td>田中 一郎</td><td>開発部</td><td>リーダー</td></tr>
    <tr><td>佐藤 由美</td><td>総務部</td><td>スタッフ</td></tr>
  </tbody>
</table>
$(function () {
  $('#table-filter').on('keyup', function () {
    var keyword = $(this).val().toLowerCase();

    // tbody の tr のみ対象(thead の行は除外される)
    $('#member-table tbody tr').each(function () {
      var rowText = $(this).text().toLowerCase();
      $(this).toggle(rowText.includes(keyword));
    });
  });
});
tbody tr でヘッダーを自動除外
#member-table tbody tr と指定することで、thead 内の行はセレクターに引っかからず、ヘッダーが消えることはありません。より複雑なセレクターパターンは全ての要素を取得する方法完全ガイドを参照してください。

6. アニメーション付きフィルター(fadeIn/fadeOut)

単純な show/hide ではなく、フェードアニメーションで滑らかに切り替えることでUXが向上します。fadeIn() / fadeOut() を使います。

$(function () {
  $('.fade-btn').on('click', function () {
    var filter = $(this).data('filter');
    $('.fade-btn').removeClass('is-active');
    $(this).addClass('is-active');

    $('.card-item').each(function () {
      var category = $(this).data('category');
      var matched  = filter === 'all' || category === filter;

      if (matched) {
        $(this).stop(true).fadeIn(300);   // 表示時はフェードイン
      } else {
        $(this).stop(true).fadeOut(200);  // 非表示時はフェードアウト
      }
    });
  });
});
stop(true)でアニメーションの重複を防ぐ
ボタンを素早く連打すると、前のアニメーションが完了する前に次のアニメーションが始まり、表示状態が乱れることがあります。stop(true) で前のアニメーションを即座にキャンセルしてから次を開始します。詳しくはアニメーション完全ガイドを参照してください。

7. フィルター状態をURLパラメーターに保存・復元する

URLパラメーターにフィルター状態を反映することで、「このURLを共有すると同じ絞り込みで表示される」という体験を提供できます。

$(function () {
  var params = new URLSearchParams(location.search);
  var initFilter  = params.get('cat')  || 'all';
  var initKeyword = params.get('q')    || '';

  // URLパラメーターから初期状態を復元
  $('#url-search').val(initKeyword);
  $('[data-filter="' + initFilter + '"]').addClass('is-active');
  applyUrlFilter(initKeyword, initFilter);

  function applyUrlFilter(keyword, filter) {
    $('#url-list li').each(function () {
      var text    = $(this).text().toLowerCase();
      var cat     = $(this).data('category');
      var textOk  = text.includes(keyword.toLowerCase());
      var catOk   = filter === 'all' || cat === filter;
      $(this).toggle(textOk && catOk);
    });
  }

  function updateURL(keyword, filter) {
    var p = new URLSearchParams();
    if (keyword) p.set('q',   keyword);
    if (filter !== 'all') p.set('cat', filter);
    var qs = p.toString() ? '?' + p.toString() : location.pathname;
    history.replaceState(null, '', qs);  // ページ遷移なしでURLを更新
  }

  var currentCat = initFilter;

  $('#url-search').on('keyup', function () {
    var kw = $(this).val();
    applyUrlFilter(kw, currentCat);
    updateURL(kw, currentCat);
  });

  $('.url-btn').on('click', function () {
    currentCat = $(this).data('filter');
    $('.url-btn').removeClass('is-active');
    $(this).addClass('is-active');
    var kw = $('#url-search').val();
    applyUrlFilter(kw, currentCat);
    updateURL(kw, currentCat);
  });
});
history.replaceState()でURLを書き換える
history.replaceState() はページを遷移させずにアドレスバーのURLだけ変更します。pushState() と違い履歴に積まれないため、フィルター操作のたびにブラウザバックが増えることを防げます。

8. debounceでパフォーマンスを改善する

キーを押すたびにフィルター処理が走ると、項目数が多い場合に動作が重くなります。debounce(一定時間入力がなければ実行)を使うことで処理回数を削減できます。

// debounce関数(ライブラリ不要)
function debounce(fn, delay) {
  var timer;
  return function () {
    var ctx  = this;
    var args = arguments;
    clearTimeout(timer);
    timer = setTimeout(function() { fn.apply(ctx, args); }, delay);
  };
}

$(function () {
  var doFilter = debounce(function () {
    var keyword = $('#debounce-input').val().toLowerCase();
    $('#debounce-list li').each(function () {
      $(this).toggle($(this).text().toLowerCase().includes(keyword));
    });
  }, 200);  // 入力が止まって200ms後に実行

  $('#debounce-input').on('keyup', doFilter);
});
debounceの適用目安

  • 項目数が100件未満: debounceなしの keyup で十分
  • 項目数が100〜500件: 100〜200ms の debounce が効果的
  • 項目数が500件以上: debounce+仮想スクロールの検討を推奨

フィルターパターンの選び方まとめ

パターン 条件 おすすめの用途
テキスト絞り込み(基本) keyupイベント シンプルなリスト・辞書検索
カテゴリボタン data属性一致 ポートフォリオ・製品一覧
複数条件AND テキスト+カテゴリ ECサイト絞り込み・社員検索
テーブル行フィルター tbody tr対象 管理画面・データ一覧
アニメーション付き fadeIn/fadeOut カード型ギャラリー・ポートフォリオ
URL連動 URLSearchParams 共有可能な絞り込みページ

まとめ

jQueryのフィルター機能は toggle()data() の組み合わせで幅広いパターンを実現できます。要件に応じて本記事のパターンを組み合わせてください。

関連記事: classの追加・削除・切り替えを完全解説 / アニメーション完全ガイド / サジェスト機能の実装方法

よくある質問(FAQ)

Q大文字・小文字を区別せずに絞り込むには?
A入力値と比較対象テキストの両方を toLowerCase() で小文字に変換してから比較します(セクション1参照)。全角英字も対象に含めたい場合は normalize("NFKC") でさらに正規化すると効果的です。
QjQueryの filter() メソッドとの違いは?
A$("li").filter(fn) はセレクターで取得した集合から条件に合う要素を返すメソッドです。本記事の実装は each() でループして toggle() で表示切り替えしており、同じ結果を読みやすい形で書いています。小規模なら each()、集合操作が多い場合は filter() が向いています。
Q:contains() セレクターではダメですか?
A:contains("keyword") は大文字・小文字を区別するため、ユーザー入力の絞り込みには適していません。text().toLowerCase().includes() の組み合わせを推奨します。
Qページネーションと組み合わせるには?
Aフィルター後の可視項目数を数えて、ページあたりの表示件数で割ってページ数を算出します。フィルター実行ごとにページ番号を1にリセットし、可視項目のうち当該ページ範囲だけ show() するのが基本的なアプローチです。
QAjaxで取得した動的コンテンツにもフィルターを適用できますか?
AAjaxで要素を追加した後にフィルター関数を再実行すれば適用できます。フィルター関数を function applyFilter() {...} のように外出しにしておき、Ajax完了コールバックで applyFilter() を呼ぶ設計にしてください。