CSS3アニメーションでつくるインターフェイス表現

第15回ハンバーガーメニューのアニメーション

今回のお題は、ハンバーガーメニューのアニメーションだ。ハンバーガーアイコンをクリックすると、メニューが右に向けて広がるサンプル1⁠。CSS Gooey Menuのデザインとアニメーションをもとに、コードは絞り込んでわかりやすく組み立て直した。技術的にむずかしいところは少なく、ただメニューの数だけ細かい設定が増える。なお、SVGを用いたフィルタは、今回のお題からは外した。

サンプル1 CSS3:Hamburger menu

アイコンWebフォントを使う

サンプル1のハンバーガーメニューには、文字は使われていない。替わりにアイコンがある。これらのアイコンにはWebフォントのFont Awesomeを用いた。たとえば、つぎのようなアイコンがWebフォントとして読み込んで表示できる。

  • bar-chart
  • plus
  • heart
  • envelope

Font Awesomeは、つぎのコードのとおりBootstrapCDNから読み込んだ。Font Awesomeのページで、⁠Font Awesome CSS」のフィールドの右ボタンを展開すると、⁠HTML」のコードが示されるので、それをコピー&ペーストすればよい図1⁠。integrityも含んだ<link>要素が加えられる。なお、いつものように<script>要素に-prefix-freeを読み込んで、ベンダープレフィックスは省いた(第1回のベンダープレフィックスと-prefix-freeの項参照⁠⁠。

<head>要素
<script src="lib/prefixfree.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
図1 BootstrapCDNのFont Awesomeのページ
図1 BootstrapCDNのFont Awesomeのページ

Font Awesomeのアイコンは、つぎのように<i>要素にclass属性で定める(⁠⁠Examples」Basic Icons参照⁠⁠。HTMLドキュメントにおけるハンバーガーメニューをつくるための<body>要素の記述は以下のコード1のとおりだ。メニューの開け閉じは<input>要素のチェックボックスtype属性"checkbox")で行う。もっとも、要素そのものはCSSで隠す。そのため、ハンバーガーボタンの<label>要素にfor属性で、チェックボックスのid属性("menu-open")を指定した。したがって、<label>属性のクリックでチェックボックスは切り替えられる。

<i class="fa fa-アイコン名"></i>

コード1 ハンバーガーメニューをつくる<body>要素の定め
<body>要素

<nav class="menu">
    <input type="checkbox" class="menu-open" name="menu-open" id="menu-open"/>
    <label class="menu-open-button" for="menu-open">
        <span class="hamburger hamburger-1"></span>
        <span class="hamburger hamburger-2"></span>
        <span class="hamburger hamburger-3"></span>
    </label>
    <a href="#" class="menu-item menu-item-1">
        <i class="fa fa-bar-chart"></i>
    </a>
    <a href="#" class="menu-item menu-item-2">
        <i class="fa fa-plus"></i>
    </a>
    <a href="#" class="menu-item menu-item-3">
        <i class="fa fa-heart"></i>
    </a>
    <a href="#" class="menu-item menu-item-4">
        <i class="fa fa-envelope"></i>
    </a>
</nav>

ハンバーガーメニューを開いたときの静的スタイル

ハンバーガーメニューを開いたときの静的なスタイルは、以下のコード2のCSSで定めた図2⁠。メニューclass属性"menu-item")とハンバーガーclass属性"menu-open-button")のボタンは、border-radiusプロパティで円形にして、それぞれ前掲コード1のとおりアイコンと3本線<span>要素)を加えている。開いたメニューの水平位置は、transformプロパティにtranslateX()関数で決めた。なお、メニューを閉じたとき各項目が後ろに隠れるよう、ハンバーガーボタンのz-indexプロパティを高めてある。

図2 ハンバーガーメニューが開いたときのスタイル
図2 ハンバーガーメニューが開いたときのスタイル
コード2 ハンバーガーメニューを開いたときの静的なスタイル
.menu-item, .menu-open-button {
    background: darkcyan;
    border-radius: 100%;
    width: 60px;
    height: 60px;
    position: absolute;
    color: white;
    text-align: center;
    line-height: 60px;
}
.menu-open {
    display: none;
}
.hamburger {
    width: 25px;
    height: 3px;
    background: white;
    display: block;
    position: absolute;
    top: 50%;
    left: 50%;
    margin-left: -12.5px;
    margin-top: -1.5px;
}
.hamburger-1 {
    transform: translateY(-8px);
}
.hamburger-3 {
    transform: translateY(8px);
}
.menu {
    position: absolute;
    margin: 10px;
    box-sizing: border-box;
    font-size: 20px;
    text-align: left;
}
.menu-item-1 {
    transform: translateX(80px);
}
.menu-item-2 {
    transform: translateX(160px);
}
.menu-item-3 {
    transform: translateX(240px);
}
.menu-item-4 {
    transform: translateX(320px);
}
.menu-open-button {
    z-index: 2;
    cursor: pointer;
}

ハンバーガーボタンのアニメーション

ハンバーガーボタンclass属性"menu-open-button")のアニメーションからつくろう。チェックボックスclass属性"menu-open")がチェックされたら:checked擬似クラス⁠、3本線の上下はtransformプロパティにrotate()関数で±45度回し、真ん中はscaleX()関数でつぶした。これで、閉じるボタンの×のかたちができあがる図3⁠。

.menu-open:checked + .menu-open-button .hamburger-1 {
    transform: rotate(45deg);
}
.menu-open:checked + .menu-open-button .hamburger-2 {
    transform: scaleX(0);
}
.menu-open:checked + .menu-open-button .hamburger-3 {
    transform: rotate(-45deg);
}
図3 閉じるボタン
図3 閉じるボタン

それでは、transitionプロパティで滑らかなアニメーションにしよう。つぎのように、アニメーションの時間transition-durationプロパティ)やタイミング関数transition-timing-functionプロパティ)の値の変え方を、細かく調整した。ハンバーガーボタンの大きさもtransformプロパティで、マウスポインタを重ねたときや、クリックしたときとでscale()関数により変えている。

.menu-item, .menu-open-button {

    transition: ease-out 200ms;
}

.hamburger {

    transition: 200ms;
}

.menu-open-button {

    transform: scale(1.1, 1.1);

    transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
    transition-duration: 400ms;
}
.menu-open-button:hover {
    transform: scale(1.2, 1.2);
}
.menu-open:checked + .menu-open-button {
    transform: scale(0.8, 0.8);
    transition-timing-function: linear;
    transition-duration: 200ms;
}

cubic-bezier()関数で定めるタイミング関数の値の変わり方は、Cubic Bezier Generatorで確かめられる図4⁠。ここまで書いたCSSを、以下のコード3にまとめた。

図4 cubic-bezier()関数が定める値の変わり方
図4 cubic-bezier()関数が定める値の変わり方
コード3 ハンバーガーボタンのアニメーションを加えたメニュー
.menu-item, .menu-open-button {
    background: darkcyan;
    border-radius: 100%;
    width: 60px;
    height: 60px;
    position: absolute;
    color: white;
    text-align: center;
    line-height: 60px;
    transition: ease-out 200ms;
}
.menu-open {
    display: none;
}
.hamburger {
    width: 25px;
    height: 3px;
    background: white;
    display: block;
    position: absolute;
    top: 50%;
    left: 50%;
    margin-left: -12.5px;
    margin-top: -1.5px;
    transition: 200ms;
}
.hamburger-1 {
    transform: translateY(-8px);
}
.hamburger-3 {
    transform: translateY(8px);
}
.menu-open:checked + .menu-open-button .hamburger-1 {
    transform: rotate(45deg);
}
.menu-open:checked + .menu-open-button .hamburger-2 {
    transform: scaleX(0);
}
.menu-open:checked + .menu-open-button .hamburger-3 {
    transform: rotate(-45deg);
}
.menu {
    position: absolute;
    margin: 10px;
    box-sizing: border-box;
    font-size: 20px;
    text-align: left;
}
.menu-item-1 {
    transform: translateX(80px);
}
.menu-item-2 {
    transform: translateX(160px);
}
.menu-item-3 {
    transform: translateX(240px);
}
.menu-item-4 {
    transform: translateX(320px);
}
.menu-open-button {
    z-index: 2;
    transform: scale(1.1, 1.1);
    cursor: pointer;
    transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
    transition-duration: 400ms;
}
.menu-open-button:hover {
    transform: scale(1.2, 1.2);
}
.menu-open:checked + .menu-open-button {
    transform: scale(0.8, 0.8);
    transition-timing-function: linear;
    transition-duration: 200ms;
}

メニューの開け閉じのアニメーション

仕上げはメニューを開け閉じするアニメーションだ。はじめはすべての項目を、ハンバーガーボタンの後ろに隠しておく。そして、ボタンがクリックされたら:checked擬似クラス⁠⁠、つぎのようにそれぞれの定位置に動かせばよい。メニューが開くときのアニメーションの時間transition-durationプロパティ)は、動く距離に応じて変えた。また、メニュー項目にマウスポインタを重ねたとき:hover擬似クラス)の色も変えている図5⁠。これでできあがったので、以下のコード3にまとめた。

.menu-item:hover {
    background: turquoise;
    color: darkcyan;
}
.menu-item-1 {
    /* transform: translateX(80px); */
    transition-duration: 180ms;
}
.menu-item-2 {
    /* transform: translateX(160px); */
    transition-duration: 180ms;
}
.menu-item-3 {
    /* transform: translateX(240px); */
    transition-duration: 180ms;
}
.menu-item-4 {
    /* transform: translateX(320px); */
    transition-duration: 180ms;
}

.menu-open:checked ~ .menu-item {
    transition-timing-function: cubic-bezier(0.165, 0.84, 0.44, 1);
}
.menu-open:checked ~ .menu-item-1 {
    transform: translateX(80px);
    transition-duration: 190ms;
}
.menu-open:checked ~ .menu-item-2 {
    transform: translateX(160px);
    transition-duration: 290ms;
}
.menu-open:checked ~ .menu-item-3 {
    transform: translateX(240px);
    transition-duration: 390ms;
}
.menu-open:checked ~ .menu-item-4 {
    transform: translateX(320px);
    transition-duration: 490ms;
}
図5 メニューにマウスポインタを重ねると色が変わる
図5 メニューにマウスポインタを重ねると色が変わる
コード4 ハンバーガーメニューのアニメーション
.menu-item, .menu-open-button {
    background: darkcyan;
    border-radius: 100%;
    width: 60px;
    height: 60px;
    position: absolute;
    color: white;
    text-align: center;
    line-height: 60px;
    transition: ease-out 200ms;
}
.menu-open {
    display: none;
}
.hamburger {
    width: 25px;
    height: 3px;
    background: white;
    display: block;
    position: absolute;
    top: 50%;
    left: 50%;
    margin-left: -12.5px;
    margin-top: -1.5px;
    transition: 200ms;
}
.hamburger-1 {
    transform: translateY(-8px);
}
.hamburger-3 {
    transform: translateY(8px);
}
.menu-open:checked + .menu-open-button .hamburger-1 {
    transform: rotate(45deg);
}
.menu-open:checked + .menu-open-button .hamburger-2 {
    transform: scaleX(0);
}
.menu-open:checked + .menu-open-button .hamburger-3 {
    transform: rotate(-45deg);
}
.menu {
    position: absolute;
    margin: 10px;
    box-sizing: border-box;
    font-size: 20px;
    text-align: left;
}
.menu-item-1 {
    transform: translateX(80px);
}
.menu-item-2 {
    transform: translateX(160px);
}
.menu-item-3 {
    transform: translateX(240px);
}
.menu-item-4 {
    transform: translateX(320px);
}
.menu-open-button {
    z-index: 2;
    transform: scale(1.1, 1.1);
    cursor: pointer;
    transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
    transition-duration: 400ms;
}
.menu-open-button:hover {
    transform: scale(1.2, 1.2);
}
.menu-open:checked + .menu-open-button {
    transform: scale(0.8, 0.8);
    transition-timing-function: linear;
    transition-duration: 200ms;
}

おすすめ記事

記事・ニュース一覧