focusで高機能ドロップダウンメニュー(どこをタッチしても閉じる、CSSのみで簡単に)

#この記事について
僕のサービスのテストユーザーから、「閉じるボタンでしかメニューが閉じられないのがストレス」と言われたので、どこを触っても閉じるドロップダウンメニューを作りました。
意外とCSSだけで簡単に作れました。

([スポーツチーム用練習日記アプリClubCloud](https://www.club-cloud.jp)の開発を行っています。β版公開中です(宣伝)。)
![dropdown_menu.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/371615/2bd68282-6841-aaf9-37f4-c95578294ea1.gif)


#通常のドロップダウンメニュー
ドロップダウンメニューの閉じ方は主に3つあります。

1. 開いたときのボタンをもう一度押す
2. 開いたメニューの中のリンクにとぶ
3. 開いたメニューの中の閉じるボタンを押す
![menu_close.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/371615/8d8c5afb-db30-a490-74de-5f0c4f271551.png)



1はメニューの表示/非表示と紐づいているcheckboxがoffになることで、
2はページ遷移によって(厳密には閉じていない)、
3は主にJavaScriptの操作で
メニューが閉じます。

しかし、1~3のように特定のボタンを押さないとメニューを閉じられないのは、ユーザーにとってストレスが大きいようです。

出来れば、画面の関係のない場所を触るだけでメニューが閉じて欲しい！
出来れば、1~3も併用したい！
出来れば、CSSだけで実装したい！

出来ます。めちゃ簡単に。
以下、実装ベースの話なのでボタンをラベルと言い換えています。

#focusを使う！
さて、何を使うかというと、focus要素を使います。
focus要素を使えば、他の場所を触ることで元のfocusが外れるので、それをメニューを閉じるためのトリガーに出来るからです。
つまり、
開くラベル:focus ~ メニュー 
というセレクタをうまく使います。

ざっくり説明します。
1. 最初、メニューは画面外に隠しておきます。
2.「開くラベル」をクリックすると「開くラベル」にfocusが当たります。
2. 同時に、メニューが出現します。
2. さらに同時に、focusされた「開くラベル」は画面外に固定され、「閉じるラベル」が同じ位置に出現します。
3. メニューは、「開くラベル」にfocusが当たっているときのみ出現するので、画面内のどこをクリックしてもメニューは閉じます。「開くラベル」は画面外にあるのでどこかクリックすれば必ず他の要素にfocusが移るからです。


#コード

```html
<label class="menu_label_open" tabindex="0">開く</label>
<label class="menu_label_close">閉じる</label>
<div class="menu">
    <ul>
        <li><a href="http://google.com">メニュー1</a></li>
        <li><a href="http://google.com">メニュー2</a></li>
        <li><a href="http://google.com">メニュー3</a></li>
        <li><span>キャンセル</span></li>
    </ul>
</div>
```

```css
label{
	cursor: pointer;
}
.menu{
	transform: translate3d(0,-200%,0);
	transition: .3s ease-in-out;
	-webkit-transition-delay: .1s;
	transition-delay: .1s;
}
.menu_label_open:focus ~ .menu{
	transform: translate3d(0,0,0);
}
.menu_label_open:focus ~ .menu_label_close{
	display: block;
}
.menu_label_open:focus{
	position: fixed;
	top:-100%;
}
.menu_label_close{
	display: none;
}
.menu ul{
	display: inline-block;
	width:100%;
	padding:0;
	list-style: none;
	border:1px solid black;
	background-color: #fff;
	text-align: center;
	cursor: pointer;
}
.menu ul li:hover{
	background-color: rgba(0,0,0,0.2);
}
.menu ul li:not(:last-child){
	border-bottom:0.5px solid black;
}
.menu ul li span,a{
	text-decoration: none;
	display: block;
	padding:20px 0;
	width:100%;	
}
```

このドロップダウンメニューのキモをいくつか書いておきます。

```html
<!--label要素にfocusを有効にするため-->
<label class="menu_label_open" tabindex="0">開く</label>
```

```css
/*メニューを画面外に隠しておく*/
.menu{
	transform: translate3d(0,-200%,0);
}
```

```css
/*メニューを表示(元の位置に戻す)*/
.menu_label_open:focus ~.menu{
	transform: translate3d(0,0,0);
}
```

```css
/*メニューが開いているときは「閉じるラベル」を表示*/
.menu_label_open:focus ~ .menu_label_close{
	display: block;
}
```


```css
/*メニューが開いているときは「開くラベル」を画面外に隠す(display:noneだとfocusが強制的に外れてしまう)*/
.menu_label_open:focus{
	position: fixed;
	top:-100%;
}
```


```css
/*メニューが閉じるのを少し待ってあげないと、リンクのクリック終了時にはリンクの位置がズレていてリンクを踏んだことにならない*/
.menu{
	-webkit-transition-delay: .3s;
	transition-delay: .3ms;
}
```




#開いたままにしたいときは
ちなみに、特定の要素(特にメニューの中の要素)を触ったときにはメニューは開いたままに、という場合は、下記のjqueryコードをHTMLに入れてください。
その要素を触ったときに瞬時に「開くラベル」にfocusを戻すので、メニューは閉じません(transition-delayが効いているのでメニューは少しも動かないで済みます)。

```html
<script>
$(document).on('click', '.hoge', function(){
    $('.menu_label_open').focus()
})
</script>
```

#まとめ
なかなかいい感じのドロップダウンメニューかなと思います。
checkboxを使うよりもHTMLはスッキリします。
閉じるボタンをずっと使い続けて、他の場所を触っても閉じることに気づくユーザーは少ないかもしれませんが…。
装飾は最低限にしてあるので、ラベルやメニューをいい感じにして使ってみてください！