CSSで作る。目次 (開閉式) の作り方

1. デモ

2. 作り方

このデモでは、アイコンの部分を Material Icon を使っているので下記を head タグ内に記述します。

<link
  href="https://fonts.googleapis.com/icon?family=Material+Icons"
  rel="stylesheet"
/>
<!-- 目次全体 -->
<div class="index_area">
  <!-- 開閉部分 -->
  <input type="checkbox" id="label" class="check" />
  <label for="label"></label>
  <!-- リスト部分 -->
  <div class="index_list">
    <ul>
      <li><a href="#1">text</a></li>
      <li>
        <a href="#2">text</a>
        <ul>
          <li><a href="#2-1">text</a></li>
          <li><a href="#2-2">text</a></li>
        </ul>
      </li>
      <li><a href="#3">text</a></li>
    </ul>
  </div>
</div>
/*目次全体*/
.index_area {
  padding: 20px;
  max-width: 700px;
  font-size: 15px;
  color: #6c6c6c;
}

/*チェックボックス非表示*/
.index_area input {
  display: none;
}

/*開閉部分のスタイル*/
.index_area label {
  display: block;
  position: relative;
  padding: 25px 15px;
  font-weight: bold;
  background: #efefef;
  cursor: pointer;
}

.index_area label:before {
  content: "arrow_drop_down";
  font-family: "Material Icons";
  font-size: 20px;
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
}

.index_area label::after {
  content: "目次";
  position: absolute;
  top: 50%;
  left: 40px;
  transform: translateY(-50%);
}

/*切り替え*/
.check:checked ~ .index_list {
  display: none;
}

.check:checked ~ label:before {
  content: "arrow_right";
}

/*リスト部分のスタイル*/
.index_list {
  background: #efefef;
  padding-bottom: 2.5em;
}

.index_list ul {
  padding: 0 1.5em;
  list-style: none;
  counter-reset: number 0;
}

.index_list li {
  line-height: 2.5;
  padding: 0 1em;
}

.index_list li a {
  display: block;
  color: #6c6c6c;
  text-decoration: none;
  border-bottom: solid 1px #d3d3d3;
}

.index_list li a::before {
  counter-increment: number 1;
  content: counter(number) ".";
  padding-right: 0.5em;
}

.index_list li a:hover {
  opacity: 0.5;
}
.index_list li ul li {
  padding: 0;
}

開閉部分

<input type="checkbox" id="label" class="check" /> <label for="label"></label>

チェックボックスは画面上必要ないので非表示にしています。なので画面上表示されているのは<label>の方です。<input><label>は、id="label"と for="label"で関連付けられているので<label>をクリックしてもチェックボックスにチェックが入る仕組みです。

.check:checked ~ .index_list {
  display: none;
}
.check:checked ~ label:before {
  content: "arrow_right";
}

上記のコードでチェックボックスにチェックが入ったときに、スタイルを追加、上書きして開閉する動きとアイコンを切り替えています。

A ~ B - A より後ろにある同じ階層の B にスタイルを適用する

リストの部分

リストに番号を付けたい場合、<ol>でつけると思いますが、このデモでは、リストに(番号を含めた)下線をつけるために、counter-reset、counter-incrementプロパティで番号をつけています。(黒丸とかは、text-indentで動いてくれますが、何故か番号は動いてくれませんでした)

CSS カウンタの使い方

ul {
  counter-reset: number 0;
}
li::before {
  counter-increment: number 1;
  content: counter(number) ". ";
}

counter-reset、counter-increment -カウンタ名と整数を指定。
counter-increment - 疑似要素でのみ使用可能。
content: counter(カウンタ名) - 番号を呼び出す。

3. ページ内リンクについて

ページ内リンクの指定は下記のように、移動させたい場所に「id」を設定して、リンク先を「#id」にすることでその場所に移動できます。

<!--移動したい場所を指定-->
<li><a href="#1">text1</a></li>
<!--移動する場所-->
<h2 id="1">text1</h2>

ページ内リンクを URL に残さないでスクロール

上記のままだと移動はしてくれますが、スムーススクロール(滑らかにスクロール)はしてくれないのと URL にリンクが残ってしまいページ移動のとき不便なので、動きの部分は jQuery で実装しています。

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
//ページ内リンクスクロール
$(function () {
  $('a[href^="#"]').click(function () {
    var speed = 400
    var href = $(this).attr("href")
    var target = $(href == "#" || href == "" ? "html" : href)
    var position = target.offset().top
    $("html, body").animate({ scrollTop: position }, speed, "swing")
    return false
  })
})