スマホナビゲーションの定番、ハンバーガーメニューを
Vue.js
で作ってみたメモ。
Vue.js
で作る時は、template
の中に3つイベントを書きます。
クリックした時、マウスが乗った時、マウスが離れた時です。
状態をdata属性
に保存しておかないといけないので、
data-state
とdata-mouse
を用意しています。
state
はメニューを開いているか、閉じているか?を記録します。
mouse
は、マウスが乗ったか、離れたか?を記録します。
<template>
<div id="app">
<header class="header">
<div
class="menu"
data-state="hoge"
data-mouse="fuga"
@click="click"
@mouseenter="enter"
@mouseleave="leave"
>
<div class="line line-1"></div>
<div class="line line-2"></div>
<div class="line line-3"></div>
<div class="line-close line-close-1"></div>
<div class="line-close line-close-2"></div>
</div>
</header>
</div>
</template>
次に、Javascript
を書きます。クリックしたらstate
にbatsu
を入れます。
もしstate
がbatsu
だったらhamburger
を入れます。
マウスが乗ったらmouse
にenter
、離れたらleave
を入れます。
<script>
export default {
name: 'App',
methods:{
click(e){
if(e.target.dataset.state !== 'batsu'){
e.target.dataset.state = 'batsu';
}else{
e.target.dataset.state = 'hamburger';
}
},
enter(e){
e.target.dataset.mouse = 'enter';
},
leave(e){
e.target.dataset.mouse = 'leave';
},
}
}
</script>
コレでイベントの制御は完了です。書き方さえ覚えてしまえばjQuery
並に…
いや、$('').toggleClass()
の方がカンタンですけどね。。
DOM操作に関してはjQuery
が神すぎるって事で。。
##スタイルを作っていく。
動きがあるので、CSSが面倒ですよね。いくつかに分割して書きます。
まずは基本セットから。
.menu {
position: absolute;
top: 18px;
right: 18px;
width: 44px;
height: 40px;
cursor: pointer;
touch-action: manipulation;
.line {
position: absolute;
left: 12px;
width: 20px;
height: 3px;
overflow: hidden;
pointer-events: none;
&.line-1 { top: 12px }
&.line-2 { top: 19px }
&.line-3 { top: 26px }
&::before,
&::after {
content: "";
display: block;
position: absolute;
top: 0;
left: 0;
width: 20px;
height: 3px;
background-color: #222;
transition-property: transform;
transition-duration: .15s;
transition-timing-function: cubic-bezier(0.5, 0.1, 0.1, 1)
}
&::after {
transform: translate3d(-100%, 0, 0);
}
}
}
ちょっと見慣れない奴は解説します。
まずtouch-action: manipulation;
ですが、
これは標準的なタップ以外を無視するヤツです。ダブルタップとか(?)
次にtransition-timing-function: cubic-bezier(0.5, 0.1, 0.1, 1)
ですが、これはアニメーションのイージングをどうするかってヤツです。
「ベジェ曲線」の「アンカーポイント」の位置を示しています。
transform: translate3d(-100%, 0, 0);
は、
これは昔text-indent:-100%
っていうCSSの使い方があったと思うんですが、
今はこの書き方をしてる、って感じです。左にしまっておく感じです。
##アニメーション
data属性にenter
が入った時のアニメーションです。
最初に表示されているハンバーガーは疑似要素のbefore
ですね。
それを右に100%(消えるまで)送ってしまい、
次に疑似要素after
を左からbefore
が元々あった位置に持ってきます。
.menu[data-mouse="enter"] .line::before {
transform: translate3d(100%, 0, 0);
}
.menu[data-mouse="enter"] .line::after {
transform: translate3d(0, 0, 0);
}
このままだと「≡が右に消えて、左から出てきましたね。」
というだけなんですが、3本の線それぞれにdelayを付けてあげることで
なんか、「斜めに消えてる」っていうより、「キラーンと光ってる」みたいな感じに見えます。
(アニGIFだと表現できてないですが、実際はマウスが乗った時にも動きます。)
コレ最初に考えた人天才ですね。。
.menu[data-mouse="enter"] .line-1::before{
transition-delay: 0s
}
.menu[data-mouse="enter"] .line-1::after{
transition-delay: 0.15s;
}
.menu[data-mouse="enter"] .line-2::before{
transition-delay: 0.05s
}
.menu[data-mouse="enter"] .line-2::after{
transition-delay: 0.2s;
}
.menu[data-mouse="enter"] .line-3::before{
transition-delay: 0.1s;
}
.menu[data-mouse="enter"] .line-3::after{
transition-delay: 0.25s;
}
ちなみに「×印」になる場合、この3本は右に消えます。
.menu[data-state="batsu"] .line::before,
.menu[data-state="batsu"] .line::after {
transform: translate3d(100%, 0, 0)
}
##バツ印を作る。
つぎはバツ印です。バツ印はstate
にbatsu
が入った時のアニメーションです。
基本セットはこの2本です。
.menu .line-close {
position: absolute;
top: 50%;
left: 50%;
width: 22px;
height: 2px;
background-color: #111;
overflow: hidden;
transition-property: opacity, transform;
transition-duration: .4s;
transition-timing-function: cubic-bezier(0.5, 0.1, 0.1, 1);
pointer-events: none
}
.menu .line-close-1 {
opacity: 0;
transform: translate(-50%, -50%) rotate(-80deg)
}
.menu .line-close-2 {
opacity: 0;
transform: translate(-50%, -50%) rotate(-80deg)
}
透明だから見えないんですけど、こんな感じで疑似要素が2本重なっています。
それを−50%
して真ん中に持ってきます。
さらに80度回転した状態にして透明にしておきます。
それを表示する時は角度を「45度」、「−45度」にアニメーションします。
.menu[data-state="batsu"] .line-close-1 {
opacity: 1;
transform: translate(-50%, -50%) rotate(45deg);
transition-delay: 0.3s
}
.menu[data-state="batsu"] .line-close-2 {
opacity: 1;
transform: translate(-50%, -50%) rotate(-45deg);
transition-delay: 0.3s
}
そうすると、クルリンと折りたたみ椅子が開くようなバツマークになります。
上から下までつなげたCSSです。
<style lang="scss" scoped>
.menu {
position: absolute;
top: 18px;
right: 18px;
width: 44px;
height: 40px;
cursor: pointer;
touch-action: manipulation;
.line {
position: absolute;
left: 12px;
width: 20px;
height: 3px;
overflow: hidden;
pointer-events: none;
&.line-1 { top: 12px }
&.line-2 { top: 19px }
&.line-3 { top: 26px }
&::before,
&::after {
content: "";
display: block;
position: absolute;
top: 0;
left: 0;
width: 20px;
height: 3px;
background-color: #222;
transition-property: transform;
transition-duration: .15s;
transition-timing-function: cubic-bezier(0.5, 0.1, 0.1, 1)
}
&::after {
transform: translate3d(-100%, 0, 0);
}
}
}
.menu[data-mouse="enter"] .line::before {
transform: translate3d(100%, 0, 0);
}
.menu[data-mouse="enter"] .line::after {
transform: translate3d(0, 0, 0);
}
.menu[data-state="batsu"] .line::before,
.menu[data-state="batsu"] .line::after {
transform: translate3d(100%, 0, 0)
}
.menu .line-close {
position: absolute;
top: 50%;
left: 50%;
width: 22px;
height: 2px;
background-color: #111;
overflow: hidden;
transition-property: opacity, transform;
transition-duration: .4s;
transition-timing-function: cubic-bezier(0.5, 0.1, 0.1, 1);
pointer-events: none
}
.menu .line-close-1 {
opacity: 0;
transform: translate(-50%, -50%) rotate(-80deg)
}
.menu .line-close-2 {
opacity: 0;
transform: translate(-50%, -50%) rotate(-80deg)
}
.menu[data-state="batsu"] .line-close-1 {
opacity: 1;
transform: translate(-50%, -50%) rotate(45deg);
transition-delay: 0.3s
}
.menu[data-state="batsu"] .line-close-2 {
opacity: 1;
transform: translate(-50%, -50%) rotate(-45deg);
transition-delay: 0.3s
}
.menu[data-mouse="enter"] .line-1::before,
.menu[data-state="batsu"] .line-1::before {
transition-delay: 0s
}
.menu[data-mouse="enter"] .line-1::after,
.menu[data-state="batsu"] .line-1::after {
transition-delay: 0.15s;
}
.menu[data-mouse="enter"] .line-2::before,
.menu[data-state="batsu"] .line-2::before {
transition-delay: 0.05s
}
.menu[data-mouse="enter"] .line-2::after,
.menu[data-state="batsu"] .line-2::after {
transition-delay: 0.2s;
}
.menu[data-mouse="enter"] .line-3::before,
.menu[data-state="batsu"] .line-3::before {
transition-delay: 0.1s;
}
.menu[data-mouse="enter"] .line-3::after,
.menu[data-state="batsu"] .line-3::after {
transition-delay: 0.25s;
}
.menu[data-state="hamburger"] .line::before,
.menu[data-state="hamburger"] .line::after {
transform: translate3d(0, 0, 0)
}
.menu[data-state="hamburger"] .line-1::before,
.menu[data-state="hamburger"] .line-1::after {
transition-delay: 0.3s
}
.menu[data-state="hamburger"] .line-2::before,
.menu[data-state="hamburger"] .line-2::after {
transition-delay: 0.35s
}
.menu[data-state="hamburger"] .line-3::before,
.menu[data-state="hamburger"] .line-3::after {
transition-delay: 0.4s
}
</style>
さわってて面白いアイコンだなと。。以上です。
##参考資料
https://jp.vuejs.org/v2/guide/transitions.html
https://jp.vuejs.org/v2/guide/events.html
http://www.htmq.com/css3/transition-timing-function.shtml
https://developer.mozilla.org/ja/docs/Web/CSS/transform-function/translate3d