1年くらい前に 「CSS で作るスマートフォン向け片手操作メニュー」 って記事書いたんですが、同じようなのをまたやってみたので紹介。
Tumbler の Android アプリ (Tumblr for Android) の新規投稿の UI がカッコよかったんで、これを CSS と超簡単な JavaScript (jQuery 使用) で再現してみました。実際のサンプルは下記に。
現在最新版の Firefox、Safari、Chrome では動くと思います。あと手持ちの iOS 6 Safari では動作確認しました。他のスマートフォンとか知らない。IE? 一応、IE10 は問題なく動作します。IE9 だと動作はするけどアニメーションとかしないです。
元ネタを下記に貼っておきますね (画像は 「Tumblr Staff」 から引用)。
で、下が今回、CSS で作ったやつの動作。さすがにオリジナルと全く同じにはなっていませんが、雰囲気は似せられたかなと。
片手操作メニューの時と同様、今回も CSS で見た目と動きを作って、JavaScript でメニュー開閉のトリガーだけ操作するって感じ。
では早速、順を追って説明してみたいと思います。
1. メニュー部分の HTML
まずはメニュー部分の HTML 。ソース自体は簡単です。
<div id="menu"> <ul> <li id="video"><a href="./"><i class="icon"><span>Video</span></i></a></li> <li id="chat"><a href="./"><i class="icon"><span>Chat</span></i></a></li> <li id="link"><a href="./"><i class="icon"><span>Link</span></i></a></li> <li id="quote"><a href="./"><i class="icon"><span>Quote</span></i></a></li> <li id="photo"><a href="./"><i class="icon"><span>Photo</span></i></a></li> <li id="text"><a href="./"><i class="icon"><span>Text</span></i></a></li> <li id="openMenu"><i class="icon">Post</i></li> <li id="closeMenu"><i class="icon">Close Menu</i></li> </ul> </div>
HTML 上は、必要なボタンをすべて単純なリストとしてマークアップ。CSS を当てるための class 属性などを付けていきます。
2. CSS でメニュー部分の見た目を作る
まずは基本的なメニューの見た目を作っていきます。今回は、メニュー内のアイコンを画像で作ったので、下記のような画像を1つ用意し、あとは CSS で見た目を作ります。
#menu ul li { height: 44px; margin: 0; overflow: hidden; padding: 2px; } #menu .icon { background-image: url(img/btn_tumblr_ui.png); border-radius: 50%; display: inline-block; font-style: normal; line-height: 40px; height: 40px; position: relative; width: 40px; } #video .icon { background-position: 0 0; } #chat .icon { background-position: 0 -50px; } #link .icon { background-position: 0 -100px; } #quote .icon { background-position: 0 -150px; } #photo .icon { background-position: 0 -200px; } #text .icon { background-position: 0 -250px; } #openMenu .icon { background-position: 0 -300px; } #closeMenu .icon { background-position: 0 -350px; }
一部を抜粋していますが、i 要素に対して、用意した画像を背景に、CSS Sprite で見た目を作ります。ついでに border-radius: 50%;
で i 要素自体も円形に。これで丸いボタンのできあがり。
i 要素 (.icon) に対して、position: relative;
を指定しているのは、あとでラベルテキストを動かすための下準備です。
ここまでやると、下記のようになります (わかりやすいように背景を黒くしてあります)。
単純な縦並び、i 要素の中にラベルのテキストがありますので、当然ですがそれがアイコンにかぶった状態で表示されているのがわかります。
次に、メニュー全体の位置調整と、ラベルの位置調整をします。
#menu ul { list-style: none; margin: 0; padding: 0; position: absolute; right: 10px; bottom: 10px; width: 390px; } #menu ul li { height: 44px; margin: 0; overflow: hidden; padding: 2px; text-align: right; } #menu span { position: absolute; right: 50px; } #menu #openMenu .icon, #menu #closeMenu .icon { overflow: hidden; text-indent: 999em; }
li 要素に対して text-align: right;
を指定し、右揃えに。さらに、ul 要素を position: absolute;
して、右下に配置します。
さらに、i 要素内の span 要素に対して position: absolute;
及び right: 50px;
を指定して、ラベルを右側に移動させます。開くボタンと閉じるボタンに関しては text-indent: 999em;
して外にぶっ飛ばしとく。
ここまでくると、下記のような感じになります。
この状態はメニューが開いた状態ですので、投稿ボタン (下から 2番目) は消えていないといけません。ですので、閉じるボタン (一番下) と重ねちゃいます。
#menu #closeMenu { margin-top: -48px; }
これで、下記のようになりますね。
で、これだとまだ縦 1列になってしまっていますので、これをオリジナルのようにずらして配置します。
#menu ul li { position: absolute; width: 100%; } #menu ul li:nth-child(1) { bottom: 295px; right: 148px; } #menu ul li:nth-child(2) { bottom: 254px; right: 105px; } #menu ul li:nth-child(3) { bottom: 210px; right: 70px; } #menu ul li:nth-child(4) { bottom: 161px; right: 40px; } #menu ul li:nth-child(5) { bottom: 107px; right: 20px; } #menu ul li:nth-child(6) { bottom: 53px; right: 8px; }
:nth-child()
でやっていますが、position: absolute;
した li 要素を、それぞれの位置に配置します。
これで、下記のようになりますね。だいぶ近づいてきましたよ。
ここまできたら、一旦、CSS は置いておいて、JavaScript の方を追加します。
3. メニューを動作させるためのトリガーを jQuery で
JavaScript に関しては、一気に完成形を示して解説しちゃいます。
<!-- jQueryの読み込み --> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script> <!-- ↓class を付けたり消したりなど↓ --> <script> $(function(){ $("#closeMenu").hide(); $("#openMenu").click(function () { $("#menu, #menu ul").addClass("active"); $("#closeMenu").fadeIn("800"); }); $("#closeMenu").click(function () { $("#menu, #menu ul").removeClass("active"); $("#closeMenu").hide(); }); }); </script>
jQuery を使うので、まずはそれを読み込んで、その後でスクリプトを書きます。
まず、ページが開いたところで、$("#closeMenu").hide();
して、閉じるボタン (li#closeMenu) を非表示に。
続いて、次の行からですが、投稿ボタン (li#openMenu) をクリックしたときに、#menu と #menu ul に .addClass("active");
し、さらに $("#closeMenu").fadeIn("800");
で、閉じるボタンを表示 (フェードインエフェクトと合わせて) させます。
こうすることで、ページを開いた状態では投稿ボタンが表示されていて、それをクリックすると、HTML 上は上に重なっている 閉じるボタンがフェードインしてきて投稿ボタンが見えなくなります。
ちなみに、class="active"
を付けるのは、メニューを動かすだけなら #menu ul に対してだけでいいんですが、#menu にも付けているのは、背景がブラックアウトするエフェクトを付けるためです。
さらに、その逆も書いておく必要がありますが、$("#menu, #menu ul").removeClass("active");
で、閉じるボタン (li#closeMenu) をクリックしたときに、先ほど付けた .active を削除し、さらに $("#closeMenu").hide();
で、閉じるボタンも非表示にします。
JavaScript の方はこれだけですので、CSS の方に戻ります。
4. メニューの動きを作っていく
JavaScript でクリックに応じて class="active"
を付けることができるようになりましたが、この class 属性値はメニューを開いた状態の時につきます。
で、今のところできあがっているメニューの見た目は、メニューが開いた状態です。つまり、この状態が class="active"
が付いた状態の見た目ということになりますので、今まで書いてきたスタイルに .active というセレクタを追加して、class="active"
が付与されているときにこれらスタイルが適用されるようにします。
ラベルを移動させた下記の部分や、
#menu.active span { position: absolute; right: 50px; }
各アイコンの配置を指定した下記の部分などに .active というクラスセレクタを追加します。
#menu.active ul li { position: absolute; width: 100%; } #menu.active ul li:nth-child(1) { bottom: 295px; right: 148px; } #menu.active ul li:nth-child(2) { bottom: 254px; right: 105px; } #menu.active ul li:nth-child(3) { bottom: 210px; right: 70px; } #menu.active ul li:nth-child(4) { bottom: 161px; right: 40px; } #menu.active ul li:nth-child(5) { bottom: 107px; right: 20px; } #menu.active ul li:nth-child(6) { bottom: 53px; right: 8px; }
当然ですが、これによって、ページが読み込まれた初期状態では、下記のようにちょっぴり先祖返りした感じになっていますが、これは意図した通り。
投稿ボタンをクリックすると下記のように元に戻ります。
ここまでくれば、後は仕上げです。ページが読み込まれた初期状態では、すべてのアイコンが右下に重なっていればいいので、下記のように指定して、全部重ねちゃいます。
#menu ul li { position: absolute; right: 0; bottom: 0; }
このように重なりましたね。
これで、投稿ボタンをクリックすれば、開いた状態に切り替わるはずです。まだ動き的にはパッと一瞬で切り替わるだけですけども。
transition プロパティを使って動作をスムーズに
で、最後に動きをスムーズにしていきます。
まずは、transition プロパティを使って、メニューの開閉をスムーズに。
#menu ul li { position: absolute; right: 0; bottom: 0; -webkit-transition: right .2s, bottom .2s; transition: right .2s, bottom .2s; } #menu.active ul li { position: absolute; -webkit-transition: right .2s, bottom .2s; transition: right .2s, bottom .2s; width: 100%; }
閉じて戻るときもスムーズにしたいので、閉じている状態のスタイルにも、transition プロパティを追加します。
#menu ul li { position: absolute; right: 0; bottom: 0; -webkit-transition: right .2s, bottom .2s; transition: right .2s, bottom .2s; }
それから、投稿ボタンは、クリックした瞬間、回転しながら消えていってるんですよね。なのでそれを再現します。
#menu.active #openMenu .icon { -webkit-transform: rotate(50deg); transform: rotate(50deg); -webkit-transition: -webkit-transform .5s; transition: transform .5s; }
投稿ボタンをクリックしたとき (.active 時) に 50度回転させます。transition プロパティも併記。
ラベルの動きは CSS アニメーションで
メニュー展開後、ラベルがスライドして出てくる感じの動きは、CSS アニメーション を使用しています。
まずは、@keyframes 規則でキーフレームを定義しますが、下記のような感じ。
@-webkit-keyframes textSlidein { from { right: 0; opacity: 0; } to { right: 50px; opacity: 1; } } @keyframes textSlidein { from { right: 0; opacity: 0; } to { right: 50px; opacity: 1; } }
キーフレームを指定したら、アニメーションさせたい span 要素に対して、下記のように指定します。
#menu span { opacity: 0; } #menu.active span { -webkit-animation-name: textSlidein; animation-name: textSlidein; animation-duration: .4s; -webkit-animation-duration: .4s; animation-duration: .4s; -webkit-animation-timing-function: cubic-bezier(1, 0, 1, 0); animation-timing-function: cubic-bezier(1, 0, 1, 0); opacity: 1; position: absolute; right: 50px; }
これで、ラベルが右から左に、フェードインしながらスライドしますので、オリジナルのメニューの動きに近い動きになると思います。
animation-timing-function プロパティの指定で、まずボタンが展開されてから、少しだけおいてラベルがスライドしてくるっていう動きを再現しています。
今回は、ボタンが展開される動作に関して、position プロパティと、transition プロパティの組み合わせで再現しましたが、各ボタンごとに細かく CSS アニメーションで動きを指定してあげれば、もっとオリジナルの動作に近づけられるとは思うんですが、面倒なのでこの辺で。
ということで、CSS の完成版は下記の通り。上の解説では省いた、細かい指定も入っています。
#menu ul { list-style: none; margin: 0; padding: 0; position: absolute; right: 10px; bottom: 10px; width: 390px; } #menu ul li { height: 44px; margin: 0; overflow: hidden; padding: 2px; text-align: right; } /* ↓各メニューを装飾する基本的な指定↓ */ #menu li a { color: #fff; display: block; } #menu li a:hover { opacity: .9; } #menu .icon { background-image: url(img/btn_tumblr_ui.png); border-radius: 50%; display: inline-block; font-style: normal; line-height: 40px; height: 40px; position: relative; width: 40px; } #video .icon { background-position: 0 0; } #chat .icon { background-position: 0 -50px; } #link .icon { background-position: 0 -100px; } #quote .icon { background-position: 0 -150px; } #photo .icon { background-position: 0 -200px; } #text .icon { background-position: 0 -250px; } #openMenu .icon { background-position: 0 -300px; } #closeMenu .icon { background-position: 0 -350px; } #menu span { opacity: 0; } /* ↓メニューをクリックしてないときは全メニューが右下で重なってる状態↓ */ #menu ul li { position: absolute; right: 0; bottom: 0; -webkit-transition: right .2s, bottom .2s; transition: right .2s, bottom .2s; } /* ↓メニューをクリックするとメニューを展開します↓ */ #menu.active ul li { position: absolute; -webkit-transition: right .2s, bottom .2s; transition: right .2s, bottom .2s; width: 100%; } #menu.active ul li:nth-child(1) { bottom: 295px; right: 148px; } #menu.active ul li:nth-child(2) { bottom: 254px; right: 105px; } #menu.active ul li:nth-child(3) { bottom: 210px; right: 70px; } #menu.active ul li:nth-child(4) { bottom: 161px; right: 40px; } #menu.active ul li:nth-child(5) { bottom: 107px; right: 20px; } #menu.active ul li:nth-child(6) { bottom: 53px; right: 8px; } #menu.active .icon { box-shadow: 0 0 3px rgba(0, 0, 0, .4); } /* ↓メニュー展開時、テキストラベルの表示は CSS アニメーション↓ */ #menu.active span { -webkit-animation-name: textSlidein; animation-name: textSlidein; -webkit-animation-duration: .4s; animation-duration: .4s; -webkit-animation-timing-function: cubic-bezier(1, 0, 1, 0); animation-timing-function: cubic-bezier(1, 0, 1, 0); opacity: 1; position: absolute; right: 50px; } @-webkit-keyframes textSlidein { from { right: 0; opacity: 0; } to { right: 50px; opacity: 1; } } @keyframes textSlidein { from { right: 0; opacity: 0; } to { right: 50px; opacity: 1; } } /* ↓メニューを開く / 閉じるボタン周り↓ */ #menu #openMenu, #menu #closeMenu { cursor: pointer; } #menu #closeMenu { margin-top: -48px; } #menu #openMenu .icon { box-shadow: 0 0 3px rgba(0, 0, 0, .4); } #menu.active #openMenu .icon { -webkit-transform: rotate(50deg); transform: rotate(50deg); -webkit-transition: -webkit-transform .5s; transition: transform .5s; } #menu #openMenu .icon, #menu #closeMenu .icon { overflow: hidden; text-indent: 999em; }
実際に動作するサンプルは下記で確認できます。
上記サンプルのファイル一式は下記から落とせますので適当にいじってみてください。
もし、このメニューを実際の Web ページで使うなら、ボタンの配置は、position : fixed
でページ下部に固定とかしないとダメですね。あと、ページをブラックアウトさせるエフェクトは今回のサンプルでは決められた枠内だけが対象ですので簡単にやっていますが、実際には別の方法で実装しないといけないでしょうね。