data-*(カスタムデータ属性)と、SCSSを組み合わせた、メニューバー作成

## はじめに

スマホ用のメニューバーの作成です。
ボタンを押すと、メニューが現れる、何処にでも良くありそうなものです。

それを勉強の為に、0からSCSSで作ってみたのですが、上手く行かない部分があったため、解決策をメモ書きです。

## メニューバー（改良前）

非常にテキトーなMenubarです。実際の動作はこちら。
「■」を押すと、メニューバーが表示されます。

https://jsfiddle.net/2xz876tk/

一応ソースコードも載せて置きます。

```html:html
<nav>
  <div class="menubar">
    <div>Logo</div>
    <label class="viewbutton" for="nav-visible">■</label>
  </div>
  <input class="open" type="checkbox" id="nav-visible">
  <ul>
    <li>Menu1</li>
    <li>Menu2</li>
    <li>Menu3</li>
    <li>Menu4</li>
  </ul>
</nav>
```

```scss:scss
nav {
  display: flex;
  flex-direction: column;
}

.menubar {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  height: 3rem;
  line-height: 3rem;
  background-color: aqua;
  .viewbutton {
    display: block;
    font-size: 2rem;
    height: 3rem;
    width: 3rem;
    text-align: center;
  }
}

.open {
  display: none;
}

ul {
  display: flex;
  flex-direction: column;
  $li_height: 2rem;
  background-color: red;
  margin: 0;
  height: 0;
  overflow-y: hidden;
  li {
    height: $li_height;
    line-height: $li_height;
    list-style: none;
  }
  .open:checked ~ & {
     height: $li_height * 4;
  }
}
```

このサンプルのポイントは、下記のコードの部分です。

```scss:問題点
  .open:checked ~ & {
     height: $li_height * 4;
  }
```

Menu部分（Menu1～Menu4が表示される部分）の高さは、ul要素で決めています。
これはli要素の個数を、scssに直接書き込まなければならないので、決して良いコードとは思えませんでした。
出来る限り、依存性は無くしたい所です。

## li要素でheightを決める

li要素でheightを決めれば良いのでは？、というツッコミがあるかと思います。
実際に作ってみました。transition部分の細かい動作（メニューの非表示時に、文字が潰れた様な表現になる）には気になりますが、これでも良い様には思います。

最大のメリットは、li要素を幾ら増やしても、scssの修正が不要である事だと思います。

https://jsfiddle.net/mgwhq23p/

```scss:ul部分
ul {
  display: flex;
  flex-direction: column;
  background-color: red;
  margin: 0;
  li {
    $li_height: 2rem;
    height: 0;
    line-height: 0;
    list-style: none;
    overflow-y: hidden;
    .open:checked ~ & {
      height: $li_height;
      line-height: $li_height;
    }
    transition: height, 0.5s;
  }
}
```

ですが、諸事情からどうしても親要素でheightを決めたかった、と言うことです。
メニューの非表示時に、文字が潰れた様な表現になるのも気になりますし……。

javascriptを使えば解決するのでしょうが、ここではそれ以外の解決策を探ってみる事にしました。

## 解決案

data-*(カスタムデータ属性）をul属性につける事で、liの個数をscssに伝える様にしました。
html上に個数を指定しないとならないのは面倒ですが、scssに直接個数を書くよりはマシな様な気がします。

https://jsfiddle.net/ea91o5fy/

```html:修正後html(一部抜粋)
  <ul data-items="5">
    <li>Menu1</li>
    <li>Menu2</li>
    <li>Menu3</li>
    <li>Menu4</li>
    <li>Menu5</li>
  </ul>
```

```scss:修正後scss(一部抜粋)
ul {
  display: flex;
  flex-direction: column;
  $li_height: 2rem;
  background-color: red;
  margin: 0;
  height: 0;
  overflow-y: hidden;
  li {
    height: $li_height;
    line-height: $li_height;
    list-style: none;
  }

  @for $i from 1 through 12 {
    .open:checked ~ &[data-items="#{$i}"] {
      height: $li_height * $i;
    }
  }

  transition: height 0.5s;
}
```

`@for`の部分がポイントです。
このコードによって、下記コードを出力します。

```scss:forによるコード出力
.open:checked ~ &[data-items="1"] {
  height: $li_height * 1;
}
.open:checked ~ &[data-items="2"] {
  height: $li_height * 2;
}
.open:checked ~ &[data-items="3"] {
  height: $li_height * 3;
}

// 以下省略
```

そして、data-itemsが一致した部分で、heightを決めています。
このコードでは、`data-items="5"`としたので、`height: $li_height * 5`となります。
要するに、5行分のheightを設定する、という事ですね。

## 最後に

素直にjavascript使おうよ、というツッコミはあると思います。
全く持ってその通りです。

ただ、出来る限りデザインはcssで完結させたいというのが、私の考えなんですよね……。
私はプロのhtmlデザイナーでも無ければ、Webプログラマーでも無い、タダの素人なので、この考えが正しいかどうかは解りませんが……。

今回は、scssでこの様なコードを書けば良いのでは、と言うことで記事を書いてみました。
もしかたら、他に良い方法があるのかも知れませんが、私の頭ではこれが精一杯です。

## おまけ

css4からは`:has`疑似要素と言うのがあるそうで、これを使えば、子要素の条件に基づいて、親要素を読む事が可能だとか。
これを使えば、data-itemsを使わなくともいけそうな気がするのですが、残念ながら、まだどのBrowserも未サポートとの事。