[Astro #04] レスポンシブの深淵と dvh による画面固定

はじめに

Astroのサイト構築に関する備忘録メモです。

大まかなサイトデザインは、AIに任せると可能なのですが、細かくカスタムしようとすると、特にスマホデザインでレイアウトが崩れたり、そもそも想定して作って無かったりして、AIの修正でも直らない為、手動で設定しつつ少しずつ進めています。

また、Three.jsのプロジェクトをTOPに表示する際に、そちらのCSSのカスタムも必要だったりと、やる事が多くかなり大変です。

前回の記事:

1. Astro プロジェクトの基本ツリー構成

Astroでモダンなサイトを構築する際、ディレクトリ構成の理解は不可欠だ。ここでは本サイト(PROTOCOL.LAIN)の実際の構成を例に、各ディレクトリの役割を解説する。

/
├── public/              # 静的資産(画像、Three.jsのギャラリー等)
├── src/
│   ├── components/      # 再利用可能なUIパーツ(Navigation, Footer)
│   ├── content/         # Markdown記事(Hugoからの移行先)
│   │   └── blog/        # ブログ記事のデータ
│   ├── layouts/         # 共通テンプレート(BaseLayout.astro)
│   └── pages/           # 実際のURLになるファイル(index.astro)
├── astro.config.mjs     # Astroの設定ファイル
└── package.json         # 依存関係
  • public/ (静的資産ディレクトリ)
    [cite_start]Astroのビルドプロセスを通さず、そのままルートディレクトリに公開されるファイルを配置する場所。FaviconやWebフォントだけでなく、本サイトのように独立したThree.jsのHTMLプロジェクト(ProceduralWindFlowなど)を格納し、トップページからiframeで安全に呼び出す際にも重宝する [cite: 1, 6]。

  • src/components/ (コンポーネント)
    [cite_start]サイト内で使い回す独立したUIパーツを格納する。ヘッダー(Navigation.astro)やフッター(Footer.astro)など、各ページで共通して使用する要素をここで部品化する [cite: 70, 84, 100]。CSSはそのコンポーネント内にスコープされるため、他のデザインを破壊する心配がない。

  • src/content/ (コンテンツコレクション)
    Astro v2から導入された、Markdownファイルを型安全に管理するための強力なディレクトリ。旧環境(Hugo等)で管理していた膨大なブログ記事データはここに移行する。スキーマを定義することで、フロントマターの記述漏れや型エラーをビルド時に検知できるようになる。

  • src/layouts/ (レイアウトテンプレート)
    [cite_start]ページの骨組み(HTML構造)を定義する場所。本サイトの BaseLayout.astro では、全体のメタデータ、ナビゲーションバーの読み込み、SPAのような滑らかなページ遷移を実現する ClientRouter の設定を一括で管理している [cite: 70, 73, 76]。

  • src/pages/ (ページルーティング)
    [cite_start]ここに配置したファイルが、そのままサイトのURLパスとしてマッピングされるファイルベースルーティングの中核。例えば src/pages/index.astro はトップページ(/)として機能する [cite: 1]。

2. スマホ表示の「最終防衛ライン」:dvh と clamp() によるレイアウト制御

Astroでサイトを構築する際、PC環境では完璧に機能していても、実機のモバイルブラウザ特有の仕様(特にアドレスバーの動的な挙動)によってレイアウトが容易に崩壊する。ここでは、モダンなCSSを用いた実践的な解決策を解説する。

  • 100vw の罠と width: 100% への統一 画面幅いっぱいに要素を広げる際、width: 100vw を指定すると、OSの縦スクロールバーの幅まで計算に含まれてしまい、意図しない横スクロールが発生する原因になる。コンテナの幅は基本的に width: 100% とし、親要素の枠内で制御するのが定石だ。

  • vh ではなく dvh (Dynamic Viewport Height) を使う スマホブラウザ最大の障壁が「スクロールによるアドレスバーの伸縮」だ。従来の 100vh ではこの伸縮が考慮されず、アドレスバーが表示されている間はUIの下部が画面外に押し出されてしまう。これを解決するのが dvh という単位だ。height: calc(100dvh - 75px) (ナビゲーションとフッターの高さを引く)のように指定することで、アドレスバーの有無に関わらず、常に「今実際に見えている範囲」に要素をピタッと収めることができる。

  • clamp() による流動的かつ安全なフォントサイズ制御 レスポンシブ対応として font-size: 1vw のような相対指定のみを行うと、画面幅が約400pxのスマートフォンでは文字がわずか4px相当になり、視認性が完全に失われる。これを防ぐには clamp() 関数が有効だ。 font-size: clamp(12px, 2vw, 0.8rem) のように記述することで、「最小12pxを死守しつつ基本は画面幅(vw)に連動させ、最大でも0.8remで止める」という、安全で読みやすい可変フォントを実現できる。

3. 実践トラブルシューティング・ログ

今回のAstro移行およびUI構築プロセスで直面した具体的なバグと、その解決策を記録しておく。似たような構成を作る開発者の参考になれば幸いだ。

  • ナビゲーションの文字が重なる・押し出される

    • 原因: 狭い画面幅で gap と文字サイズが固定されたまま、ロゴとメニュー要素が押し合っていた。
    • 解決策: メニューの文字サイズに clamp() を用いて最小サイズを確保しつつ、ロゴ部分に flex-shrink: 0 と white-space: nowrap を指定。これでロゴが圧縮されたり改行されたりするのを完全に防ぎ、安全に右寄せメニューと分離できた。
  • ブログの検索機能が動かなくなる(スクロール競合)

    • 原因: 全画面表示を強制するために BaseLayout の html, body に対して overflow: hidden をグローバル適用した結果、スクロールを検知して動くライブラリや検索機能が死んでしまった。
    • 解決策: グローバル設定での固定は撤廃。トップページのメインコンテナ(.copland-container)内に限定して overflow: hidden を適用するようスコープを切り分け、影響範囲を最小化した。
  • UIとフッターのめり込み

    • 原因: フッターを絶対配置(position: absolute)にしていたため、画面サイズが変わるとコンテンツ領域にめり込む事態が発生した。
    • 解決策: フッターを position: fixed に変更し、メインコンテナの高さ計算 calc() からフッターの高さ分を明示的に差し引くことで干渉を回避した。
  • 検索導線の欠如(UI設計の考慮漏れ)

    • 原因: 移行時のヘッダー設計において、検索ページへの導線が確保されていなかった。
    • 解決策: Navigation.astro 内のメニュー項目を右寄せのグループ(.nav-right-group)としてまとめ、その先頭に [ 🔍 ] リンクを配置。デザインを崩すことなく機能を追加した。