🍆

モバイルのレイアウトしか考慮しない怠惰な開発を実現する【Next.js + Tailwind CSS】

2023/02/05に公開

個人開発ではパソコンとモバイルのデザインを用意するの大変じゃないですか?
そこで、個人開発のプロダクト向けに「モバイルレイアウトだけを開発」という手法を提案させて頂きます!

ピクセルパーフェクトは時代遅れ!これからはパーフェクトレムだ!(?)

ただでさえデザインできないんだから、スマホのデザインだけに集中して作ろうぜ!

実現するもの

例:

https://donzuba.jp/

僕が最近気に入っている怠惰な開発手法です。
上記のキャプチャの通り、ブラウザサイズに応じて、まるで1枚画像のように全ての要素が可変してくれています。(marginもpaddingもborder-radiusも。)

デバイスサイズ毎に見た目が違うという問題が基本的に起きないので、レイアウト崩れが発生しにくくなります。その結果、考慮すべき点がかなり減って開発が楽になります。UIに係る無駄な時間を極限まで削減できるメリットがあります。

また、ブラウザサイズをぐりぐり動かしながら動作チェックする必要もなくなります。 chrome://inspect でモバイルを繋げてプレビューするだけでOKなのです。

超スモール開発の新しい選択肢として広まってほしいなと思い、この記事を書いています。

【はじめに】仕組みを簡単に説明します

この仕組みを簡単に説明します。
ルート(html, body)のフォントサイズを 10px相当(単位vw) のサイズになるように指定しています。全ての要素のプロパティ(widthやheight、margin等)で rem 単位を使用して実装します。remはルートのフォントサイズを基準に再計算されるという仕様を活用した手法です。

例えば、ルートフォントが10pxのとき、width: 40remwidth: 400px 相当として動作することになります。ルートフォントが12pxのとき、 width:40rem;width: 480px 相当して動くことになります。

つまり、ルートフォントをブラウザ幅に応じて可変させることができれば、怠惰な開発が実現するということになるのです。

そこで、ルートフォントには vw 単位を利用します。
ルートフォントを10px相当になる値で vw として指定してあげれば良いことはお分かりでしょうか。

ということで、この手法で重要になるのが「元となるデザインデータのサイズ」と「vw の値の算出方法」です。

「元となるデザインデータのサイズ」が重要な理由(360px推奨)

デザインデータを元に実装をしていくことになりますが、ブラウザの font-size の最小値は 10px相当です。これよりも小さい値はfont-sizeだけでは表現できません。

ですので、デザインデータ = 想定している最も小さいデバイスサイズ という扱いになります。

デザインデータが 375px で作られることも多いかと思いますが、
この手法では 横幅360px を推奨しています。というのも、意外と横幅360pxサイズのデバイスのユーザーがそれなりに存在するからです。(320px以下はほぼ居ないので気にしなくても大丈夫でしょう。)375pxで作ってしまうと、360pxサイズのときに font-size が最小値10pxのまま固定されてしまうので、レイアウト崩れの原因となってしまいます。

ということで、今回の例では 360px のデザインデータをベースに解説していくことにします。

vw の値の算出方法

https://webcode.solutions/en/tools/px-to-vw/

こちらのサイトにアクセスしてください。
Viewport width in PXSize in PX という欄があります。

今回は360pxデザインなので、

  • Viewport width in PX に 360 と入力
  • Size in PX に 10 と入力

これは360pxサイズを基準にしたときの10px相当のvwの値を算出するための指定です。
この状態で計算をすると、 2.778vw という値が出てきました。

これがルートフォントとして指定する、デバイス360pxのときに10px相当の値ということになります。

Next.jsで適用してみよう

globals.css

globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

html,
body {
  font-size: 2.778vw;
}

たったこれだけで準備が完了です。
ブラウザサイズが360pxのときに10px相当として挙動してくれます。

widthやheightにCSSを当ててみよう!

example.tsx
export default function GlobalHeader() {
  return (
    <div className="w-[8rem] h-[4rem] rounded-[0.8rem] bg-red"></div>
  );
}

360pxの横幅のとき、
80px x 40pxで8px角丸の赤い四角形が描画されます。

375pxのときは、
83px x 41pxと8pぐらいの角丸の赤い四角形が描画されます。

ブラウザサイズによって自動的に可変してくれていることが分かることでしょう。

デザインデータをほぼ実寸で採寸でき、かなり怠惰な開発が可能となります。

パソコンサイズに対応するには?

パソコンサイズに対応するまでの手順を下記に示します。
まずはじめに、パソコンサイズで対応したいレイアウトの最大サイズを決めてください。今回は例として480pxとします。ベースとなっているデザインのサイズは360pxです。

ルートフォントを再計算する

ルートフォントの最大値をpxとして決定する必要があります。

480 / 360 = 1.333 となりました。
ルートフォントは10pxを想定しているので、可変最大サイズは13pxとなります。

globals.css
html,
body {
  font-size: min(2.778vw, 13px);
}

このようにすれば、10px相当から最大値13pxまで自動可変してくれます。

全体をdivで囲むことで最大値を制御する

layout.tsxの children を何かdivで囲ってあげましょう。

layout.tsx

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ja">
      <body>
        <div className="global-container">{children}</div>
      </body>
    </html>
  );
}

.global-container としておきました。

このクラス名に対して最大の横幅を適用します。

globals.css
.global-container {
  max-width: 480px;
  margin-left: auto;
  margin-right: auto;
}

@media screen and (min-width: 480px) {
  body {
    overflow-y: scroll; /* コンテンツ量に応じてスクロールバーの表示・非表示されると面倒なので常に表示しておく */
  }

  /* モバイルレイアウトの左右に境界線をつけて見た目を整えるとGood */
  .global-container {
    border-left: 1px solid #eee;
    border-right: 1px solid #eee;
  }
  
  /* fixed要素に対する細かな調整 */
  .fixed.fixed-pc {
    max-width: 480px;
    left: calc(50% - 240px);
  }
  body[style*="hidden"] .fixed.fixed-pc {
    left: calc(50% - 240px - (var(--scrollbar-width) / 2));
  }
}

としておきました。
.fixed.fixed-pc は、固定要素の位置がズレてしまうので、画面中央に来るようにするための調整です。fixedを指定しているHTMLに対して fixed-pc を付与するだけでPC対応が可能になります。

これらの対応は実際に https://donzuba.jp/ にて使用している手法です。

.fixed-pc の実装に関して

body[style*="hidden"] .fixed.fixed-pc としているのは、 body-scroll-lock というパッケージへの対応のためです。このパッケージによるスクロール禁止処理ではスクロールバーが消えてしまうため、CSSセレクタを駆使してスタイルの分岐をしている次第です。あとはクライアントサイドで共通処理として layout.tsx でスクロールバーの横幅をJSで取得してCSS変数に格納すればOKです。

/src/app/_Provider.tsx
"use client";

import { useEffect } from "react";

export function GlobalProvider() {
  useEffect(() => {
    const body = document.querySelector("body");
    if (body) {
      const scrollbarWidth = window.innerWidth - document.body.clientWidth;
      body.style.setProperty("--scrollbar-width", `${scrollbarWidth}px`);
    }
  }, []);

  return <></>;
}

このコンポーネントを /src/app/layout.tsx で呼び出してあげれば完了です。

実装する際の注意点

フォントサイズは 10px 以下にはならないので予め理解しておきましょう。
border-radius も rounded-[0.4rem] を使用して実装しましょう。
line-height は rem を使わずに leading-[1.6] と実装しましょう。

副次的なうれしいこと

この方法で実装するとちょっと嬉しいことがあります。
その代表がスケルトンスクリーンの実装です。

スケルトンスクリーンはReact Content Loaderなどを用いて実装することになるかと思いますが、その際に各要素の値を寸分違わず用意する必要があります。

この方法で実装すると、エミューレションで横幅360pxで閲覧すればデザインデータ通りなので、Chrome開発者ツールで値を採寸する際にそのままの値を使えば良いのです。実装時に発生しがちな若干のズレを全く考慮する必要が無く、ブラウザ表示通りに採寸できるのはこの実装方法の特権でしょう。特にline-height + font-sizeを加えた計算をする必要が無くなるので、見たままでスケルトンスクリーンを作れてしまうのはとても楽です!

▼これぐらいピッタリにできちゃう!

パソコンユーザー向けに左右の空間を活用して操作パネルを用意してあげるのも良し!

メインとなるモバイルレイアウトに触れてしまうと不具合の温床になり面倒くさいので、PCユーザー向けに左右の空間に、利便性を向上させるための簡単な操作パネルやナビゲーションを設置しても良いかもしれません。

左右のパネルからモバイルレイアウトを操作するイメージにすれば、不具合の温床となる可能性を極限まで減らしつつ、パソコンユーザーに快適性を提供することができると思います。(これに関しては未検証なので妄想でしかありませんが。)

楽しながら最高の価値を提供する手段の選択肢としてご活用いただければ幸いです!

Discussion