JavaScriptの絞り込みフィルタで選択条件をURLに反映させる方法(共有・戻るボタン対応)
JavaScriptで作った絞り込みフィルタの選択条件を、URLに反映させる方法をサンプルコード付きで解説します。 URLSearchParamsとhistory.pushStateで、絞り込んだ状態の共有、戻るボタン、再訪問時の復元に対応できます。
商品一覧や事例一覧に絞り込みフィルタを設置するとき、選んだ条件をURLに反映させておくと、使い勝手が大きく上がります。この記事では、そのメリットと実装方法を、サンプルコード付きで紹介します。
使うのは、ブラウザーに標準で備わっている URLSearchParams と history.pushState の2つだけです。追加のライブラリは要りません。
絞り込み条件をURLに反映させると何が良いのか
たとえば「Web制作の事例だけを絞り込んだ状態」が example.com/cases?category=web のようなURLになると、次の3つのメリットが生まれます。
- 01
絞り込んだ状態のまま共有できる
URLをそのまま送れば、相手にも同じ条件の一覧が表示されます。「この条件の結果を見てほしい」というやりとりや、営業資料・SNSでの紹介がスムーズになります。
- 02
戻る・進むボタンが正しく動く
条件を変えるたびに履歴が残るので、ブラウザーの戻るボタンで前の条件に戻れます。URLに反映していないと、戻るボタンで一覧から出てしまい、絞り込みをやり直すことになります。
- 03
次に開いたときも同じ条件で表示できる
ブックマークしておけば、次に開いたときも同じ条件で一覧が表示されます。詳細ページを見てから一覧に戻ったときも、条件が残ります。
基本の実装
考え方はシンプルで、やることは次の2つだけです。
- 条件が変わったら、URLに書き込む
- ページを開いたとき(と、戻る・進むボタンが押されたとき)は、URLから条件を読み取って絞り込みを再現する
ポイントは、絞り込みの状態を持っているのはURLだけにすることです。画面の表示は、いつもURLを読んで作り直します。こうしておくと、どこから開いても同じ結果になります。
<label for="category">カテゴリ</label>
<select id="category">
<option value="">すべて</option>
<option value="web">Web制作</option>
<option value="print">印刷物</option>
</select>
<ul class="case-list">
<li data-category="web">事例A</li>
<li data-category="print">事例B</li>
<li data-category="web">事例C</li>
</ul>
<script src="filter.js" defer></script>
const select = document.getElementById('category');
const items = document.querySelectorAll('.case-list li');
// URLから条件を読み取って、表示を合わせる
function applyFilter() {
const params = new URLSearchParams(location.search);
const category = params.get('category') || '';
items.forEach((item) => {
item.hidden = category !== '' && item.dataset.category !== category;
});
// セレクトボックスの表示もURLに合わせる
select.value = category;
}
// 条件が変わったら、URLに書き込む(履歴にも追加する)
select.addEventListener('change', () => {
const params = new URLSearchParams(location.search);
if (select.value) {
params.set('category', select.value);
} else {
params.delete('category');
}
const query = params.toString();
history.pushState(null, '', query ? `?${query}` : location.pathname);
applyFilter();
});
// 戻る・進むボタンに対応
window.addEventListener('popstate', applyFilter);
// ページを開いたときに、URLの条件で絞り込む
applyFilter();
コードのポイント解説
URLSearchParamsで、URLの条件を読み書きする
new URLSearchParams(location.search) で、今のURLの ? から後ろを操作できるようになります。get で読み取り、set で書き込み、delete で消します。文字列を自分で組み立てたり分解したりしなくてよいので、日本語などの値も自動で変換してくれます。
history.pushStateで、読み込み直さずにURLを変える
history.pushState を使うと、ページを読み込み直さずにURLだけを書き換え、履歴に積むことができます。location.search に直接代入してもURLは変わりますが、そのたびにページを読み込み直すので、画面が一瞬白くなり、操作が重く感じられます。
popstateイベントを必ず受け取る
戻る・進むボタンが押されると、URLは前の状態に戻り、popstate というイベントが発生します。これを受け取って applyFilter() を呼ばないと、URLは戻っているのに、表示は前の条件のままという食い違いが起きます。
「すべて」のときはパラメータを消す
「すべて」を選んだら params.delete('category') で条件を消し、パラメータが何も残らないときは、? を付けずにページのパスだけに戻します。?category= のような空の条件が残ると、共有されたときに分かりにくくなります。
表示の切り替えは hidden 属性で
当てはまらない項目は hidden 属性で隠しています。見た目はCSS、表示するかどうかはHTMLの属性と分けておくと、デザインを変えたときに表示の切り替えが壊れにくくなります。ただし、項目のCSSで display: flex などを指定していると hidden が効かなくなるので、[hidden] { display: none !important; } を明示しておくと安心です。
絞り込みの状態はURLだけに持たせ、画面はいつもURLから作り直す。これが崩れにくい作り方です。
条件が複数ある場合の広げ方
カテゴリと価格のように条件が複数ある場合は、パラメータの名前を category と price のように分け、どちらも applyFilter の中で読み取ります。条件ごとに別々の関数を作ると管理が大変になるので、URLを読む関数は1つにしておくのがおすすめです。
チェックボックスで複数の値を選べるようにする場合は、同じ名前のパラメータを並べます。?tag=web&tag=logo のような形です。
// 書き込み:チェックの入っている値を、同じ名前で並べる
const params = new URLSearchParams(location.search);
params.delete('tag');
document.querySelectorAll('input[name="tag"]:checked').forEach((box) => {
params.append('tag', box.value);
});
// → ?tag=web&tag=logo
// 読み取り:getAll で配列として受け取る
const tags = new URLSearchParams(location.search).getAll('tag');
// → ['web', 'logo']
知っておきたい注意点
履歴を増やしたくないときは replaceState
キーワードを1文字入力するたびに絞り込むような作りで pushState を使うと、履歴が1文字ずつ積まれ、戻るボタンを何回も押すことになります。こうした場合は、履歴を増やさずにURLだけを置き換える history.replaceState を使います。選び直す操作は pushState、入力の途中は replaceStateと使い分けると自然です。
URLに想定外の値が来たときの表示を用意する
URLは手で書き換えられますし、以前のカテゴリ名が残ったリンクから開かれることもあります。存在しない値が来ると、どの項目も当てはまらず、一覧が空になります。こうしたときのために、0件のときの表示を用意しておきましょう。0件の画面の作り方は、絞り込み検索で結果が0件のときの表示はどうする?で紹介しています。
まとめ
絞り込み条件のURLへの反映は、URLSearchParams と history.pushState の組み合わせで、ライブラリなしに実装できます。共有・戻るボタン・再訪問の3つのメリットは、事例紹介や商品一覧が充実しているサイトほど実感しやすいので、今ある絞り込みフィルタに加えてみてください。
絞り込み機能そのものの作り方やデモは、商品ページに絞り込み(フィルタリング)機能を追加する方法で紹介しています。
よくある質問(FAQ)
Q. ページを読み込み直すと、絞り込みが元に戻ってしまいます
次の3つを確認してください。1つ目は、ページを開いたときに applyFilter() を呼んでいるか。2つ目は、スクリプトがHTMLより先に動いていないか(defer を付けるか、</body> の直前で読み込みます)。3つ目は、サーバーやCMSのリダイレクトで、URLの ? から後ろが消えていないかです。
Q. 絞り込みごとにURLが増えると、SEOで問題になりませんか?
セレクトボックスなどで切り替えるだけなら、絞り込んだURLへのリンクがページ内にないので、検索エンジンに大量に巡回されることは通常ありません。気になる場合は、ページのcanonical(正規のURLを伝えるタグ)が、条件の付いていない一覧のURLを指しているかを確認しておきましょう。検索結果に出したくないページの扱いは、noindexの解説記事も参考にしてください。
Q. VueやReactなどのフレームワークでも、同じ考え方ですか?
考え方は同じです。フレームワークを使う場合は、ルーティングの仕組みが用意しているクエリパラメータの読み書きの方法に置き換えます。
