実寸プレビューは常に横16:9なのに、スマホ用@media(max-width:899px)が紛れ込み崩れた
※本記事にはアフィリエイトリンクを含む場合があります。内容は広告の有無に影響されません。
結論
1280×720に固定したステージをtransform: scale()で縮小表示していても、実機のスマホ向けに書いた@media (max-width: 899px)はステージの見た目のサイズではなくブラウザの実ビューポート幅で評価されるため、PCブラウザの横幅を899px以下に狭めるとスマホ向けの縦積みレイアウトがそのまま混入する。
結論
1280×720に固定したステージを transform: scale() で縮小表示していても、実機のスマホ向けに書いた @media (max-width: 899px) はステージの見た目のサイズではなくブラウザの実ビューポート幅で評価されるため、PCブラウザの横幅を899px以下に狭めるとスマホ向けの縦積みレイアウトがそのまま混入する。 実寸プレビューは常に横16:9であるべきなのに、ブラウザの実際の幅だけを見て発火する @media によって、1列縦積みのスクロールUIやページャのドットなど、実機のスマホ向けに作られたレイアウトが縮小ステージの中に紛れ込んで崩れる。
症状
サイネージ盤面をエディタ上で実寸に近い見た目のまま任意の幅に縮小表示する ScaledSignageBoard というラッパーコンポーネントがある。中身は1280×720に固定した .stage を transform: scale() で縮小し、外側の .frame は aspect-ratio: 16 / 9 を保って overflow: hidden で切り取る。ここまでは大型モニタ向けの対策(後述)で既に720p基準に固定されている。
ところが、このプレビューを開いたままブラウザのウィンドウ幅を狭めたり、狭い分割表示のパネルでエディタを開いたりすると、常に横16:9であるはずのステージの中に、1列縦積みのスクロールUIやページャのドットが混入して崩れた。パターンによっては2カラムの盤面がいきなり縦積みになり、ヘッダーの高さや位置もずれる。
.stage 自体の幅・高さは1280px・720pxのまま変わっていないので、原因はステージのサイズではなくブラウザの実際の幅にあった。
原因
.signageRoot には、実機のサイネージ端末(縦横比不明の実物理ビューポート)向けに、盤面を1列縦積みのスクロールUIへ作り替えるレスポンシブ定義が入っている。
@media (max-width: 899px) {
.container { /* 1列縦積みへ */ }
.p2Grid { /* pattern2 の縦積み */ }
.p4Grid { /* pattern4 の縦積み */ }
.p5Grid { /* pattern5 の縦積み */ }
/* … */
}
これは実機の狭いビューポート(真のスマホや、縦長設置の実物理ディスプレイ)を対象に書かれたもので、実機ではその前提が成り立つため正しく機能する。
しかし @media はステージに適用された transform: scale() を見て評価するわけではなく、ブラウザ(あるいはビューポートを提供している祖先要素)の実際の幅を評価する。ScaledSignageBoard は同じ .signageRoot を1280×720の .stage に固定して縮小描画するラッパーなので、ステージ自体のサイズとは無関係に、開いているブラウザの実ビューポート幅が899px以下であれば @media (max-width: 899px) がそのまま発火し、実機向けの縦積みレイアウトがステージ内にも適用されてしまう。
この抜け漏れは、実は最初から自覚されていた。大型モニタ(≥1800px)向けに .scaledStage というマーカークラスを導入して720p基準を固定した対策が入ったとき、同じファイルのコメントにはこう書かれていた(対策直前の時点)。
(≤899px のモバイル縦積み中和は別軸=§12 のモバイル経路一式の復元が要るため本ブロックには含めない。実寸プレビューは
主に職員室 PC からの導線。スマホ実機での見切れが確認されたら別途対応する。)
つまり「≤899pxのモバイル縦積みは別軸の対応が必要で、実寸プレビューの主な導線は職員室PCだから、確認されたら別途対応する」という判断で、意図的に対象から外されていた。だが実際には、実機のスマホでなくても、PCブラウザの幅を899px以下に狭めるだけで同じ経路を通ってしまう。
直し方
大型モニタ側の対策と同じ考え方で、ステージ内だけを対象にした @media (max-width: 899px) を追加し、実機向けの縦積みレイアウトが変更する各プロパティを、元のデスクトップの既定値へ1つずつ復元した。
@media (max-width: 899px) {
.scaledStage .signageRoot { overflow: hidden; }
.scaledStage .container {
grid-template-columns: 70% 30%;
grid-template-areas: "info ad";
height: 100%;
padding-top: var(--header-height);
}
.scaledStage .p2Grid { overflow: hidden; }
.scaledStage .p4Grid { overflow: hidden; }
.scaledStage .p5Grid {
display: grid;
grid-template-columns: 2fr 1fr;
grid-template-rows: minmax(0, 1fr) auto;
grid-template-areas:
"notice schedule"
"foot foot";
}
/* pattern3/4/5 の大型ヘッダー(§12 の sticky 化)も既定値へ戻す */
.scaledStage .signageRoot.p3Root,
.scaledStage .signageRoot.p4Root,
.scaledStage .signageRoot.p5Root {
--header-height: 64px;
}
/* … 各パターンの上書きを1つずつ復元 */
}
.scaledStage <selector> は、実機向けの @media (max-width: 899px) ブロック内にある単純な <selector> よりも詳細度が高い。そのため、ブラウザの実ビューポート幅が899px以下になって実機向けのルールが発火しても、.scaledStage 配下では常にこの復元ルールが勝ち、ステージは横16:9のデスクトップ骨格を保つ。
復元したのは grid-template-columns やレイアウト用の flex 値、overflow など見た目の骨格に関わるプロパティで、clamp(…vw…) を使ったフォントサイズはそのままにしている。スマホ相当の幅で見ればフォントは縮小されるが、それは50インチの盤面を縮小コピーして見ている以上当然の見え方であり、レイアウトそのものは崩れない。
この復元は .scaledStage というマーカークラスを起点にしたセレクタにしか書かれていないため、実機のライブ配信中の画面や、公開されている /signage 配下のページには一切影響しない。それらのDOMには .scaledStage が付かないので、実機向けの @media (max-width: 899px) は元のまま縦積みレイアウトとして機能する。
再発防止
この抜け漏れが最初から見えていた理由は、大型モニタ側の対策を入れた時点のコメントに残っている。「≤899pxのモバイル縦積み中和は別軸」「実寸プレビューは主に職員室PCからの導線」という判断は、実機のスマホという狙った条件では正しかったが、同じ条件はPCブラウザの幅を狭めるだけでも成立するという点を見落としていた。@media はデバイスの種類を判定しているわけではなく、その時点のビューポート幅だけを見ているため、「実機のスマホ以外では起きない」という前提自体が成り立たない。
実際には、この判断が書かれたコメントを残したのと同じ日のうちに、もう一段の対応としてこの復元が追加されている。ステージ内で発生し得る @media の抜け漏れは、大きい方向(≥1800pxの大型モニタ)と小さい方向(≤899pxのモバイル)の両方を、同じ「ステージ内は常に720p基準へ固定する」という1つの原則で塞ぐ必要があった。
よくある質問
Q1transform: scale()で縮小しているのに、なぜスマホ用の@mediaが効くのですか?
@media (max-width: 899px)はtransformで縮小された後の見た目のサイズを見ません。ブラウザの実際のビューポート幅そのものを評価するため、ステージを1280×720に固定してscale()で縮小描画していても、開いているブラウザの実ビューポート幅が899px以下であればスマホ向けのプロファイルがそのまま発火します。
Q2職員室のPCでプレビューを開くだけなら関係ないのでは?
PCブラウザでもウィンドウ幅を狭めたり、狭い分割表示でエディタを開いたりすれば実ビューポート幅は899px以下になります。デバイスの種類ではなく実際のブラウザ幅で発火するため、PCでも条件を満たせば再現します。
Q3実機のサイネージ表示や公開ページ/signageにも同じ復元がかかりますか?
かかりません。この復元は`.scaledStage`というプレビュー用のマーカークラスを起点にしたセレクタだけに書かれています。実機の表示や公開`/signage`経路にはこのクラスが付かないため、@media (max-width: 899px)の縦積みレイアウトはそのまま有効です。
確認した環境
- Next.js ^16 / React ^19(apps/web)
- 2026-07-12、大型モニタ向けの対策(別コミット)の直後に同日中で対応
この記事の根拠
- CSSファイル 2490〜2510行目コミット a9f97dd
- CSSファイル 2499〜2707行目コミット 84b1689
本文の主張は、上の記録に書かれていることだけです。運用しているリポジトリは非公開のため リンクは張れませんが、どのファイルの何行目を、どのコミット時点で見て書いたかは 記事ごとに残しています。推測で書いた箇所はありません。