CSSのdisplayプロパティを使ったレスポンシブでよくある間違い

2018年2月9日

CSSのdisplayプロパティを使うとHTML要素の表示と非表示を切り替えることができますが、HTMLの基本を知らないとページ内リンクの誤動作を引き起こしかねません。

レスポンシブなコーディングでよく使われる方法

レスポンシブなサイトをコーディングするとき、楽をするために次のようなアプローチが使われます。

まず、画面幅が何ピクセル以下の場合にスマホ向けのCSSを適用するかを決めます。
たとえば991pxまではスマホ向けのCSS、992px以上はPC向けのCSSを適用するとしましょう。

そして、displayプロパティを使って次のようなclassを定義しておきます。

.sp-only { display: block!important; }
.pc-only { display: none!important; }
@media  (min-width: 992px) {
    .sp-only { display: none!important; }
    .pc-only { display: block!important; }
}

こうすると、sp-onlyというclassを割り当てた要素は、991px以下のデバイスでは表示され、992px以上のデバイスでは非表示になります。そして、pc-onlyというclassを割り当てた要素は、991px以下のデバイスでは非表示になり、992px以上のデバイスでは表示されます。

class名 991px以下のデバイス 992px以上のデバイス
sp-only 表示 非表示
pc-only 非表示 表示

要するに、表示と非表示が自動的に切り替わってくれるスイッチの役目をするclassを用意するわけですね。

すると、HTMLは次のように書き分ければ、スマホ用とPC用の表示を併記することができます。

<section id="blog" class="sp-only">
スマホ向けのブログ記事一覧が入る
</section>
<section id="blog" class="pc-only">
PC向けのブログ記事一覧が入る
</section>

この方法の間違いはココ

CSSの都合だけを考えると正しいのですが、HTMLの文法として間違っています。HTMLには、id属性は同一ページ内で重複してはいけないという大原則があります。上記のアプローチは、この大原則に反しています。

ブラウザの画面上では2つのsection要素のどちらか一方しか表示されませんが、HTMLには同じid属性を持つsection要素が2つとも出力されてしまうからです。

すると、aタグを使ってページ内リンクを張ったときに動作しなくなってしまいます。

たとえば次のようにHTMLを記述すると、aタグのリンクをクリックしてもブログ記事一覧のところへスクロールしません。

<a href="#blog">ブログ記事一覧へ</a>

なぜ間違っているのか

id属性の意味をよく考えてみましょう。そもそもidとはidentification(識別)の頭文字をとった略称です。ブラウザは、ページ内リンクの移動先や、JavaScriptを使って要素にアクセスする際など、特定のHTMLタグを見つけるためにidを参照します。

そのため、同じページ内に同じidが2つ以上存在すると、ブラウザはどちらを参照すればよいかを判断できないのです。

CSSだけの都合でコーディングをすると、

「リンクを張ったけれど動かない」
「プラグインを入れたのに動かない」

といった副作用の元凶を埋め込んだサイトになってしまいかねません。

きちんとした品質のサイトを作るためには、個々のタグやスタイルの使い方やテクニックだけに目を向けるのではなく、HTMLとCSSとJavaScriptの関係から「なぜこの文法はこうなのか?」を理解することが大切です。

正しいアプローチ

原理原則に戻って考えてみましょう。ブラウザが期待しているのは、「スマホでアクセスしたのならスマホ向けのHTMLだけが書かれている」「PCでアクセスしたのならPC向けのHTMLだけが書かれている」という状態です。

これが本来あるべきHTMLの姿です。

理想は、PHPなどのサーバーサイドスクリプトを使って、HTMLを生成する時点で要らないほうを取り除くことです。たとえばワードプレスなら、wp_is_mobile() というデバイス判定用の関数が用意されているので、こうします。

<?php if (wp_is_mobile()) { //モバイルデバイスでアクセスされた場合 ?>
<section id="blog" class="sp-only">
スマホ向けのブログ記事一覧が入る
</section>
<?php } else { //それ以外でアクセスされた場合 ?>
<section id="blog" class="pc-only">
PC向けのブログ記事一覧が入る
</section>
<?php } ?>

こうすると、スマホでページを開いたときにサーバーからブラウザへ返されるHTMLは次のようになります。

<section id="blog" class="sp-only">
スマホ向けのブログ記事一覧が入る
</section>

PCでページを開いたときのHTMLは次のようになります。

<section id="blog" class="pc-only">
PC向けのブログ記事一覧が入る
</section>

これでHTML内におけるid属性の重複が解消したので、ページ内リンクなどが誤動作することがありません。

もはや sp-only と pc-only による区別も必要ないので、もっと簡潔になります。

<section id="blog">
<?php if (wp_is_mobile()) { //モバイルデバイスでアクセスされた場合 ?>
スマホ向けのブログ記事一覧が入る
<?php } else { //それ以外でアクセスされた場合 ?>
PC向けのブログ記事一覧が入る
<?php } ?>
</section>

純粋に、デバイスによって内容が異なる部分だけをPHPで分けた形になります。
美しいですね。

プログラミングは、行き当たりばったりに書くとすぐに肥大化してしまいます。
原理原則に従って論理的に組み立てていくと、見やすさと保守性を兼ね備えた美しい作品(コード)になります。