💪

HTML と CSS だけでヘッダにぶら下げ目次を作る

2020/10/08に公開

HTML と CSS だけでヘッダにぶら下げ目次を作る

まず結果を見たい人向け

ソースコードとサンプル

作りたいもの (要件)

  • ページの上部に常駐するヘッダに各ページへのリンクを含む目次を作りたい.
  • 目次はカテゴリごとに分類して, 子ページへのリンクを一覧にしたい.
  • カテゴリにマウスカーソルをホバーするか, タップすると子ページの一覧がぶら下がって出てくる.

実装解説

HTML は非常にシンプル.

index.html
<header> <!-- ヘッダそのもの. -->
  <ul> <!-- 目次. -->
    <li> <!-- カテゴリ A. -->
      <span>Category A</span> <!-- カテゴリ名. ここのホバー (またはタップ) で一覧表示 -->
      <ul> <!-- 子ページ一覧. -->
        <a href="#"><li>Item A1</li></a>
        <a href="#"><li>Item A2</li></a>
        <a href="#"><li>Item A3</li></a>
      </ul>
    </li>
  </ul>
</header>

初期状態 (子ページ一覧が非表示の状態) の CSS は以下の通り.

style.css
* {
  box-sizing: border-box;
  -webkit-overflow-scrolling: touch;
}

header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 64px;
  background: #fff;
  box-shadow: 0 2px 8px rgba(0,0,0,.3);
  padding: 8px;
}

header > ul {
  display: flex; /* 横に並べるために flex を使用. */
  list-style: none; /* 行頭の箇条書き記号を消す. */
  margin: 0; /* デフォルトの上下 margin を消す. */
  padding: 0; /* 行頭の箇条書き記号のための padding を消す. */
  height: 48px;
  cursor: default; /* マウスカーソルを文字選択から通常の矢印に変更. */
}

header > ul > li {
  position: relative;
  padding: 0 6px;
}
header > ul > li:first-child {
  padding-left: 0;
}
header > ul > li:last-child {
  padding-right: 0;
}

header > ul > li > span {
  display: inline-block;
  height: 36px;
  line-height: 36px;
  font-size: 20px;
  padding: 0 12px;
  margin: 6px 0;
  border-radius: 4px;
  white-space: nowrap;
}

header > ul > li > ul {
  height: 0; /* 子ページ一覧は高さを 0 にして非表示. */
  overflow: hidden; /* はみ出した分を非表示. */
}

まずカテゴリ名にホバーした時に背景色にハイライトを付ける.

style.css
header > ul > li:hover > span {
  background-color: rgba(0,0,0,.075);
}

次に子ページ一覧の <ul> 要素の高さを 0 から auto に変更して表示する. ついでに上下 padding も入れておいた.

style.css
header > ul > li:hover > ul {
  height: auto;
  padding: 6px 0;
}

この子ページ一覧の <ul> 要素のデザインも整えておく. header > ul > li > ulheight: 0;overflow: hidden; 以外を追記. さらに子ページへのリンク <a><li> にもスタイルを当てる.

style.css
header > ul > li > ul {
  list-style: none;
  padding: 0;
  height: 0; /* 子ページ一覧は高さを 0 にして非表示. */
  overflow: hidden; /* はみ出した分を非表示. */
  background-color: #fff;
  box-shadow: 0 4px 8px rgba(0,0,0,.3);
  border-radius: 4px;
}

header > ul > li > ul > li,      /* <- <li>~~~</li>        に対応. */
header > ul > li > ul > a > li { /* <- <a><li>~~~</li></a> に対応. */
  cursor: pointer; /* マウスカーソルを指の形に変更. */
  height: 32px;
  line-height: 20px;
  padding: 6px 12px;
  font-size: 18px;
  white-space: nowrap; /* 長いテキストの自動改行を禁止. */
}
header > ul > li > ul > li:hover,      /* <- <li>~~~</li>        に対応. */
header > ul > li > ul > a > li:hover { /* <- <a><li>~~~</li></a> に対応. */
  background-color: rgba(0,0,0,.075);
}

header > ul > li > ul > a {
  text-decoration: none; /* <a> 要素の下線を削除. */
  color: inherit; /* リンクテキストの色を通常テキストと同じにする. */
}

ここで, 子ページ一覧にカテゴリ名よりも横幅が大きくなるようなページ名があると, それに影響されてカテゴリ名の右余白が長くなってしまう問題が発生する.

テゴリ名の右余白が長くなってしまっている

これを解決するために, position プロパティを使用する. position プロパティで absolute を指定することによりカテゴリの <li> 要素の幅に影響を与えなくなる.

style.css
header > ul > li {
  position: relative; /* <- 付け足した. relative は通常の位置を基準にする. */
  padding: 0 6px;
}

header > ul > li > ul {
  position: absolute; /* <- 付け足した. absolute は祖先要素のうち position が static (初期値) 以外に設定されている要素の左上を基準にする. この場合親要素の header > ul > li の左上が基準となる. */
  top: 42px; /* <- 付け足した. header > ul > li の左上から, margin-top の 6px と span の高さの合計 42px だけ下にずらす. つまりheader > ul > li > span の底辺とこの ul の上辺が接する. */
  list-style: none;
  padding: 0;
  height: 0;
  overflow: hidden;
  background-color: #fff;
  box-shadow: 0 4px 8px  rgba(0,0,0,.3);
  border-radius: 4px;
}

不具合を修正した

Discussion