1日目のゴールと今日やること
1日目のテーマは
「URL のハッシュ(#)を使って、SPA っぽいタブ切り替えを実現する」ことです。
“SPA 風”というのは、
ページ全体をリロードせずに、
タブを切り替えるだけで画面が変わっていくように見せる、という意味です。
今日のゴールは、ざっくり言うとこうです。
タブをクリックすると表示が切り替わる。
ブラウザの戻る / 進むでタブ状態が戻る。
再読み込みしても、さっき見ていたタブが復元される。
これを「URL の hash」と「状態と表示の分離」という考え方で作っていきます。
まずは「URL の hash」とは何かを理解する
hash ってどこのこと?
ブラウザの URL を見ると、こんな形があります。
https://example.com/app.html#profile
この #profile の部分が hash(ハッシュ) です。
サーバーには送られず、
ブラウザの中だけで使われる「おまけ情報」のようなものです。
なぜ hash を使うと SPA っぽくなるのか
ページ全体をリロードせずに
「今どの画面を見ているか」を URL に表現できるからです。
#home → ホームタブ#profile → プロフィールタブ#settings → 設定タブ
こうしておくと、
タブを切り替えるたびに hash を変える。
ブラウザの戻る / 進むで hash が変わる。
hash が変わったら、それに応じて表示を切り替える。
という流れが作れます。
今日作る「SPA風タブ切り替えアプリ」のイメージ
HTML のイメージ
<nav class="tabs">
<a href="#home" data-tab="home">ホーム</a>
<a href="#profile" data-tab="profile">プロフィール</a>
<a href="#settings" data-tab="settings">設定</a>
</nav>
<section class="view" data-view="home">ホーム画面の内容</section>
<section class="view" data-view="profile">プロフィール画面の内容</section>
<section class="view" data-view="settings">設定画面の内容</section>
ポイントは、
「タブ」と「表示する画面」をdata-tab と data-view で紐づけていることです。
タブは <a href="#home"> のように hash を持っています。
画面は data-view="home" のように「名前」を持っています。
状態と表示を分けて考える、という超重要な発想
よくある“やりがち”な書き方
初心者のうちは、ついこう書きがちです。
タブをクリックしたら、その場で display: none を切り替える。
どのタブが選ばれているかは DOM を見て判断する。
これでも動きますが、
「状態」と「表示」がごちゃごちゃになります。
今日の方針
状態(今どのタブが選ばれているか)は
「URL の hash」と「JavaScript の変数」で管理する。
表示(どの画面を見せるか)は
「状態をもとに DOM を更新する」だけにする。
この分離ができると、
コードが一気に読みやすく、壊れにくくなります。
タブの「状態」を hash から読み取る
今の hash を取得する
function getCurrentTabFromHash() {
const hash = window.location.hash; // 例: "#home"
if (!hash) return "home"; // デフォルトは home にする
return hash.replace("#", ""); // "#home" → "home"
}
JavaScriptここでやっていることはシンプルです。
URL の hash を読む。
なければ “home” を返す。
あれば “#” を取り除いてタブ名にする。
これで「状態としてのタブ名」が手に入ります。
状態に応じて表示を切り替える関数を作る
表示更新の関数
function renderTab(activeTabName) {
const views = document.querySelectorAll(".view");
views.forEach(view => {
const name = view.dataset.view;
if (name === activeTabName) {
view.style.display = "block";
} else {
view.style.display = "none";
}
});
const tabs = document.querySelectorAll(".tabs a");
tabs.forEach(tab => {
const name = tab.dataset.tab;
if (name === activeTabName) {
tab.classList.add("is-active");
} else {
tab.classList.remove("is-active");
}
});
}
JavaScriptここで大事なのは、
「表示は状態に従うだけ」 にしていることです。
activeTabName という「状態」を受け取って、
画面とタブの見た目をそれに合わせているだけです。
タブクリックで「状態」と「URL」を更新する
タブにイベントを付ける
function setupTabClick() {
const tabs = document.querySelectorAll(".tabs a");
tabs.forEach(tab => {
tab.addEventListener("click", event => {
event.preventDefault(); // 通常のリンク遷移を止める
const tabName = tab.dataset.tab;
window.location.hash = tabName; // URL の hash を更新
renderTab(tabName); // 状態に応じて表示を更新
});
});
}
JavaScriptここでやっていることは 3 ステップです。
通常のリンク遷移を止める。
クリックされたタブの名前を取り出す。
hash を書き換え、同時に表示も更新する。
ポイントは、
「状態の変更 = hash の変更」
と決めていることです。
hashchange イベントで「戻る / 進む」に対応する
ブラウザの戻る / 進むで hash が変わる
ユーザーがブラウザの戻るボタンを押すと、
URL の hash が変わります。
このときに「表示も変える」必要があります。
hashchange イベントを使う
window.addEventListener("hashchange", () => {
const tabName = getCurrentTabFromHash();
renderTab(tabName);
});
JavaScriptこれで、
タブクリック → hash を変える → renderTab
戻る / 進む → hash が変わる → hashchange → renderTab
という流れが完成します。
再読み込み時に「さっきのタブ」を復元する
ここまでの仕組みができていれば、
実は「復元」はほぼ勝手に実現できます。
ページを再読み込みしても、
URL の hash はそのまま残るからです。
初期化処理で hash を見る
function init() {
setupTabClick();
const initialTab = getCurrentTabFromHash();
renderTab(initialTab);
}
document.addEventListener("DOMContentLoaded", init);
JavaScriptこれで、
https://example.com/app.html#settings
という URL で開いた場合、
最初から「設定タブ」が表示されます。
また、
タブを切り替えたあとに再読み込みしても、
そのときの hash に応じて同じタブが復元されます。
1日目の全体の流れを言葉でなぞる
ここまでを、コードではなく「流れ」で整理してみます。
タブは <a href="#home"> のように hash を持っている。
画面は data-view="home" のように名前を持っている。
ページ読み込み時に:
今の URL の hash から「どのタブか」を決める。
そのタブ名をもとに renderTab で表示を切り替える。
タブにクリックイベントを付けて、クリックされたら hash を書き換える。
ユーザーがタブをクリックしたら:
通常のリンク遷移は止める。
クリックされたタブの名前を読む。
window.location.hash にその名前を書く。
renderTab で表示を切り替える。
ユーザーが戻る / 進むを押したら:
hashchange イベントが発火する。
今の hash からタブ名を読み取る。
renderTab で表示を切り替える。
これで、
タブ切替
履歴対応
再読み込み復元
の 3 つが、
「hash を状態として扱う」という一本の軸でつながります。
今日いちばん深く理解してほしいこと
1日目の本質は、これです。
「状態(今どの画面か)を URL の hash として表現し、
表示はその状態に従って更新するだけにする」
タブをクリックした瞬間に DOM を直接いじるのではなく、
一度「状態」に落としてから表示を変える。
この感覚がつくと、
タブだけでなく、
ページング、フィルタ、モーダル、
あらゆる UI を「状態と表示の分離」で考えられるようになります。
2日目では、このタブ切り替えを
「クラス化」して、
アプリのどこでも再利用できる形にしていきます。


