モバイル用に:targetや:checkedでつくるPure HTML5/CSS3なアコーディオンについて(追記 :checked版でAndroid4.x対応の改良コード)

Pure HTML5/CSS3のロマン

ただしモバイルデバイスに限る。

しばしば話題にあがるHTML5/CSS3による気合戦法のアコーディオン(ないしクリッカブルなパーツ)を、モバイルデバイスでの動作をiOSおよびAndroidを中心に記述・検証してみました。

今のところメジャーなのは、:target戦法と:checked戦法だと思うので、それぞれ試しています。これらの擬似クラスを使って、状態制御を行いつつセレクタで表示状態のスタイルを適用します。

:target戦法

:target擬似クラス+アンカーリンクで状態を制御しながら、子要素としてコンテンツを表示させる方法。わりと手軽に使えるので、使い所さえ選べば良いのかも。

サンプル

コード全体

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
.content {
    height: 0;
    overflow: hidden;
    -webkit-transition: height 0.3s ease-in-out;
    transition: height 0.3s ease-in-out;
}
.pane:target .content {
    height: 50px;
}
</style>
</head>
<body>
<div id="pane1" class="pane">
    <a href="#pane1">Pane1</a>
    <div class="content">
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
</div>
<div id="pane2" class="pane">
    <a href="#pane2">Pane2</a>
    <div class="content">
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
</div>
<div id="pane3" class="pane">
    <a href="#pane3">Pane3</a>
    <div class="content">
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
</div>
</body>
</html>

欠点

アンカーリンクを使っているため、アンカーの表示対象が画面内におさまっていないとページ内リンクとしてスクロールが発生します。スマートフォンのような限定的な解像度でスクロールが発生すると表示がトビます。さらに、transitionを使おうものなら、transitionEnd前にスクロールが発生してしまうためスクロール位置もひどいです。

実用性は高くありません。これなら素直にJSでクラスのadd/removeでトグルしたほうが良いでしょう。もちろんアニメーション部分は、使えるブラウザならCSSで表現すれば良いと思いますが。

:checked戦法

:checkd擬似クラスで状態を制御して、間接セレクタを利用してbody部分の表示・非表示を制御します。ブラウザの実装さえ追いつけば、わりと有力なのでは。iOS用にlabel要素のポインターイベントをonclick属性で励起させています。めんどい。

サンプル

コード全体

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
.body {
    height: 0;
    overflow: hidden;
    -webkit-transition: height 0.3s ease-in-out;
    transition: height 0.3s ease-in-out;
}
[type="radio"] {
    display :none;
}
#pane1:checked ~ #body1,
#pane2:checked ~ #body2,
#pane3:checked ~ #body3 {
    height: 50px;
}
​</style>
</head>
<body>
<input id="pane1" type="radio" name="accordion" value="1" />
<label id="label1" for="pane1" onclick="">Pane1</label>
<div id="body1" class="body">
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>

<input id="pane2" type="radio" name="accordion" value="2" />
<label id="label2" for="pane2" onclick="">Pane2</label>
<div id="body2" class="body">
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>

<input id="pane3" type="radio" name="accordion" value="3" />
<label id="label3" for="pane3" onclick="">Pane3</label>
<div id="body3" class="body">
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
​</body>
</html>

欠点

:checkedのpseudoは、モダンブラウザであれば対応しているはずですが、IE6-8やAndroid4.x未満で非対応です。挙動は悪くないのですが、ブラウザ要件がデスクトップ・モバイル共に厳しめなのが実用性を乏しくしています。

と言ってたら更に、Android 4.0.3のエミュレータで見てみたら:checkedと間接・隣接セレクタの組み合わせに問題(参考)がありました。

冗長になるので上のサンプルには反映させませんでしたが、次項のようなコードであれば上記の問題を回避して、iOSと同様の動作が得られます。事実上、iOS5.xとAndroid4.xに対応した:checked戦法の改良版です。

:checked戦法の改良版(Android 4.x対応)

:checked + div { font-size: inehrit; }あたりがおまじないです。これで隣接div内のスタイル描画更新が発生するので、中のlabel要素やコンテンツ部分のdiv要素にもスタイル更新が届きます。

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
.body {
    height: 0;
    overflow: hidden;
    -webkit-transition: height 0.3s ease-in-out;
    transition: height 0.3s ease-in-out;
}
[type="radio"] {
    display :none;
}
:checked + div {
    font-size: inherit;
}
#pane1:checked ~ div #body1,
#pane2:checked ~ div #body2,
#pane3:checked ~ div #body3 {
    height: 50px;
}
​</style>
</head>
<body>
<input id="pane1" type="radio" name="accordion" value="1" />
<div>
    <label id="label1" for="pane1" onclick="">Pane1</label>
    <div id="body1" class="body">
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
</div>

<input id="pane2" type="radio" name="accordion" value="2" />
<div>
    <label id="label2" for="pane2" onclick="">Pane2</label>
    <div id="body2" class="body">
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
</div>
<input id="pane3" type="radio" name="accordion" value="3" />
<div>
    <label id="label3" for="pane3" onclick="">Pane3</label>
    <div id="body3" class="body">
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
</div>
​</body>
</html>

参考