3つのレイアウトで様々なブラウザ画面サイズに対応する、レスポンシブでタッチフレンドリーなRetinaディスプレイ対応のメニュー作成方法

R1 Retinaディスプレイ対応のレスポンシブなメニュー

この記事は、http://tympanus.net/codrops/の許可を得て、翻訳しています。一部変更して翻訳している部分もある場合があります。オリジナルの記事はここよりご覧いただけます。

今日は、ボーダーランズ(Borderlands)ゲームの武器メーカーMaliwanが打ち出す様々な色からインスピレーションを得た、多彩なRetina対応のレスポンシブなメニューを作成していきます。このメニューはブラウザのウィンドウサイズに合わせ、自動で次の3つの異なるレイアウトのうちの1つに変化します。「デスクトップ」向けの横1列表示バージョン、タブレット向けに最適化された縦2列表示バージョン、そして最後は、ナビゲーションを表示/非表示するためのメニューボタンを備える、より小さな画面のモバイル向けのバージョンです。メニューを完全Retina対応とするため、アイコンフォントを使います。アイコンフォントを使うと、リサイズ時にメニューのアイコンの品質が低下する(ボケて滲んで見えたりする)ことがなくなります。

R2 Retinaディスプレイ対応のレスポンシブなメニュー

個々のCSSプロパティをサポートするブラウザ上でのみ実現される効果もありますので、ご注意ください。


アイコンフォントを準備する

独自のアイコンフォントを作るというと少々面倒な感じがするかもしれませんが、IcoMoonなどのツールを使えば、要はアイコンを作ってそのツールにインポートするだけです。アイコンフォントは他のあらゆるフォントと同様に機能するため、色の変更やサイズの指定が簡単にでき、品質が低下することもありません。異なる画面の解像度に合わせてアイコンを用意する必要がなくなり、Retina対応デバイスに最適です。

まず最初に、メニュー用のアイコンを作る必要があります。私はイラストレータを使いますが、例えばInkscapeなどのベクタグラフィックスエディタであればどれでも構いません。アイコンをそれぞれ作成したら、それら一つ一つをSVGファイルに書き出します。アイコンをあらゆるブラウザで適切に機能させるためには、各アイコンごとに、すべてのラインを完全にオブジェクトに変換し、それらのオブジェクトをすべて統合して1つの大きな形にする必要があります。すべてをSVGファイルに書き出したら、それらのファイルをIcoMoonアプリケーションツールにインポートします。

R3 Retinaディスプレイ対応のレスポンシブなメニュー

IcoMoonから提供されている膨大なライブラリのアイコンを使って、作成したフォントの見栄えをさらに良くすることもできます。必要なすべてのアイコンが揃ったら、ページ下の「Font」ボタンをクリックし、詳細設定のページに移ります。このページでは、エンコーディングの設定を選ぶことができるほか、各アイコンに文字を付けるかどうかの選択や、Private Use Area(画面を見た第三者によるアイコンのアウトプットを禁止する)を使用するかどうかの選択ができます。私個人としては、デフォルト設定での使用をお勧めします。それで十分機能するからです。

R4 Retinaディスプレイ対応のレスポンシブなメニュー

「Download」をクリックすると、4つのフォーマット(SVG、EOT、TTF、WOFF)のファイルがまとめられたZipファイル1つと、CSSファイル、デモページが生成されます。

アイコンを使えるようにするため最初にしなくてはいけないのは、IcoMoonから提供されたCSSをコピーして、自分のCSSファイルのトップにコピーすることです。また、フォントフォルダをコピーするのも忘れないようにしましょう。「a little “hack” to make fonts look nicer on Chrome Windows」も併せて読まれることをお勧めします。

メニューのHTML

今回のナビゲーションのHTML記述は以下のような感じです。

<nav  id="menu" class="nav">
    <ul>
        <li>
            <a  href="#" title="">
                <span  class="icon"> <i aria-hidden="true"  class="icon-home"></i></span><span>Home</span>
            </a>
        </li>
        <li>
            <a href="#" title=""><span class="icon"> <i aria-hidden="true" class="icon-services"></i></span><span>Services</span></a>
        </li>
        <li>
            <a  href="#" title=""><span  class="icon"><i  aria-hidden="true" class="icon-portfolio"></i></span><span>Portfolio</span></a>
        </li>
        <li>
            <a  href="#" title=""><span  class="icon"><i  aria-hidden="true" class="icon-blog"></i></span><span>Blog</span></a>
        </li>
        <li>
            <a  href="#" title=""><span  class="icon"><i  aria-hidden="true" class="icon-team"></i></span><span>The  team</span></a>
        </li>
        <li>
            <a  href="#" title=""><span  class="icon"><i  aria-hidden="true" class="icon-contact"></i></span><span>Contact</span></a>
        </li>
    </ul>
</nav>

アイコンフォントを使うため、「icon-iconname」クラスをi要素(span要素でも同様に機能します)の中で用います。また、no-jsクラスをbodyに追加していますが、Modernizrを使った場合にはこれはjsとなりますので注意してください。これは、ユーザがJavaScriptを無効にしていた場合にメニューを開いたままにしておくためのものです。

CSSとJAVASCRIPT

ここでは、CSS3プロパティにベンダープレフィックスを付けませんが、ファイルの中にはベンダープレフィックスが付いたものがあることに注意してください。

すべての画面サイズに適用されるグローバルCSSは次のようになります。

/* Global CSS that are applied for all screen sizes */

.nav ul {
    max-width: 1240px;
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: 1.5em;
    font-weight: 300;
}

.nav li span {
    display: block;
}

.nav a {
    display: block;
    color: rgba(249, 249, 249, .9);
    text-decoration: none;
    transition: color .5s, background .5s, height .5s;
}

.nav i{
    /* Make the font smoother for Chrome */
    transform: translate3d(0, 0, 0);
}

/* Remove the blue Webkit background when element is tapped */

a, button {
    -webkit-tap-highlight-color: rgba(0,0,0,0);
}

最初に、ナビゲーション全体に対してちょっとしたトランジション効果を追加してみます。ホバーされているものを除き、その他のすべてのアイテムの不透明度を下げるというものです。

/* Hover effect for the whole navigation to make the hovered item stand out */

.no-touch .nav ul:hover a {
    color: rgba(249, 249, 249, .5);
}

.no-touch .nav ul:hover a:hover {
    color: rgba(249, 249, 249, 0.99);
}

次に、すべてのアイテムに対してちょっと素敵な感じの背景色を追加してみたいと思います。以下の記述では、リストアイテムを選択するのにnth-childを使っています。これで、好きなだけリストアイテムを追加でき、色指定のコードも追加した分だけ繰り返し実行されます。

.nav li:nth-child(6n+1) {
    background: rgb(208, 101, 3);
}

.nav li:nth-child(6n+2) {
    background: rgb(233, 147, 26);
}

.nav li:nth-child(6n+3) {
    background: rgb(22, 145, 190);
}

.nav li:nth-child(6n+4) {
    background: rgb(22, 107, 162);
}

.nav li:nth-child(6n+5) {
    background: rgb(27, 54, 71);
}

.nav li:nth-child(6n+6) {
    background: rgb(21, 40, 54);
}

Media QueryのMin-width属性を使うことで、800px(50em:bodyのフォントサイズが15pxで)よりも大きい画面を対象として、リストを横一列のナビゲーションに変化させることが可能になります。

@media (min-width: 50em) {

    /* Transforms the list into a horizontal navigation */
    .nav li {
        float: left;
        width: 16.66666666666667%;
        text-align: center;
        transition: border .5s;
    }

    .nav a {
        display: block;
        width: auto;
    }

継続してnth-childの選択テクニックを使い、メニューアイテム毎に異なる色を持つ4pxのボーダーを追加します。このテクニックを:hover疑似クラスに適用するだけでなく、タッチデバイスやキーボード・アクセスにも対応できるよう:focusと:active疑似クラスにも適用します。

/* hover, focused and active effects that add a little colored border to the different items */

.no-touch .nav li:nth-child(6n+1) a:hover,
.no-touch .nav li:nth-child(6n+1) a:active,
.no-touch .nav li:nth-child(6n+1) a:focus {
    border-bottom: 4px solid rgb(174, 78, 1);
}

.no-touch .nav li:nth-child(6n+2) a:hover,
.no-touch .nav li:nth-child(6n+2) a:active,
.no-touch .nav li:nth-child(6n+2) a:focus {
    border-bottom: 4px solid rgb(191, 117, 20);
}

.no-touch .nav li:nth-child(6n+3) a:hover,
.no-touch .nav li:nth-child(6n+3) a:active,
.no-touch .nav li:nth-child(6n+3) a:focus {
    border-bottom: 4px solid rgb(12, 110, 149);
}

.no-touch .nav li:nth-child(6n+4) a:hover,
.no-touch .nav li:nth-child(6n+4) a:active,
.no-touch .nav li:nth-child(6n+4) a:focus {
    border-bottom: 4px solid rgb(10, 75, 117);
}

.no-touch .nav li:nth-child(6n+5) a:hover,
.no-touch .nav li:nth-child(6n+5) a:active,
.no-touch .nav li:nth-child(6n+5) a:focus {
    border-bottom: 4px solid rgb(16, 34, 44);
}

.no-touch .nav li:nth-child(6n+6) a:hover,
.no-touch .nav li:nth-child(6n+6) a:active,
.no-touch .nav li:nth-child(6n+6) a:focus {
    border-bottom: 4px solid rgb(9, 18, 25);
}

次に、アイコンとテキストを配置します。


/* Placing the icon */

.icon {
    padding-top: 1.4em;
}

.icon + span {
    margin-top: 2.1em;
    transition: margin .5s;
}

ホバー時にアイテムの高さをアニメーションで変更するよう設定します。

/* Animating the height of the element*/
.nav a {
    height: 9em;
}

.no-touch .nav a:hover ,
.no-touch .nav a:active ,
.no-touch .nav a:focus {
    height: 10em;
}   

/* Making the text follow the height animation */
.no-touch .nav a:hover .icon + span {
    margin-top: 3.2em;
    transition: margin .5s;
}

次にアイコンの配置位置を指定し、かつアニメーションによる高さの変更にも対応できるようにします。

/* Positioning the icons and preparing for the animation*/
.nav i {
    position: relative;
    display: inline-block;
    margin: 0 auto;
    padding: 0.4em;
    border-radius: 50%;
    font-size: 1.8em;
    box-shadow: 0 0 0 0.8em transparent;
    background: rgba(255,255,255,0.1);
    transform: translate3d(0, 0, 0);
    transition: box-shadow .6s ease-in-out;
}

視覚的な効果をつけるためにボックスの影をアニメーションで表示し、その影のサイズを0.8emから0に変更、また色も透明からより不透明度の高い色へと変更します。ここで最初のmedia-queryを閉じます。

  /* Animate the box-shadow to create the effect */
    .no-touch .nav a:hover i,
    .no-touch .nav a:active i,
    .no-touch .nav a:focus i {
        box-shadow: 0 0 0px 0px rgba(255,255,255,0.2);
        transition: box-shadow .4s ease-in-out;
    }

}

ここで2つ目のmedia queryを使って、800px~980pxのサイズの画面向けに若干調整を施します。

@media (min-width: 50em) and (max-width: 61.250em) {

    /* Size and font adjustments to make it fit better */
    .nav ul {
        font-size: 1.2em;
    }

}

これで「デスクトップ」バージョンの設定が終わり(1024px以上の画面を持つタブレットが続々と出てくる今日、その辺について大きな疑問をお抱えだとは思いますが)、800pxより小さい画面(ここでは49.938emに相当)用に、media query のmax-width属性を使って「グローバル」CSSの処理をします。

/* The "tablet" and "mobile" version */

@media (max-width: 49.938em) {      

    /* Instead of adding a border, we transition the background color */
    .no-touch .nav ul li:nth-child(6n+1) a:hover,
    .no-touch .nav ul li:nth-child(6n+1) a:active,
    .no-touch .nav ul li:nth-child(6n+1) a:focus {
        background: rgb(227, 119, 20);
    }

    .no-touch .nav li:nth-child(6n+2) a:hover,
    .no-touch .nav li:nth-child(6n+2) a:active,
    .no-touch .nav li:nth-child(6n+2) a:focus {
        background: rgb(245, 160, 41);
    }

    .no-touch .nav li:nth-child(6n+3) a:hover,
    .no-touch .nav li:nth-child(6n+3) a:active,
    .no-touch .nav li:nth-child(6n+3) a:focus {
        background: rgb(44, 168, 219);
    }

    .no-touch .nav li:nth-child(6n+4) a:hover,
    .no-touch .nav li:nth-child(6n+4) a:active,
    .no-touch .nav li:nth-child(6n+4) a:focus {
        background: rgb(31, 120, 176);
    }

    .no-touch .nav li:nth-child(6n+5) a:hover,
    .no-touch .nav li:nth-child(6n+5) a:active,
    .no-touch .nav li:nth-child(6n+5) a:focus {
        background: rgb(39, 70, 90);
    }

    .no-touch .nav li:nth-child(6n+6) a:hover,
    .no-touch .nav li:nth-child(6n+6) a:active,
    .no-touch .nav li:nth-child(6n+6) a:focus {
        background: rgb(32, 54, 68);
    }

    .nav ul li {
        transition: background 0.5s;
    }   

}

520px(32.5em)~799px(49.938em)のサイズの画面には、2桁3行のレイアウトのメニューを表示するようにします。ユーザーが各アイテムを容易に「タップ」できるようにパディングを追加し、また、アイコンを左側、テキストを右側に表示させるようにします。

/* CSS for a 2x3 columns version */

@media (min-width: 32.5em) and (max-width: 49.938em) {

    /* Creating the 2 column layout using floating elements once again */
    .nav li {
        display: block;
        float: left;
        width: 50%;
    }

    /* Adding some padding to make the elements look nicer*/
    .nav a {
        padding: 0.8em;
    }

    /* Displaying the icons on the left, and the text on the right side using inline-block */
    .nav li span,
    .nav li span.icon {
        display: inline-block;
    }

    .nav li span.icon {
        width: 50%;
    }

    .nav li .icon + span {
        font-size: 1em;
    }

    .icon + span {
        position: relative;
        top: -0.2em;
    }

大きな画面用のアニメーションを小さい画面にフィットさせるのはかなり厄介なため、より簡単かつ慎重なやり方でボーダーをシンプルに表示させることにします。ここでmedia queryを閉じます。

 /* Adapting the icons to animate the size and border of the rounded background in a more discreet way */
    .nav li i {
        display: inline-block;
        padding: 8% 9%;
        border: 4px solid transparent;
        border-radius: 50%;
        font-size: 1.5em;
        background: rgba(255,255,255,0.1);
        transition: border .5s;
    }

    /* Transition effect on the border color */
    .no-touch .nav li:hover i,
    .no-touch .nav li:active i,
    .no-touch .nav li:focus i {
        border: 4px solid rgba(255,255,255,0.1);
    }

}

ここで再度、より小さな画面用にフォントサイズと幅を指定します。

 /* Adapting the font size and width for smaller screns*/
@media (min-width: 32.5em) and (max-width: 38.688em) {

    .nav li span.icon {
        width: 50%;
    }

    .nav li .icon + span {
        font-size: 0.9em;
    }
}

非常に小さな画面についてはナビゲーションを非表示にしておき、ユーザがクリックするとナビゲーションを表示する「メニュー」ボタンを表示させるようにします。JavaScriptのコードでこれを実現します。


//  The function to change the class
var changeClass = function (r,className1,className2) {
    var regex = new RegExp("(?:^|\\s+)" + className1 + "(?:\\s+|$)");
    if( regex.test(r.className) ) {
        r.className = r.className.replace(regex,' '+className2+' ');
    }
    else{
        r.className = r.className.replace(new RegExp("(?:^|\\s+)" + className2 + "(?:\\s+|$)"),' '+className1+' ');
    }
    return r.className;
};  

//  Creating our button for smaller screens
var menuElements = document.getElementById('menu');
menuElements.insertAdjacentHTML('afterBegin','<button type="button" id="menutoggle" class="navtoogle" aria-hidden="true"><i aria-hidden="true" class="icon-menu"> </i> Menu</button>');

//  Toggle the class on click to show / hide the menu
document.getElementById('menutoggle').onclick = function() {
    changeClass(this, 'navtoogle active', 'navtoogle');
}

// document click to hide the menu
// http://tympanus.net/codrops/2013/05/08/responsive-retina-ready-menu/comment-page-2/#comment-438918
document.onclick = function(e) {
    var mobileButton = document.getElementById('menutoggle'),
        buttonStyle =  mobileButton.currentStyle ? mobileButton.currentStyle.display : getComputedStyle(mobileButton, null).display;

    if(buttonStyle === 'block' && e.target !== mobileButton && new RegExp(' ' + 'active' + ' ').test(' ' + mobileButton.className + ' ')) {
        changeClass(mobileButton, 'navtoogle active', 'navtoogle');
    }
}

HTMLをすっきりさせるために「メニュー」ボタンを作り、JavaScriptを使ってDOMツリー内に挿入するという方法を取りました。changeClassファンクションにより、ユーザがボタンをクリックした際クラスがactive/クラスなしの間で切り替わります。

小さい画面向けバージョンのために必要なものがすべて揃ましたので、以降CSSでスタイルを指定していきます。以下は、メニューボタンのスタイルを指定する記述です。

/* Styling the toggle menu link and hiding it */
.nav .navtoogle{
    display: none;
    width: 100%;
    padding: 0.5em 0.5em 0.8em;
    font-family: 'Lato',Calibri,Arial,sans-serif;
    font-weight: normal;
    text-align: left;
    color: rgb(7, 16, 15);
    font-size: 1.2em;
    background: none;
    border: none;
    border-bottom: 4px solid rgb(221, 221, 221);
    cursor: pointer;
}

.navtoogle i{
    z-index:-1;
}

.icon-menu {
    position: relative;
    top: 3px;
    line-height: 0;
    font-size: 1.6em;
}

デフォルトでメニューボタンは非表示になっています。サイズが519px (32.438em)以下の画面向けには、このボタンを表示させることにします。

@media (max-width: 32.438em) {

    /* Unhiding the styled menu link */
    .nav .navtoogle{
        margin: 0;
        display: block;
    }

ボタンがクリックされると、ナビゲーションの高さをアニメーションで変更するように指定します。ナビゲーションを閉じるときのためには高さを0em、開くときのためには最大の高さとして30emを指定します。JavaScriptが有効になっていない場合には、ボタンが表示されないため、no-jsクラスを使って常にナビゲーションを表示させるようにします。

/* Animating the height of the navigation when the button is clicked */

/* If JavaScript is disabled, the menu stays open */
.no-js .nav ul {
    max-height: 30em;
    overflow: hidden;
}

JavaScriptが有効になっている際には、デフォルトでメニューを非表示にしておき、ユーザがボタンをクリックすると(activeクラスを取得し)表示されるようにします。

/* When JavaScript is enabled, we hide the menu */
.js .nav ul {
    max-height: 0em;
    overflow: hidden;
}

/* Displaying the menu when the user has clicked on the button */
.js .nav .active + ul {
    max-height: 30em;
    overflow: hidden;
    transition: max-height .4s;
}

より小さなサイズの画面には、アイコンを左側、テキストを右側に持つアイテムから成る縦1列のリスト形式でナビゲーションを表示させるようにします。

/* Adapting the layout of the menu for smaller screens: icon on the left and text on the right */

.nav li span {
    display: inline-block;
    height: 100%;
}

.nav a {
    padding: 0.5em;
}

.icon + span {
    margin-left: 1em;
    font-size: 0.8em;
}

素敵な色付きの8pxのボーダーも各アイテムの左側に追加します。

/* Adding a left border of 8 px with a different color for each menu item*/
.nav li:nth-child(6n+1) {
    border-left: 8px solid rgb(174, 78, 1);
}

.nav li:nth-child(6n+2) {
    border-left: 8px solid rgb(191, 117, 20);
}

.nav li:nth-child(6n+3) {
    border-left: 8px solid rgb(13, 111, 150);
}

.nav li:nth-child(6n+4) {
    border-left: 8px solid rgb(10, 75, 117);
}

.nav li:nth-child(6n+5) {
    border-left: 8px solid rgb(16, 34, 44);
}

.nav li:nth-child(6n+6) {
    border-left: 8px solid rgb(9, 18, 25);
}

デスクトップ上で小さな画面用のバージョンをテストした場合にはナビゲーションは美しく表示されますが、モバイルデバイス上だとアイテムをタップするのが難しいかもしれません。Modernizrを使えば、デバイスのタッチ機能の検出が可能です。デバイスにタッチ機能があれば、touchクラスがbodyに追加されます。このクラスを使うことで、ナビゲーションアイテムを少し拡大させることができ、ユーザがより簡単にタップできるようになるため、タッチデバイス上での体験レベルが向上します。ここで最後のmedia queryを閉じます。

 /* make the nav bigger on touch screens */
    .touch .nav a {
        padding: 0.8em;
    }
}

これでデスクトップ/タブレット/モバイルデバイス上で快適に機能するタッチフレンドリーなRetina対応のナビゲーションの完成です。皆さんに気に入っていただけると嬉しいです!