CSS3のアニメーションで動くドロワーをプラグイン無しで実装してみる

  • 更新日:
  • 公開日:

スマホサイトやアプリでよく見るUIのドロワーを実装したい。しかしjQueryのメソッドで動かせばスマホでの挙動が重いので、CSS3のアニメーションで実装してみます。ドロワーを実装する方法は他にも色々あると思いますが、そのうちの1つということで。

ドロワーをCSS3とjQueryで実装

今回実装するドロワーの仕様はこんな内容です。

  • ボタンのタップ、クリックでドロワー表示用クラスをjQueryで付ける
  • ドロワー表示時にドロワー以外の領域もしくはボタンをタップ、クリックしたら閉じる
  • IE9以下でのアニメーションは諦め、最低限の機能を提供するようにする

プラグインを使わずに、なるべくjQueryでの処理を増やさないというコンセプトなのでHTML構造はちょっと依存します。今回ご紹介する方法で実装したドロワーのデモはこちらで確認できます。

css-transition-drawer01
画像で説明するとこんな感じです。ボタンを押したらドロワーを表示します。

HTML部分

まず、HTMLはこのようにします。ドロワーはラッパー要素と分離します。

<body>
    <div id="wrapper">

        <div id="header">
            <div id="drawer-toggle">ドロワー表示用ボタン</div>
        </div>

        <!-- メイン部分。記事や画像などのコンテンツ -->

        <div class="overlay"></div>
    </div>

    <div id="drawer">
        <!-- ドロワー部分。メニューとか配置 -->
    </div>
</body>

クラスoverlayが付いたdivタグは、ドロワーが開いたときにラッパー部分を暗くするために使います。

CSS部分

CSSで重要な部分はtransitionによるアニメーション指定と、transformtranslateプロパティによる要素の移動です。

肝の部分のみ書くとこんな感じになります。まずラッパー要素。

#wrapper {
    //オーバーレイをabsolute指定するため必要
    position: relative;

    //アニメーションの指定
    -webkit-transition: all .2s;
       -moz-transition: all .2s;
         -o-transition: all .2s;
            transition: all .2s;
}

#wrapper.open {
    //クラス「open」が付いたら左に280px移動
    -webkit-transform: translate3d(-280px, 0, 0);
       -moz-transform: translate3d(-280px, 0, 0);
            transform: translate3d(-280px, 0, 0);
}

/* オーバーレイ表示をするためのCSS */
.open .overlay {
    position: absolute;
    top: 0;
    left: 0;
    //親要素の上に表示するためz-indexを指定
    z-index: 9999;

    //親要素一杯に広がるため縦横のサイズを100%に
    width: 100%;
    height: 100%;

    //暗くするため黒の透明色を指定
    background: rgba(0, 0, 0, .5);
}

次にドロワー要素です。

#drawer {
    position: fixed; //常に固定なのでfixed
    top: 0;
    right: -280px;  //初期状態では隠すため、横幅のサイズ分だけ画面外に移動する
    width: 280px;
    height: 100%;
    overflow-x: hidden;
    overflow-y: scroll;
    -webkit-overflow-scrolling: touch; //スマホでのスクロールに慣性を付ける(デバイス限定

    //アニメーション指定
    -webkit-transition: all .2s;
       -moz-transition: all .2s;
         -o-transition: all .2s;
            transition: all .2s;
}

#drawer.open {
    //クラス「open」が付いたら左へ280px移動(画面内へ移動し表示される)
    -webkit-transform: translate3d(-280px, 0, 0);
       -moz-transform: translate3d(-280px, 0, 0);
            transform: translate3d(-280px, 0, 0);
}

jQueryでボタンがタップされたら各々の要素にクラス「open」を付けること前提にCSSを書きました。

jQuery部分

jQueryでは、ボタンが押されたらopenクラスをドロワー要素とラッパー要素に追加する処理をします。

jQuery(document).ready(function($) {
        var $content = $('#wrapper'),
        $drawer = $('#drawer'),
        $button = $('#drawer-toggle'),
        isOpen = false;
    
    //ボタンをタップ、クリックした時
    $button.on('touchstart click', function () {
        if(isOpen) {
            $drawer.removeClass('open');
            $content.removeClass('open');
            isOpen = false;
        } else {
            $drawer.addClass('open');
            $content.addClass('open');
            isOpen = true;
        }
        return false; //親要素へのイベント伝播、aタグのURLクリックによる画面遷移を防ぐ
    });

    //コンテンツ部分をタップ、クリックした時
    $content.on('touchstart click', function (e) {
        e.stopPropagation(); //イベント伝播のみ阻止
        if(isOpen) {
            $drawer.removeClass('open');
            $content.removeClass('open');
            isOpen = false;
        }
    });
});

変数にあらかじめコンテンツ(#wrapper)、ドロワー(#drawer)、ボタン(#drawer-toggle)を格納して、それらにon()メソッドでスマホとPCでのタッチ、クリックイベントを割り当てています。

これでドロワー用クラスのON/OFFが切り替えられるようになり、実装完了です。

2014年10月5日追記:

コンテンツ部分をタップした時の処理も「return false;」と書いていましたが、これではURLが設定されたaタグをクリックした時にページ移動しなくなってしまうので、イベント伝播のみ阻止するstopPropagation()に変更しました(上記ソースコード23行目)。

 IE9以下はプログレッシブ・エンハンスメントで対応

IE9以下で通常の動作にならない点はtransitionが効かない他「translateで移動しない」、「オーバーレイのrgbaがIE8以下で効かない」の2つがあります。オーバーレイの半透明色とtransitionのアニメーションは使用上問題無いとして、ドロワーが表示されないのは痛いです。なので、IE9以下には別のCSSで対応してみます。

まず、Internet Explorer独自仕様の条件付きコメントを使い、IE9以下だった場合にhtmlタグに専用のクラスを付けます。

<!--[if lte IE 9]> <html class="lte-ie9"> <![endif]-->
<!--[if gt IE 9]><!--> <html> <!--<![endif]-->

これでクラス「lt-ie9」をCSSで使えばIE9以下でのみのスタイリングができるようになります。ラッパー要素とドロワー要素でこのクラスを使った結果がこちらです。

.lte-ie9 #wrapper.open {
    //rightでウインドウ右から280pxの位置に移動
    right: 280px;
}

.lte-ie9 #drawer.open {
    //隠れていたのでrightを使い元に位置に移動
    right: 0;
}

代替案としてそれぞれの要素をrightプロパティで移動させることにしました。これでアニメーションはしませんが、一先ずドロワーを開けるようになり最低限の機能が提供できるようになりました。

ドロワー実装時につまづいたこと

最後に、このドロワーを作った時につまづいたことを3つ挙げておきます。

要素の移動にはtranslate3dを使った

positionの位置を変更するだけでも可能ですが、アニメーションするときにラッパーとの動作が一致しない(隙間が一瞬できたりする)ことがありました。結果安定感のあるtranslate3dを使っています。

translateは3D版を使う

translateは2D版ならIE9でも動くのですが、スマホ(iphoneのsafariで確認)だと2D版はなぜかドロワー自体が消えたりしました…。fixedとの相性なのでしょうか。

IE9ではアニメーションが効きませんし、それならもう3D版に統一しようという結果に。

jQueryでのボタンクリックイベントが親要素にも伝播した

今回で言えばドロワーを表示するボタンはラッパー要素の中に入っているのですが、このラッパーにもイベントを割り当てていました(ドロワーを閉じる処理)。すると、子要素(ボタン)をクリックしたときに親要素(ラッパー)までもがイベント処理をし始めたんです。どうやらjQueryには親要素にもイベントが伝播する性質があるようで、これにハマりました。

親要素にイベントを伝播させないため、またaタグのURLクリックで画面遷移させないために「return false;」を追加しました。参考なった記事はこちらです。

子要素のイベント後、親要素のイベントも発生してしまう|teratail

1分でわかるreturn false; preventDefault(); stopPropagation() の違い | iwb.jp

書いた人

Symbol Mark

Ryoichi(しつ)

除菌ティッシュを買い込んで使いきれずによく乾かす人。

療養目的で退職し、どうやって生きていくか模索中。最近は勉強目的でLaravelやVue.js弄ったり、趣味で音で遊んでます。

※2019年10月16日現在ブログリニューアル中です。崩れなどが発生していたらすみません。

うぇぶ: @s_ryone