私が以前担当したコーディング案件での話です。新規1ページのレスポンシブ対応ありの仕事ですが、他の案件と並行してた上、スケジュールがタイトだったため、今回は作業効率が良さそうな「モバイルファースト」でCSSを書こうと決めました。一部複雑なレイアウトがあってリスク管理の面から、スマホ版の方を早く作り込んで表示確認しておきたいと思ったのも理由でした。ところが無事テストアップした翌日、サイトを確認したお客様から私に修正依頼のメールがきました。PDFで添付されていたPC版ページのキャプチャを見て愕然。「プリントしたら画面とレイアウトが変わりました」とのご指摘。確かにPC版のレイアウトが崩れまくってます。画像サイズが大きすぎたり、floatが効いてなかったり……。お客様サイトで運用中の他の既存ページはPDFに印刷してもコンテンツ部分のレイアウトは崩れません。それらのページはHTMLやCSSで特に印刷用に何か設定してるわけでもないのに。明確な違いは1つ。それら既存ページのCSSは「デスクトップファースト」で制作されていたものでした。
この記事では、「モバイルファーストで作成したWebページを印刷した時にレイアウトが崩れる現象」について、その原因および最も簡易的な解決法を書きます。
発生する現象の例
例として、シンプルな画像ギャラリー風のページをモバイルファーストでサクッと作ってみました(下図)。ブレイクポイントは2箇所 600px, 1025pxで、画像リストのカラム数を変えています。どのメディアクエリが選択されたかわかるように、見出しにSP, Tablet, PCの文字列を表示させました。
HTML(要点のみ)
<div>
<h2 class="status">Layout: </h2>
<ul class="img-list">
<li>
<img src="https://picsum.photos/id/1001/800/565" alt="">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</li>
<li>
<img src="https://picsum.photos/id/102/800/565" alt="">
<p>Sed et quam at velit tincidunt molestie.</p>
</li>
<!-- 残りの画像も同様 -->
</ul>
</div>
CSS(要点のみ)
/* 各画面サイズに共通のコード */
/* スマホ画面サイズ用のコード */
.status::after {
content: "SP";
}
@media screen and (min-width: 600px) {
.status::after {
content: "Tablet";
}
/* タブレット画面サイズとPC画面サイズに共通のコード (PCにも継承) */
/* タブレット画面サイズ用のコード */
}
@media screen and (min-width: 1025px) {
.status::after {
content: "PC";
}
/* PC画面サイズ用のコード */
}
原因と対策
印刷レイアウトがスマホ版になってしまった原因はメディアクエリの書き方にありました。印刷やPDFに出力する場合、以下のクエリの書き方はメディアタイプにscreenしか指定していないため印刷にはマッチせず、中身のCSSルールが適用されません。
@media screen and (min-width: 600px) { /* Tablet & PC */ }
@media screen and (min-width: 1025px) { /* PC */ }
これらのスタイルを印刷にも適用させたい場合は、メディアタイプprint
を付け足します。
@media print,screen and (min-width: 600px) { /* Tablet & PC */ }
@media print,screen and (min-width: 1025px) { /* PC */ }
モバイルファーストでCSSを記述した場合、min-width
が小さい方から大きい方へCSSルールが継承されていくので、すべての@media
行にprint
を追加しておく必要があります。なお、@media
構文はカンマ,
がメディアクエリの区切りなので、print,screen
が1つのまとまりではなく、意味はprint
とscreen and (min-width:...)
という2つのクエリになります。(CSS仕様書 3. Syntax)
@media print, /* 印刷用 */
screen and (min-width: 1025px) /* 画面用 */
上記の対策をした結果、PC版レイアウトで印刷/PDF出力されるようになりました(下図)。
print
指定が効いていることを確認するため、見出しの前に文字列「PRINT」を表示させてみました。メディアクエリは
@media print {
.status::before {
content: "PRINT - ";
color: red;
}
}
完成したコードはこちら: https://codepen.io/kaz_hashimoto/pen/WNeMQyz
冒頭に書いた「他の既存ページはPDFに印刷してもコンテンツ部分のレイアウトは崩れなかった」理由は、既存ページのCSSはデスクトップファーストで記述されていたため、印刷時は元々メディアクエリの外側に書いてあるPCレイアウト用のCSSルールだけが適用されたからでした。
ところで、print
の代わりにall
を指定したらどうなるでしょうか? 2.3. Media Typesによると、all
はすべてのデバイスにマッチするはずです。試したところ、
/* スマホもPCレイアウトになってしまうのでダメ */
@media all,screen and (min-width: 600px) { /* すべてのデバイス */ }
@media all,screen and (min-width: 1025px) { /* すべてのデバイス */ }
/* 画面はOKだがA4縦印刷時はタブレット版のレイアウトになる */
@media all and (min-width: 600px) { /* A4縦印刷時はこちらが適用 */ }
@media all and (min-width: 1025px) { /* A4縦印刷時は適用されない */ }
後者はA4横を指定するとPC版レイアウトで出力されました。
上記のサンプルは@media
が2箇所だけでしたが、CSSファイル内に@media
行が散在している場合、抜け漏れなく直すのは面倒で間違いの元です。実際、私が書いていたコードは、レイアウトのブロックごとにベース記述の後ろに@media
を記述する形式にしていたため、修正箇所が多数に及びました。しかも、
@media screen and (min-width: xxx) and (max-width: yyy)
のような範囲を指定したクエリがあって、print
を入れる箇所を慎重に判断しなければならず注意が要りました。
まあ、こんな対策に後から工数がかかってしまったことを踏まえると、最初から従来どおりデスクトップファーストで作っておけばよかったと思いました。以来私は実際の案件ではそうしています。