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 では開いた状態になっていて、保存した状態はブラウザで読み込んでから反映しているためです。

これを避けるには、ページを表示する前に開閉を決める仕組みが要ります。今のところは、一瞬のことなので、仕組みを複雑にするよりこのままにしています。

⎇ main✓ 13 deps9 articles
UTF-8LFMDX