ついにハッシュは
GPSへ進化
ハッシュと :target 疑似クラスとセットで
まるで追跡装置に

hash_become_a_tracking_device  key visual by Yohiichiroh Kohtani | digivalley
Toplabo
Hash Become A Tracking Device

この記事は『ハッシュだって超えられる』の続きになります。先にそちらをご覧ください。

ハッシュが長い旅の果てにたどり着いたものとは

 | 2026/05/20

事の
経緯

ハッシュはtarget疑似クラスとの併用でGPS化

  • これまで、ハッシュについて、いくつか記事を公開してきたけど、今回はさらに実用的な記事を紹介するよ。

    ハッシュはtarget疑似クラスとセットで使うことでまるでGPSのような使い方ができるんだ。
    登場したのは15年ほど前だから、もうずいぶんと枯れた技術だね。私もまったく気にも留めてなかった。
    それが最近、急に魅力を放ち始めたんだよね。実際面白いんだよ、これが。
    これは、最近のCSSや他のJS、ブラウザの進化が、target疑似クラスの真価を見直すきっかけになったんじゃないかな。(ダジャレになっちゃったね爆)

target疑似クラスとは

  • ちょっと異質な疑似クラスtarget

    まずは基本のメカニズムから。:target は、「現在のURLの末尾にあるハッシュ(#id)と、ページ内の要素の id が一致した瞬間」にだけ適用される疑似クラスなんだ。
    なんか特殊だよね。

    JSのクリックイベントを一切書かずに、「状態の切り替え(State Management)」をURL側(ブラウザの標準機能)に委ねられるのが、この機能の最大の魅力。
    なんかすごく魅力的なのになぜ普及しなかったんだろうね。

    基本的な概念のコードを下記に。

    <!-- リンクをクリックすると、URLが example.com/#section1 になる -->
    <a href="#section1">URLを#section1へ切り替え</a>
    
    <!-- URLのハッシュが #section1 の時だけ、この要素にスタイルが当たる -->
    <section id="section1" class="box">
      <h2>セクション 1</h2>
    </section>
    
    /* 通常時は非表示 */
    .box {
      display: none;
    }
    
    /* URLのハッシュとidが一致した時だけ表示 */
    .box:target {
      display: block;
      background-color: #f0f0f0;
    }
    
    

原因を
探る

なぜ15年前は普及しなかったのか?

  • 前の章で紹介した通り、凄く魅力的だよね。しかし、なぜか普及しなかったんだよね。

    今、いろいろ試してみると結構面白いんだよね。
    当時も、面白いデモがたくさん作られたんだって、見てみたいね。
    ま、でも敬遠されたのには、ブラウザの仕様に絡むハッキリとした理由があるんだよね。
    実際試してみるとわかるよ。
    主に次の三つ。

  • 1.続けて二度はCSSが機能しない問題(発火の限界)

    一度処理が走ると、二度目が無理だった。これが一番のネックだね。一回しかできない機能なんて使い道ないからね。もっとも致命的。
    :target は「URLのハッシュが変化したこと」をトリガーにする。
    そのため、例えば「モーダルを閉じるボタン」を作ろうとして、URLを元の状態(ハッシュなし)に戻さない限り、同じ要素をもう一度アクティブにすることができないんだ。
    また、同じページ内で「閉じる」ために href="#" や href="#close" などのダミーハッシュへ遷移させる必要があり、「2回連続で同じリンクを押しても何も起きない」「状態をリセットするためだけの空のハッシュが増える」という頭の悪い設計も嫌われた。

  • 2.履歴(History)が汚れる問題

    :target を使ってタブを切り替えたり、モーダルを開いたりするたびに、ブラウザの「戻る」「進む」の履歴にハッシュ(#tab1 ➔ #tab2 ➔ #tab3)が刻まれてしまう。
    ユーザーが「前のページに戻りたい」と思ってブラウザの「戻る」ボタンを押したとき、ページが戻らずに、さっき閉じたモーダルがまた開くという、最悪のユーザー体験(UX)を引き起こす原因になるよね。
    さらにこの履歴にからむ挙動はブラウザによってかなり違うんだよね。対策の方法によってはfirefoxなんかは往ったり来たりするループにはまることもあるしね。
    当時は悪夢のIE問題とかあったから、ブラウザの挙動が違うってのは、使いたくなくなる大きな要因だったことは容易に想像できるね。

  • 画面が勝手にスクロールする(アンカー挙動の強制)

    ブラウザのネイティブな仕様として、URLに #id が付くと、その id を持つ要素が表示されている位置まで画面が自動スクロールする。
    「画面の最上部で固定されたタブUI」を作りたいのに、タブを切り替えるたびに画面がピコピコ上下にスクロールしてしまうため、ユーザビリティは最悪だね。
    これは今までも『ハッシュでもスムーススクロール』などで取り上げた問題だね。

再発見
再始動

なぜ「今」だと面白いのか?

  • 目的・用途の変化と、周辺機能の進化

    当時は前章の問題点が原因で普及しなかったようだね。
    しかし、コンポーネント駆動開発やモダンCSSが成熟した今、本来の「URLと連動する」という強みが綺麗に活かせるようになってきた。
    これがtarget疑似クラスの本来持ってる魅力をひきだしたって感じだね。
    特にhas疑似クラスと一緒に使うと可能性を感じるね。
    とはいうもののtarget疑似クラスをうまく使いこなすにはやはりこれまで説明してきた問題点を解決する必要があるよね。
    次の段落でtarget疑似クラスの問題点の解決方法を説明するね。

  • 解決方法1. スクロール問題は設計方針と用途

    昔はアンカー移動のスクロールを止めるのが大変だったけど、今のモダンCSSなら、スクロールの挙動を上品にコントロールできるよね、止めることもできる。前にやったよね。
    例えば、スクロールを活かすにしても、固定ヘッダーに被らないように scroll-padding-top や、滑らかに移動させる scroll-behavior: smooth; で、:targetへのジャンプをゆるゆるにできるしね。
    まあ、一番はスクロールを必要としない、高さがブラウザ画面と同じ高さのデザインにする。用途限定だけどね。
    つまり用途次第ってことだね。用途・目的を合わせれば何の問題もない。

  • 解決方法2. 続けて二度はCSSが機能しない問題

    これはJSで最適なタイミングでハッシュをクリアすることで続けて同じ処理を走らせることが可能になる。このあとデモで確認してみて。

  • 解決方法3. 履歴(History)が汚れる問題

    これが一番解決が難しいね。前に話したように、ブラウザによってhistory.back()の挙動が違うからね。
    基本はハッシュがURLに入るたびに直前のURLで最後に上書きすればいいんだけど、ブラウザそれぞれに対策する必要があるね。
    なので、これに関しては、ブラウザの戻るボタンを必要としないデザイン爆!ってことになるね。
    え、いやいや、きちんとUIを構築すればブラウザの戻るボタンなんて使わないよね。(笑)

  • 新しい価値

    SPA(Single Page Application)全盛の今だと、「URLをシェアしたら、その特定の状態をそのまま相手に共有できる」というのを簡単に構築できるのは強みだね。
    JSでガチガチに状態管理されたUIだと、URLが変わらないため「この部分だけを誰かに教えたい」ができない、(大きい声では言えないけどパラメータをいっぱい追加すれば簡単(笑))。でも:target なら、CSSだけでそれが一瞬で実現しちゃうね。

    さらに、モーダル系、ポップアップ系は&lg;dialog<など主流だけど、「外部からアクセスしてきた時だけ、特定の要素をハイライト(またはスクロール注目)させる」みたいな演出として :target を使うのは面白いんじゃないかな。これも基本を『ハッシュだって超えられる!』でやったよね。

実践

では実践編。target疑似クラスとhashのコンビでGPS化!

  • 一回こっきりのサンプル紹介

    よく駅の改札を出たところにあるマップ。あれをイメージしてみたよ。
    病院とかのボタンを押すと、地図上で光るやつね。
    ただし、このサンプルは同じIDは連続では発光させられない。一回こっきり。
    しかし、同じIDを続けてはだめだけど、他のIDを挟むと問題なく同じIDを処理できる。
    これが前の章で解説した問題点のひとつだね。
    だけど、このデモの面白いところは、CSSだけで、簡単に『どこどこマップ』が実装できるってことだね。

    target疑似クラスとハッシュで作る『どこどこマップ』を外部ページで確認:https://labo.studio-happyvalley.com/hashmap/simple/

    このデモの簡単な解説を下記で。

    <!-- アイコンの方にはIDを指定して -->
    <figure id="inn"><img src="/assets/labo/hashmap/images/inn.avif" alt="Inn"></figure>
    
    <!-- ボタンの方にはそのIDをハッシュにする -->
    <a href="#inn">冒険者の宿</a>
    
    <!-- CSSには:targetクラスにスタイルを指定するだけ -->
    figure:target {なんか適当に色とか指定}
    
    <!-- ただのこれだけでどこどこマップが実現するんだよ -->
  • 問題を解決したバージョンのサンプル

    最初は、history.replacementで進めていたけど、履歴を書き換えるタイミングが問題で、上手くいかなかった。
    また、ブラウザによっても挙動が全くちがうのでJSで履歴をいじるのはやめて他の方法を模索。
    しかし、よく考えてみると、前回のデモは同じIDを続けてはだめだけど、他のIDを挟むと問題なく同じIDを処理できる。
    これって、自動的にハッシュを消すようにJSで組めばいいんじゃないの??ってのが解決の糸口。
    自動的に消すタイミングはどうするか、そんなの決まってる、アニメーションで発光させてる処理が終わった後。
    つまり

    window.addEventListener('animationend', () => {
    	location.hash = '_';
    });
  • というようにアニメーション処理が終わった後に、ハッシュをクリアしてやればよい。
    ここはJSが必要だね。
    で、次がそのデモ。

    target疑似クラスとハッシュで作る『どこどこマップ修正版』を外部ページで確認:https://labo.studio-happyvalley.com/hashmap/ext/
    これで何度でも同じアイコンをクリックできる。ちょっとだけJS使ったけど、でもこれも基本はCSSのアニメーション処理が終了したあとのイベントを処理してるだけだから、ほぼCSSのみといっても過言じゃないよね。
    target疑似クラス、こういう使い道ならすごく面白いよね。
    ただし、履歴が蓄積する問題はクリアできないね、なのでブラウザの戻るボタンが必要ない用途向け。これも使い方次第ってことかな。
    ※実はこの履歴の問題もJSで解決できた。ただし、通常は上で問題ないので、これで良しとする爆!!

拡張版・ダンジョンマップのアリス。

  • 拡張版では、いよいよGPSを装着したアリスが登場

    これまではただ、アイコンが光るだけの動きのないデモだったよね。そこで今回は拡張版というっことで、GPSを装着したアリスの登場だ。
    一応緑色のセルがアリスのつもりね。💦
    動き回るアリスが時々消えちゃうんだよ。
    消えてるときだけGPSが起動するので、『どこにいる、アリス?』をクリックするとアリスの位置を教えてくれるよ。
    アリスがどこにいるのか、追跡してね。

    target疑似クラスとハッシュで作る『どこにいる、アリス?』を外部ページで確認:https://labo.studio-happyvalley.com/hashmap/dungeon_invisible/
    このデモはほとんどJSだけど、それでもやっぱりコアの機能はtarget疑似クラスなんだよね。
    面白かったかな?

今後

target疑似クラスの使いどころ

  • 最新のCSS/JSと比較した、:target のメリット

    今や、ポップアップやモーダルならHTML5の <dialog> タグがあるし、状態管理ならJavaScript(React, Vue, Vanilla JSのクラス切り替え)が主流だね。
    それらと比較して、:target を使うメリットはあるのかな?


    機能 / 技術
    状態の保持(URL連動)
    実装の軽さ
    アクセシビリティ・制御


    :target (CSS)
    ⭕ 自動(URLに刻まれる)
    爆速(HTML/CSSのみ)
    ⚠️ スクロールや履歴の制御が硬い


    <dialog> (HTML/CSS)
    ❌ なし(JSで開閉)
    🟨 普通
    ⭕ 最強(フォーカス制御等)


    JavaScript / 状態管理
    🔺 実装次第(History APIが必要)
    🟨 コード量増
    ⭕ 自由自在

  • :target疑似クラス が勝っているポイント1:JSが1行もいらない(絶対的なパフォーマンスと堅牢性)

    どれだけJSの実行が重くなろうが、あるいはユーザー環境でJSがブロックされていようが、ブラウザの基本機能として100%確実に、超高速で動作。

  • :target が勝っているポイント2:URLの共有」が標準装備

    JSで「特定のタブが開いた状態のURL」を作ろうとすると、URLのクエリパラメータを監視して、ロード時にそれをパースして…という処理(ルーティング)をわざわざ書く必要がある、これはさっき書いた通り。
    :target は、ブラウザに最初から組み込まれているネイティブなルーティングシステムなので何もする必要なし。

    まあ、この二つくらいかな

長い旅路の果てについにhashがGPSに進化

  • 癖は強いけど工夫次第でとても魅力的な機能

    ということで3回にわたってハッシュを解説してきたけど(実質2回?)、今回をもって、hashは長い旅路の果てについにGPSに進化したってことで、めでたしめでたし。(笑)
    まあ、いろいろ面白い点も多いけど、非常に癖が強いのは扱いづらい点でもあるね。特にURLの履歴が難しいね。ブラウザの機能と直結しているからね。
    でも、簡単な演出用とかだと、工夫次第でいろいろ出番があると思うんだけど、どうかな。

『ついにハッシュはGPSへ進化』関連のお薦め

このサイトで紹介しているコード、プログラムなどは個人の学習目的で作成されたものであり、いかなる保証も行いません。
利用はすべて自己責任でお願いします。
ただし、このページで紹介しているプログラムやビジュアルなどはご依頼いただければ実装を賜ります。
お問い合わせはこちら