フリーワード検索やカテゴリボタンでページをリロードせずにリストやテーブルを絞り込む機能は、ユーザーが大量のコンテンツを素早く見つけるための定番UIです。この記事では基本的なテキスト絞り込みから、カテゴリフィルター・件数表示・ソート・複数条件の組み合わせまで実用的なパターンを体系的に解説します。
この記事でわかること
- テキスト入力でリスト・テーブルをリアルタイムに絞り込む
- カテゴリボタンでフィルタリング(data属性を使った管理)
- 一致件数の表示と「0件」メッセージの実装
- テキスト検索とカテゴリを同時に絞り込む複数条件
- アルファベット順・逆順のソート機能
基本: テキスト入力でリストを絞り込む
入力したテキストに部分一致するリストアイテムのみを表示し、一致しないものを非表示にします。
HTML
<input type="text" id="search-input" placeholder="絞り込み..."> <p>ヒット: <span id="hit-count">8</span> 件</p> <ul id="item-list"> <li class="list-item">HTML / CSS</li> <li class="list-item">JavaScript</li> <li class="list-item">jQuery</li> <li class="list-item">TypeScript</li> <li class="list-item">React</li> <li class="list-item">Vue.js</li> <li class="list-item">Node.js</li> <li class="list-item">Python</li> </ul> <p id="no-result" style="display:none">該当する項目が見つかりません。</p>
JavaScript
$(function () {
$('#search-input').on('input', function () {
var keyword = $(this).val().toLowerCase();
var count = 0;
$('.list-item').each(function () {
var matches = $(this).text().toLowerCase().includes(keyword);
$(this).toggle(matches);
if (matches) count++;
});
$('#hit-count').text(count);
$('#no-result').toggle(count === 0);
});
});
keyupではなくinputを使う理由
keyup イベントはキーを押した後に発火しますが、スマホのIME入力やペーストで変化が検出されない場合があります。input イベントはフォームの値が変化したときに確実に発火するため推奨です。テーブル行の絞り込み
テーブルの場合は <tr> をまとめて show/hide します。ヘッダー行(thead)は常に表示したままにします。
<input type="text" id="table-search" placeholder="絞り込み...">
<table id="data-table">
<thead>
<tr><th>名前</th><th>カテゴリ</th><th>言語</th></tr>
</thead>
<tbody>
<tr><td>React</td><td>フレームワーク</td><td>JavaScript</td></tr>
<tr><td>Vue.js</td><td>フレームワーク</td><td>JavaScript</td></tr>
<tr><td>Django</td><td>フレームワーク</td><td>Python</td></tr>
<tr><td>Laravel</td><td>フレームワーク</td><td>PHP</td></tr>
</tbody>
</table>
$(function () {
$('#table-search').on('input', function () {
var keyword = $(this).val().toLowerCase();
// tbody の tr のみを対象(thead はスキップ)
$('#data-table tbody tr').each(function () {
var rowText = $(this).text().toLowerCase();
$(this).toggle(rowText.includes(keyword));
});
});
});
カテゴリボタンによるフィルタリング
data属性でカテゴリを管理し、ボタンでフィルタリングするパターンはポートフォリオや記事一覧でよく使われます。
HTML
<div class="filter-buttons"> <button class="filter-btn is-active" data-filter="all">すべて</button> <button class="filter-btn" data-filter="frontend">フロントエンド</button> <button class="filter-btn" data-filter="backend">バックエンド</button> <button class="filter-btn" data-filter="db">データベース</button> </div> <ul id="portfolio-list"> <li class="card-item" data-category="frontend">HTML / CSS</li> <li class="card-item" data-category="frontend">JavaScript</li> <li class="card-item" data-category="frontend">React</li> <li class="card-item" data-category="backend">PHP</li> <li class="card-item" data-category="backend">Python</li> <li class="card-item" data-category="db">MySQL</li> <li class="card-item" data-category="db">PostgreSQL</li> </ul>
JavaScript
$(function () {
$('.filter-btn').on('click', function () {
var filter = $(this).data('filter');
// ボタンのアクティブ状態を更新
$('.filter-btn').removeClass('is-active');
$(this).addClass('is-active');
// カードの表示・非表示
if (filter === 'all') {
$('.card-item').show();
} else {
$('.card-item').each(function () {
var matches = $(this).data('category') === filter;
$(this).toggle(matches);
});
}
});
});
.filter-btn {
padding: 6px 14px;
border: 1px solid #d1d5db;
border-radius: 20px;
background: #fff;
cursor: pointer;
transition: background 0.2s, color 0.2s;
}
.filter-btn.is-active {
background: #0284c7;
color: #fff;
border-color: #0284c7;
}
テキスト検索とカテゴリを同時に絞り込む(複数条件)
$(function () {
var currentFilter = 'all';
var currentKeyword = '';
function applyFilter() {
$('.card-item').each(function () {
var categoryMatch = (currentFilter === 'all') ||
($(this).data('category') === currentFilter);
var textMatch = $(this).text().toLowerCase().includes(currentKeyword);
// 両方の条件を満たす場合のみ表示
$(this).toggle(categoryMatch && textMatch);
});
var count = $('.card-item:visible').length;
$('#hit-count').text(count);
$('#no-result').toggle(count === 0);
}
$('.filter-btn').on('click', function () {
currentFilter = $(this).data('filter');
$('.filter-btn').removeClass('is-active');
$(this).addClass('is-active');
applyFilter();
});
$('#search-input').on('input', function () {
currentKeyword = $(this).val().toLowerCase();
applyFilter();
});
});
状態を変数で管理するのがポイント
現在のカテゴリと検索テキストをそれぞれ変数(
現在のカテゴリと検索テキストをそれぞれ変数(
currentFilter・currentKeyword)に保持することで、どちらが変更されても常に最新の両条件でフィルタリングし直せます。ソート機能(A-Z順・Z-A順)
リストアイテムをjQueryで並び替える場合は、配列にソートしてからDOMに追加し直します。
$(function () {
$('#sort-asc').on('click', function () {
var $list = $('#item-list');
var items = $list.find('.list-item').toArray();
items.sort(function (a, b) {
return $(a).text().localeCompare($(b).text(), 'ja');
});
// DOMを並び替えた順で追加し直す
$.each(items, function (i, item) {
$list.append(item);
});
});
$('#sort-desc').on('click', function () {
var $list = $('#item-list');
var items = $list.find('.list-item').toArray();
items.sort(function (a, b) {
return $(b).text().localeCompare($(a).text(), 'ja'); // 逆順
});
$.each(items, function (i, item) {
$list.append(item);
});
});
});
まとめ
jQueryでリストやテーブルを絞り込む実装のポイントをまとめます。
- テキスト絞り込み:
inputイベント +text().toLowerCase().includes()+toggle() - カテゴリフィルター:
data-category属性 + ボタンのdata("category")と比較 - 複数条件: 現在の状態を変数で管理し、どちらが変更されてもフィルタ関数を再実行
- 件数表示: フィルタ後に
$(".item:visible").lengthで件数取得 - ソート:
.toArray()で配列化→sort()→append()で追加し直す
関連記事: フィルター機能の実装完全ガイド / 検索サジェスト(オートコンプリート)機能 / 文字列をハイライトする完全ガイド
よくある質問(FAQ)
Q絞り込み後、入力をクリアしたら全件表示に戻したいです。
A
input イベント内で $(this).val() が空のときに $(".list-item").show() を呼ぶか、絞り込み条件が空なら全件表示のロジックを toggle() 内に含めてください。上記のコード例では keyword が空文字の場合 "".includes(anything) はtrueになるため自動的に全件表示されます。QAjax で追加されたアイテムにも絞り込みを適用したいです。
AAjaxでアイテムを追加した後、絞り込みフィルタ関数を再実行してください。フィルタ関数を独立した名前付き関数(
function applyFilter())にしておくと、Ajax完了コールバックから呼び出しやすくなります。Q絞り込みにアニメーション(フェード)を付けたいです。
A
toggle() の代わりに fadeIn(200) / fadeOut(200) を使えます。ただし、アニメーション中に次の入力が来ると重なる場合があるため、stop(true, true) を先に呼んでキューをクリアしてください:$(this).stop(true, true).fadeToggle(200, matches)QURLにフィルター状態を保存してシェアできるようにしたいです。
A
history.pushState() でURLにクエリパラメーター(例: ?cat=frontend&q=react)を追加し、ページロード時に URLSearchParams でパラメーターを読み取ってフィルターを自動適用してください。URL操作の詳細はURLを取得・操作する完全ガイドを参照してください。Q大量データ(1000件以上)でパフォーマンスが低下します。
AjQueryのDOM操作は件数が増えると重くなります。対策として:(1) debounce で処理頻度を下げる(300ms)、(2) 仮想スクロール(表示範囲のDOM要素のみ生成)、(3) サーバーサイドでフィルタリングしてAjaxで結果を受け取る、などの方法があります。1000件以下であれば通常は問題ありません。