🤓

俺流レスポンシブコーディング

2021/02/16に公開
4

俺流レスポンシブコーディングの覚書。「人には人のレスポンシブ」があるのでこれが正解だってわけではないのですが、レスポンシブコーディングで悩んでいる人にとって参考になる記事になってくれたら嬉しいです。

ブレイクポイントは特定のデバイスの画面サイズを基準にしない

以前アンケートを取った時にデバイスのサイズを意識して決める人が半数以上を占めていた。

https://twitter.com/tak_dcxi/status/1354375892844244992

アンケート結果を抜きにしても「2021 年のブレイクポイント決定版はこれだ!」的な記事がバズっているのを定期的に目撃し、主流のデバイスのサイズを比較するアプローチがほとんどであるが、僕はデバイスの端末のサイズを基準にブレイクポイントを決めることには否定的である

  • 主流のデバイスのサイズなんてものは時間が経てば変化する。
  • 昨年 iPhone 12 が発表された時に従来の画面サイズとは違うバリエーションになることが分かるやいなやタイムラインが慌てふためいていた記憶がある。特定のデバイスのサイズを基準にブレイクポイントを決めていたら、これから先も新しい端末が登場するたびに慌てふためくことになるのでは?
  • 主流のデバイスを贔屓して設計を最適化しても、別のデバイスでは窮屈だったりゆとりが余りすぎている…という場合も多い。iPhone 12 mini が発表された際に多くの小画面カンプが 375px で作られている故か「375px 未満は対応したくない」というコメントが散見されたがAndroid の多くは 360px であることを忘れていないだろうか
  • どのみち、短期公開の LP やディザーサイトならまだしも、長期的に運用される Web サイトにおいては流行りの端末に左右される設計は致命的だ。

出回っている数多くの画面幅に対応し、さらには縦持ち・横持ちなども考慮したら、考えられる最小の画面幅から最大の画面幅まで全ての環境である程度最適化された見た目を実現するのが理想なのは間違いない。レスポンシブデザインは一つの寸法で 100 点を目指す思想ではなく、より多くの寸法で 90 点が出るように最適化する思想だと僕は考えている。特定のデバイスを意識したブレイクポイントの決定はこの思想と相反してしまう。

ブレイクポイントは予め定数として用意しておくと良い

理想としてはデザインごとに最適なブレイクポイントを探して決定したほうが良いのだが、デザインごとに最適なブレイクポイントを探るのは非常に難易度が高いように感じる。

  • デザインカンプで表現されるのは「あくまでその画面幅での見た目」なだけであって、そのデザインの最適なブレイクポイントを紐解くためにはカンプで表現されていない部分に目を向ける必要がある。
  • そのため、デザインごとの最適なブレイクポイントを見つけ出すためにはデザイナーとの細かい認識合わせが必要になる。
  • デザイナーとの細かい認識合わせは必要だと以前投稿したが、締切・納期というものがある以上デザイナーとのやり取りに多く時間を掛けられないのが現実。

そういった事情から、予めある程度の的確な位置・間隔でブレイクポイントを用意しておくほうがやりやすい。実装者側からデザイナーに「ブレイクポイントはこのようにしていきたい」と働きかけたほうが良いと思う。

  • ある程度の的確な位置・間隔で打ち決めされていればブレイクポイントは何でも良い。
  • おすすめのブレイクポイントは次の 2 パターン。パターン 1 はBootstrap 5を、パターン 2 はTailwind CSSのそれを参考にしている。
パターン1
$breakpoints: (
  'sm': (min-width: 576px),
  'md': (min-width: 768px),
  'lg': (min-width: 992px),
  'xl': (min-width: 1200px),
  'xxl': (min-width: 1400px)
) !default;
パターン2
$breakpoints: (
  'sm': (min-width: 640px),
  'md': (min-width: 768px),
  'lg': (min-width: 1024px),
  'xl': (min-width: 1280px)
) !default;
  • 変数の命名は抽象的にしておき、sp, tb, pc のようにデバイスを連想させないようにする。これはレスポンシブごとのユーティリティクラスを作る上でも同じ。ブレイクポイントが増えた場合にそれらに対して具体的な命名をするのは難しいため、相対的な名前にするほうがベター。
  • 加えて、tb と命名しても必ずしもタブレットだけで見られるわけではない。例えばバナナに「おやつ」という名前をつけたら、じゃあ朝飯にはならないのかってなる。汎用的な変数や class の命名にも似たようなことが言えるだろう。
  • ブレイクポイントは少ないほうがメンテナンス面では良いのだが、デザインによっては 2 つ 3 つでやりくりするのは非常に辛い。特に 1024px 付近は癖が強いため、最低でも 4 つほど保険として用意しておいたほうが良いという経験則がある。保険として複数用意した上で、なるべくブレイクポイントを少なく抑える努力をすれば良い。

現在はパターン 1 で実装しているが、パターン 2 に移行しようか迷っている。

360px 未満は JS で viewport を固定する

4 インチ対応(320px 対応)はこの先も必須であることは間違いないのだが、実装コストの割にリターンが小さいため 360px 未満は JS で viewport を書き換える方針に移行した

https://twitter.com/tak_dcxi/status/1342748212289916930

!(function () {
  const viewport = document.querySelector('meta[name="viewport"]');
  function switchViewport() {
    const value =
      window.outerWidth > 360
        ? 'width=device-width,initial-scale=1'
        : 'width=360';
    if (viewport.getAttribute('content') !== value) {
      viewport.setAttribute('content', value);
    }
  }
  addEventListener('resize', switchViewport, false);
  switchViewport();
})();
  • 前提として 375px 付近で作られるデザインをそのまま実装したら 4 インチでほぼほぼ崩れると言っても過言ではない。
  • そのため、4 インチ対応する場合は 4 インチでどう見せたらいいか?をデザイナーと細かい認識合わせした上で 4 インチを意識した実装を行うことになり、実装コストがそこそこ掛かるのが実情。
  • statcounter のデータによれば 4 インチ端末のシェアは当記事執筆時点では日本国内のモバイル全体における 2.93%と減少傾向にある。約 3%は決して無視して良い数値ではないのだが、2021 年にリリースされるだろう iOS 15(仮)は旧 SE でのサポートが外れると予想されるため、シェアの減少はより加速されるだろう。
  • iPad の Slide Over およびに Split View の論理サイズは 320px なので現在 4 インチを意識したコーディングをする必要がある理由はここにあるのだが、前述した実装コストに対して見返りが釣り合っていないという意見もある。

加えて、画面幅が 300px 以下の端末が登場したことも理由の一つである

  • 一昨年リリースされた折りたたみスマートフォンの Galaxy Fold の一部の画面幅はまさかの 280px
  • Galaxy Fold 自体は一昨年の時点で世界で 100 万台出ているものの、日本国内においては流通量は少ない。ただ、将来どうなるかは分からないし、折りたたみスマートフォンが国内においてもシェアが広がり 280px 相当の画面幅がマイナーではなくなる…という未来が訪れる可能性は 0 とは言い切れない。
  • また、「Galaxy Fold lang:ja」で Twitter 検索したら分かるように Galaxy Fold を愛用している国内ユーザーは想像以上に多い印象。
  • さらに言うと、広げた状態のマルチ状態だと画面幅は 229px まで狭まるらしい。ここまで来るとインターネットがとても辛そうという感想しか出ないのだが。

https://medium.com/samsung-internet-dev/current-web-on-galaxy-fold-ad12d7f57c26

https://twitter.com/nomen_machine/status/1328629383477694464?s=20

https://twitter.com/nomen_machine/status/1328633355290562560?s=20

  • あとはデベロッパーツールのデフォルトに Galaxy Fold が加わったので変に Web をかじっているディレクターからツッコまれたくないというのもある。

以上の理由から 360px 未満は JS で viewport 書き換えで対応とするのがベターだという結論に達した。

  • 将来どんな極狭画面サイズが来ても JS 無効にされなければ安心。前述した 229px 相当の画面幅でもスケーリングされる関係で相当読みづらくなるが、そこはピンチアウトしていただく方向で。レイアウトが崩れるよりかは大分マシだろう
  • Android の標準サイズが 360px なので、そこまではスケーリングしないで対応したい。
  • もちろん JS 無効環境を考慮してもできる限り 4 インチで見られても崩れないようなコーディングをしたほうが良いのは言うまでもない。

モバイルファースト(min-width)で書く

スタイリングはシンプルなものを基準として上書きしていったほうが無駄がない

  • レスポンシブデザインにおいてはビューポートが狭ければ狭くなるほど画面構成がシンプルになる場合が多い。
  • 最小のビューポートを基準とし、順に画面を広げて設計したほうが継承を促しつつ最小限のスタイルの上書きをすることができるようになるため都合が良い。
  • 一方、最大のビューポートを基準とし、順に画面を狭めて設計するとdisplay:flexblockなど初期値での上書きが必要になったりして無駄が多くなりがち。
  • 原則的にはメディアクエリの指定は min-width を使い、max-width は避けて書くのがベターだろう。

もし、ミディアムサイズなどで一時的に max-width を用いたい場合は未満を表す方法には注意して書いたほうが良い

  • モバイルファーストで指定しているけれど、一部だけ 1024px 未満全体に当てたいスタイルがある場合は安易に max-width を用いるのではなく@media not all and (min-width: 1024px)のようにnot キーワードと min-width を組み合わせて指定することを推奨する
Not Good
@media (max-width: 1023px) {
  /* 1024pxから1px引いて未満とするのは違う */
}
Good
@media not all and (min-width: 1024px) {
  /* 1024pxを含まずそれより小さいという表現ができる */
}
  • @media (max-width: 1023px) @media (min-width: 1024px)のように記述すると、ブラウザの拡大率および解像度との組み合わせ次第で画面幅が 1023px と 1024px の間の状態になった場合にスタイルが当たらなくなったり意図しない現象が起こる可能性がある。タイムラインに流れてきた次のような事象がそのまま具体例になり得る。

https://twitter.com/shoko1851/status/1350256863149961222

https://twitter.com/shoko1851/status/1350669489571037184

あと、min-width を利用して書く都合上スモールサイズのカンプからコーディングしたほうが効率が良いけれど、広い画面から矛盾なく狭い画面を逆算できるスキルを持っていればラージサイズのカンプからコーディングしても問題ない。実際、僕もカンプの支給順の都合でラージサイズのカンプからコーディングすることが多い。

レスポンシブで画像を切り替える場合は picture 要素を使う

よく CSS で画像の切り替えを行っている例を見かけるが、picture 要素を使って出し分けたほうが良い。

Not Good
<img class="pc" src="/assets/img/home/img_about_pc.jpg" alt="">
<img class="sp" src="/assets/img/home/img_about_sp.jpg" alt="">
Good
<picture>
  <source srcset="/assets/img/home/img_about_md.jpg" media="(min-width: 768px)">
  <img src="/assets/img/home/img_about_sm.jpg" alt="">
</picture>
  • CSS でdisplay:noneしてもコンテンツはダウンロードされるため、CSS で画像の切り替えを行うと不必要な画像の読み込みが発生してパフォーマンス面に影響する
  • picture 要素を利用して出し分ければ必要な画像のみが読み込まれるためパフォーマンス的に良い。

以下、picture 要素を利用する際の注意点。

  • picture 要素の中には必ず 1 つの img 要素を含める必要がある。この img 要素は source 要素で利用可能な画像を提供できなかった場合の代替として用いられ、picture 要素そのものが非対応のブラウザで表示されることに留意。
  • 画像の寸法、alt 属性、decoding 属性および loading 属性などは picture 要素内の img 要素に記述する。
  • img 要素は source 要素の後に配置する。
  • img 要素にはレイアウトシフトを防止するために width 属性と height 属性を指定しておくことが望ましいが、picture 要素で違う縦横比の画像を出し分ける場合は現時点では width 属性と height 属性を指定しないようにする。(仕様を固めている段階?)
  • IE11 と Android4 系は picture 要素に対応していないので、これらが対応範囲である場合は Picturefill.js を導入する必要がある。

ちなみに、CSS Wizardry の Harry Roberts 氏によるとdisplay:noneしたコンテンツはダウンロードされるという仕様を利用して background-image に利用する画像のレンダリング時間(LCP)を大幅に改善できるとのこと。

https://twitter.com/csswizardry/status/1276854595382325248

ただ、背景に cover 的な写真を敷くときは background-image ではなく img 要素を背面に絶対配置することが多い。背景画像に WebP を用いる時に background-image だとフォールバックが面倒なのと、パフォーマンス改善でdecoding="async"を指定できたりと img 要素でやりくりするほうが都合が良いため。

ブレイクポイントを跨いで共通化が難しそうな場合は無理に 1 ソースにまとめる必要はない

レスポンシブにおいては HTML は 1 ソースにすることが理想であるが、グローバルナビゲーションのように小画面ではポップアップ式で大画面では固定して横並びといったようにレスポンシブで仕様が大きく変化するものだったり、デザイナーがレスポンシブあまりよくわからなくて荒業を使わないと HTML の共通化が難しい場合などは無理に 1 ソースにまとめるのではなく分けて管理したほうが良い

HTML のリファクタリングよりも CSS のリファクタリングのほうが圧倒的に難しいため、保守性や拡張性を意識したら HTML 側を妥協するほうが将来的には良い。もちろん 1 ソースで管理することを第 1 に考えて、難しそうなら妥協するという考えで。

僕が尊敬している制作者のはにわまんさんが自身の記事で同じようなことを言っていて安心した。

https://zenn.dev/haniwaman/articles/bf392f397c8db7341881

transition の all 指定は避ける

レスポンシブ関係ある?と思われるかもしれないが、transition で all 指定しているとブレイクポイントを跨いだ時に意図しないアニメーションが起こる場合がある。例えば hover を伴うボタンの padding の値がブレイクポイントを跨いで変化する場合、ブレイクポイントを跨いだ時に padding にアニメーションが走ることになって見栄えが悪くなる。

面倒でも transition を指定する際はプロパティ名を記述するようにしておこう。

Not Good
.foo {
  transition: all .3s ease-out;
}

.bar {
  transition: .3s ease-out; /* transition-propertyの初期値はallなので注意 */
}
Good
.foo {
  transition: opacity .3s ease-out;
}

.bar {
  transition: background-color .3s ease-out, transform .3s ease-out;
}

ブレイクポイント云々抜きにしても、ブラウザによっては読み込み時に意図しないアニメーションが起こったり、z-index などが transition 対象に含まれたりするとアニメーション時に意図しないバグが起こり得るため、all 指定は原則避ける方向性で良いと思う。

おわりに

  • レスポンシブコーディングは全ての画面サイズでしっかりと見られるように設計することを第 1 にして、結果的にどのデバイスにおいても最適化されているようにするのがベストだと思っている。
  • 特定のデバイスを贔屓にしないように意識して実装しよう。
  • 主流のデバイスのサイズを基にした最適なブレイクポイントはどうなんだ議論は無意味なのでやめたほうがいいと思う。
  • レスポンシブコーディングの良し悪しはメンテナンス面に影響するので実装力が試される。

最近タイムラインで「ランドスケープ対応(スマホ横向き対応)していますか?」といったツイートが流れてきたが、特定の端末を意識しないレスポンシブコーディングをしっかりやっていれば(セーフエリア対応とかは除いて)自然とできているはずだから意識しなくて良いと思う。

まだ書き漏れていることがあるかもしれないので、後々更新するかもしれません。

Discussion

たかもそたかもそ

1024px未満のところですが、ブラウザの拡大率を変更してる際にも同じ現象が起きます。
(ディスプレイ解像度よりもブラウザ拡大率のケースの方が実際には多いかも…)

TAKTAK

コメントありがとうございます。追記しました!

fnslaehrosaueyrfnslaehrosaueyr

初めまして、こんばんは。見出し「360px 未満は JS で viewport を固定する」のアイディア、素晴らしいと思いました。こちらのコードを私のサイトに利用させていただいてもよろしいでしょうか?Zennの利用規約を確認したところ「第三者は、利用者コンテンツについて、権利者の許可を得ることなく、無断で転載または二次配布等を行うことはできません。」という文言を見かけた物で、許諾可否についてお伺いいたしました。ご検討いただければ幸いです。よろしくお願いいたします。

fnslaehrosaueyrfnslaehrosaueyr

度々申し訳ございません。この度は不躾なお伺いをしてしまい、大変失礼いたしました。申し訳ございません。今回はこの情報を利用するのを控えようと思います。重ねてお詫び申し上げます。