details要素でファイルツリーを作る。開閉の状態をページ移動後も保つ方法
本記事にはアフィリエイト広告(PR)を含みます。詳しくは広告・アフィリエイトについてをご確認ください。
このサイトの左側には、エディタのようなファイルツリーがあります。books/ や dev/ のフォルダは、クリックで開いたり閉じたりできます。この記事では、このフォルダの開閉を、HTML標準の details 要素で作った方法を書きます。
details と summary で開閉を作る
details 要素は、中身を開いたり閉じたりできる HTML の要素です。最初の子要素に summary を置くと、その部分をクリックしたときに開閉します。
<details open>
<summary>books/</summary>
<ul>
<li><a href="/books">index.md</a></li>
<li><a href="/books/nmat-taisaku-hon">NMAT対策本4冊を…</a></li>
</ul>
</details>
開閉の仕組みはブラウザが持っているので、JavaScript を書かなくても動きます。キーボードでも summary にフォーカスを当てて Enter キーやスペースキーで開閉でき、スクリーンリーダーにも開いているかどうかが伝わります。自分で開閉のボタンを作るより、ずっと手間がかかりません。
open 属性を付けておくと、最初から開いた状態で表示されます。
矢印を自前にする
summary には、ブラウザが付ける三角の印が出ます。エディタのツリーに近づけるため、これを消して、自前の「›」を回すようにしました。
.TreeFolder__summary {
list-style: none;
}
/* Safari などで出る三角を消す */
.TreeFolder__summary::-webkit-details-marker {
display: none;
}
.TreeFolder__chevron {
display: inline-block;
transition: transform 0.12s ease-out;
}
/* 開いているときは矢印を下向きにする */
.TreeFolder[open] > .TreeFolder__summary > .TreeFolder__chevron {
transform: rotate(90deg);
}
開いているかどうかは details の open 属性で分かるので、CSS の属性セレクタで矢印の向きを変えられます。ここでも JavaScript は使っていません。
開閉の状態を、ページを移動しても保つ
ここからは JavaScript の出番です。静的なサイトでは、ページを移動するたびに、ツリーが書き出したときの状態(初期の開閉)に戻ってしまいます。閉じたはずのフォルダが、次のページで開いていると落ち着きません。
そこで、開閉したときに状態を sessionStorage に保存し、ページを読み込んだあとに戻すようにしました。
const detailsRef = useRef<HTMLDetailsElement>(null);
// 読み込み後に、保存済みの開閉状態へ合わせる
useEffect(() => {
const details = detailsRef.current;
if (!details) return;
const saved = readFolderOpen(folderId);
details.open = containsActive || (saved ?? defaultOpen);
}, [folderId, defaultOpen, containsActive]);
// 開閉されたら保存する
const handleToggle = () => {
const details = detailsRef.current;
if (details) writeFolderOpen(folderId, details.open);
};
return (
<details ref={detailsRef} open={defaultOpen} onToggle={handleToggle}>
{/* summary とファイルの一覧 */}
</details>
);
開閉そのものはブラウザに任せたままで、JavaScript は「保存」と「復元」だけを受け持ちます。sessionStorage はブラウザにしかないので、書き出す HTML では初期の開閉にしておき、読み込んだあとで保存済みの状態に合わせています。
今のページを含むフォルダは開いて見せる
閉じたフォルダの中の記事を開いたとき、ツリーのどこにいるのか見えないと迷います。VS Code も、開いたファイルの場所をツリーで見せてくれます。
そこで、今のページを含むフォルダは、保存された状態が「閉じる」でも開いて見せるようにしました。上のコードの containsActive がそれです。ほかのページに移ると、また保存された状態に戻ります。
ただし、ツリーの一番上の階層(my-deps)だけは、この対象から外しました。どのページも一番上の階層の中にあるので、対象にすると、閉じても次のページで必ず開き直してしまうからです。最初はこれに気づかず、一番上の階層を閉じられない状態になっていました。
知っておいてほしい弱点
閉じた状態を保存したフォルダは、ページを読み込んだ直後に一瞬だけ開いて見え、すぐに閉じます。書き出した HTML では開いた状態になっていて、保存した状態はブラウザで読み込んでから反映しているためです。
これを避けるには、ページを表示する前に開閉を決める仕組みが要ります。今のところは、一瞬のことなので、仕組みを複雑にするよりこのままにしています。