【Astro×Mermaid&自動検証の壁突破】27インチ5K iMacで図解が巨大化する問題と、Playwright『WebP 16,383px上限 0バイト画像クラッシュ』解決録
作成日:
更新日:

【Astro×Mermaid&自動検証の壁突破】27インチ5K iMacで図解が巨大化する問題と、Playwright『WebP 16,383px上限 0バイト画像クラッシュ』解決録


🔧 論旨

技術解説や構造図をテキストだけで美しく描ける「Mermaid(マーメイド)図解」は非常に便利ですが、Astro サイトへ導入して 27 インチ 5K iMac(論理解像度 2560×1440)の大画面で開いた瞬間、縦長のフローチャートが横 2,400px × 縦 6,060px という怪物サイズに巨大化してしまう問題 に直面しました。さらに自動ブラウザ検証ツール(Playwright MCP)で縦長記事の全体スクリーンショットを撮ろうとすると、突然 0 バイト の空画像が生成されて AI チャットごとクラッシュする という別の罠まで潜んでいました。

本記事では、SVG の実描画領域(getBBox())と画面の幅・高さから最適サイズを自動算出する動的スケーリング関数と、スマホ・タブレット・フル HD・5K iMac の 4 画面すべてで全 37 個の図解を実機検証して見つけた改善則、そして WebP 規格の「最大 16,383px 上限」による 0 バイト画像クラッシュを 100% 防ぐ鉄則をまとめて解説します。
🔧 合わせて読みたい深まる関連情報
🔧 対象となる方
  • Astro(特に Astro 5 / 6 系)で Mermaid コードブロックを書いたら、Shiki シンタックスハイライターに分解されて図が描画されなかった方

  • レスポンシブ対応のつもりで SVG に width: 100% を当てた結果、大型モニターで図形と文字が 3〜4 倍に引き伸ばされて困っている方

  • 逆にスマホ画面(幅 375px 等)で見ると、横長の図やガントチャートが米粒のように潰れたり重なったりしてしまう方

  • Playwright MCP の browser_take_screenshot で縦長ページを撮影した直後に、AI エージェントがメディアエラーで復帰不能になった経験がある方

吹出しアイコン

なんじゃこりゃあ〜!!27 インチの iMac で自分の記事を開いてみたら、3 個しか四角がないシンプルなフローチャートなのに、画面の端から端まで看板みたいにデカデカと引き伸ばされて、スクロールしてもスクロールしても図が終わらんぞ!!

吹出しアイコン

おじさん、それは CSS でよくある「良かれと思って書いた width: 100% !important の罠」なんです!さらにスマホで開くと今度は逆に文字が豆粒になってしまうんですよ。大画面でもスマホでも「人間が一目で気持ちよく読めるサイズ」に自動調整する仕組みを作りましょう!


5K大画面でMermaid図解が巨大化する2つの原因

AstroのShiki分割と横幅100%強制の罠

まず Astro(Astro 5 / 6 系)で Mermaid を動かす際の最初の関門として、標準のコードハイライターである Shiki がコードブロック内の文字列を色分け用の <span> タグに細かく分解してしまうため、Mermaid 標準の自動初期化(startOnLoad: true)だけでは構文エラーになって描画されません。

そのため、pre[data-language="mermaid"] から純粋なテキスト(graph.textContent)だけを抜き出し、手動で mermaid.render(id, content) を呼んで SVG に置き換えるスクリプトを組むのが定石です。

ところが、描画された SVG に対して「画面からはみ出さないようにしよう」と単純に width: 100% !important; height: auto; を指定すると、広大なモニターで悲劇が起きます。

  • 画面を広く使うレイアウトで記事エリアの幅が 2,000px 以上あるとき、本来は横 450px でちょうど綺麗に読める縦長フローチャートまで 横 2,400px(約 5 倍)へ強制拡大 されます。
  • アスペクト比(縦横比)が保たれたまま横に 5 倍伸びるため、縦方向も 5 倍になり、7 段のフロー図がなんと 縦 6,060px(画面 4 枚分以上!) という超巨大サイズに化けてしまったのです。

机上の計算を裏切る4画面サイズ実機撮影の衝撃

そこで最初は計算式だけでスケーリングを入れてみたのですが、「本当にすべての端末で綺麗に見えているか?」を確かめるため、Playwright MCP を使ってサイト内の 全 37 個の図解を 4 つの画面サイズ(スマホ 375×812・iPad 768×1024・フル HD 1920×1080・5K iMac 2560×1440)で実際に開いて総点検 しました。

すると、机上の計算だけでは絶対に気づけなかった「実物の崩れ」がボロボロ見つかったのです!

  1. 円グラフ(pie チャート)の謎の巨大余白 : Mermaid が生成する円グラフの viewBox 横幅が、実際の円と凡例の描画幅より何百ピクセルも広く取られており、そのせいで円本体が不自然に小さく縮んでいました。
  2. スマホでの gantt(工程表)や横長 LR 図の文字潰れ : 横幅 1,500px ある横長の図をスマホ幅 340px にそのまま縮小すると、文字が 3px 以下の米粒になって判読不能になっていました。
  3. サブグラフ見出しと矢印の貫通・引用符漏れ : 日本語記号を含む subgraph "見出し(補足)" でダブルクォートを忘れた構文エラーや、横並び要素が多すぎて線が文字を貫通している箇所がありました。

解像度と図形サイズに応じる動的スケーリング実装

getBBox余白除去と3つの上限値による最適化

この 4 画面実機テストの結果を踏まえ、レイアウト側の自動調整関数(fitMermaidSvg)を次の 4 段階アルゴリズム へ進化させました。

🔧 📐 どのモニターでも美しく収まる『fitMermaidSvg』4つの計算ルール
  1. svgElement.getBBox() による過剰余白の自動トリミング : 元の viewBox 幅が、実際に図形が描かれているバウンディングボックス(bbox.width)より 80px 以上広い場合(円グラフ等)、実描画領域+適度な左右余白だけに viewBox を自動で切り詰めます。

  2. 上限①:図本来の自然幅の 1.08倍 まで(過拡大防止) : 要素が少ないコンパクトな図が 5K モニターで看板のように引き伸ばされないよう、図本来の幅(vbWidth)の 1.08倍 を上限とし、図内の文字サイズ(15px)が記事本文と自然に揃うようにします。

  3. 上限②:画面横幅の 82% かつ最大 1,160px まで : 横に広いシーケンス図や構成図も、大型モニター上で視線が左右に迷わない最大 1,160px で中央寄せにします。

  4. 上限③:画面高さの 75%(75vh)から逆算した 1 画面収まり幅+最小可読幅の保証 : 縦に長いフロー図は、スクロールせずに全体像が一目で見渡せるよう 75vh × (幅 / 高さ) で最適幅を逆算します。ただし、縦長すぎて文字が潰れるのを防ぐため、minReadableWidth(最低 440px または元幅の 72%)を下限として保証します。

// Astroレイアウト内の is:inline スクリプトで動かす解像度適応型スケーリング関数
const fitMermaidSvg = (svgElement) => {
  if (!svgElement) return;
  const vw = window.innerWidth || document.documentElement.clientWidth || 1200;
  const vh = window.innerHeight || document.documentElement.clientHeight || 800;
  const isMobile = vw <= 768;

  let vbWidth = 0;
  let vbHeight = 0;
  try {
    const bbox = svgElement.getBBox();
    if (bbox && bbox.width > 10 && bbox.height > 10) {
      const origVb = svgElement.getAttribute('data-orig-viewbox') || svgElement.getAttribute('viewBox') || '';
      if (!svgElement.getAttribute('data-orig-viewbox') && origVb) {
        svgElement.setAttribute('data-orig-viewbox', origVb);
      }
      const parts = origVb.trim().split(/[\s,]+/).map(Number);
      // 円グラフ等で元のviewBoxが実描画幅より80px以上広い場合は余白を自動トリミング
      if (parts.length === 4 && parts[2] > bbox.width + 80) {
        const padLeft = 24, padRight = 64, padY = 20;
        vbWidth = Math.ceil(bbox.width + padLeft + padRight);
        vbHeight = Math.ceil(bbox.height + padY * 2);
        svgElement.setAttribute('viewBox', `${Math.floor(bbox.x - padLeft)} ${Math.floor(bbox.y - padY)} ${vbWidth} ${vbHeight}`);
      }
    }
  } catch (e) {}

  if (vbWidth <= 0 || vbHeight <= 0) {
    const parts = (svgElement.getAttribute('viewBox') || '').trim().split(/[\s,]+/).map(Number);
    if (parts.length === 4 && parts[2] > 0 && parts[3] > 0) {
      vbWidth = parts[2];
      vbHeight = parts[3];
    }
  }

  svgElement.removeAttribute('height');
  svgElement.style.display = 'block';
  svgElement.style.margin = '0 auto';
  svgElement.style.height = 'auto';

  if (isMobile) {
    const availW = Math.max(vw - 38, 280);
    svgElement.style.width = '100%';
    svgElement.style.maxWidth = vbWidth > 0 ? Math.min(availW, Math.round(vbWidth * 1.1)) + 'px' : '100%';
    return;
  }

  const naturalLimit = vbWidth > 0 ? vbWidth * 1.08 : 960;
  const screenWidthLimit = Math.min(vw * 0.82, 1160);
  let heightBasedLimit = 1160;
  if (vbWidth > 0 && vbHeight > 0) {
    const maxAllowedHeight = Math.max(vh * 0.75, 460);
    const rawHeightLimit = maxAllowedHeight * (vbWidth / vbHeight);
    const minReadableWidth = Math.min(vbWidth * 0.9, Math.max(vbWidth * 0.72, 440));
    heightBasedLimit = Math.max(rawHeightLimit, minReadableWidth);
  }

  const optimalMaxWidth = Math.round(Math.min(naturalLimit, screenWidthLimit, heightBasedLimit));
  svgElement.style.width = '100%';
  svgElement.style.maxWidth = optimalMaxWidth + 'px';
};

あわせて原稿側の図解も、スマホで崩れる gantt を廃止して縦方向のフロー図(flowchart TD)や Markdown テーブルへ組み直しました。

5K画面とスマホで実測した劇的な改善データ

27 インチ 5K iMac(2560×1440)上で計測した改修前後の実寸比較がこちらです。

図解のタイプ改修前(width: 100% 強制)改修後(fitMermaidSvg 適用)視覚的な変化
標準の縦フロー図横 2,400px × 縦 2,850px横 557px × 縦 661px本文の文字サイズと綺麗に揃い、中央に品よく収まる。
縦7段の長大フロー図横 2,400px × 縦 6,060px横 410px × 縦 1,035px画面4枚分だった図が、スクロールなしで1画面に見渡せる。
横広のシーケンス図横 2,400px × 縦 1,170px横 1,080px × 縦 528px首を左右に振らずに一目で追える最適サイズに収束。

Playwright縦長スクショの0バイト画像クラッシュ

WebP規格の最大16,383pxという14ビットの壁

そして、この実機検証の過程でもう 1 つ発見した AI 開発者必見の重大トラップ が、Playwright MCP のスクリーンショット機能による 「0 バイト画像クラッシュ」 です。

写真が 30〜60 枚入った長編レビュー記事など、ページ全体の縦の長さ(document.documentElement.scrollHeight)が非常に長いページに対して、Playwright MCP の browser_take_screenshot ツールを fullPage: true かつ type: "webp" で呼び出すと、突然 AI エージェントが Agent execution terminated due to error を出して停止し、何度リトライボタンを押しても二度とそのチャットが動かせなくなる事故が起きました。

原因を追跡すると、WebP 画像フォーマットの国際規格そのものにある「14 ビット最大ピクセル寸法上限(縦・横ともに最大 16,383px まで)」 に行き着きました!

  • WebP のヘッダー仕様では、画像の幅と高さ(マイナス 1)をそれぞれ 14 ビット整数(214=16,384→2^{14} = 16,384 \rightarrow 最大 16,383px、Retina 2 倍解像度なら論理高さ約 8,191px 相当で到達する場合も)で格納します。
  • そのため、高さ 16,383px を超える縦長ページを fullPage: true, type: "webp" で撮影すると、ブラウザ内部の WebP エンコーダが変換に失敗して 中身が 0 バイト(空っぽ)の壊れた .webp ファイル を吐き出します。
  • その 0 バイト 画像がそのまま AI チャットの会話履歴(マルチモーダル入力)に添付されるため、以降の全ターンで API 側が「不正な空メディアデータ」としてリクエストを拒否し、会話ごと復帰不能になっていたのです!
吹出しアイコン

ひゃあ〜!ただ画面全体のスクショを WebP で撮ろうとしただけで、16,383 ピクセルを超えると 0 バイトの爆弾画像ができてチャットごと壊れちゃうなんて、知らないと絶対ハマるわね!!

吹出しアイコン

本当に怖い罠ですよね!でも原因さえ分かってしまえば、対策はとっても簡単で 100% 確実に防げます!

会話を壊さないPNG指定とDOM計測の鉄則

AI エージェントに Playwright MCP で画面確認をさせる際は、ルールファイル(GEMINI.md 等)に次の 3 原則を明記しておくだけで、このクラッシュは永久にゼロになります。

🔧 🛡️ Playwrightスクリーンショットで0バイトクラッシュを防ぐ3原則
  1. fullPage: true と type: "webp" の併用を絶対禁止にする : WebP の 16,383px 上限を踏まないよう、スクリーンショット撮影時は必ず type: "png"(PNG は 32 ビット寸法のため上限の心配なし)を使うか、fullPage: false(現在見えているビューポートのみ撮影) を基本にします。

  2. ページ全体の画像切れや横はみ出し確認には browser_evaluate を併用する : 何万ピクセルもある巨大画像を丸ごと AI に読ませるよりも、browser_evaluate で JavaScript を走らせて「全 <img> の naturalWidth > 0 チェック」「TOC 各行の折り返し高さチェック」「横スクロール発生チェック」を数値で取得し、要点箇所だけを fullPage: false, type: "png" で撮影する方が速くて確実です。

  3. 保存先 filename にワークスペース外のパスを指定しない : Playwright MCP のセキュリティ制限による File access denied を防ぐため、filename は省略するかワークスペース内の相対パスのみを使い、確認後は一時画像を削除してクリーンに保ちます。


まとめ:大画面からスマホまで実物を見て仕上げる

「手元のノート PC 画面で綺麗に見えたからヨシ!」と思って公開していても、27 インチ 5K モニターやスマホの縦画面で開くと、図解が怪物のように膨張していたり豆粒に潰れていたりすることは珍しくありません。

  1. Mermaid 図解は width: 100% で放置せず、getBBox() 余白トリミング+自然幅・画面幅・画面高さ(75vh)の 3 上限で動的スケーリングする
  2. スマホで文字が重なる gantt や極端な横長図は、縦フロー(TD)や Markdown テーブルへ潔く組み替える
  3. Playwright での自動検証時は WebP の 16,383px 上限を忘れず、type: "png", fullPage: false + DOM 計測で安全確実にチェックする

図解のサイズがピタッと画面に調和すると、記事全体の品格と読みやすさが一気に跳ね上がります。ぜひご自身のサイトの図解も大画面とスマホの両方で見比べてみてください!

ちょうど良いサイズのクッションにぴったり収まって眠るノルウェージャンフォレストキャット

▲ 大きすぎず小さすぎない「ジャストサイズ」が一番心地いいのは、図解も猫様も同じですね