問題
Chrome の Headless モードで HTML を PNG 画像にキャプチャする際、画面下部に**白い余白(白帯)**が発生することがあります。
google-chrome --headless --screenshot=output.png \
--window-size=1920,1080 \
--hide-scrollbars \
--force-device-scale-factor=1 \
file:///path/to/slide.html
HTML 側で width: 1920px; height: 1080px を指定しているにもかかわらず、生成された画像の下部に白い帯が残り、bottom で配置した要素(テロップ、フッターなど)が切れてしまいます。
原因
--window-size=1920,1080 はブラウザの外部ウィンドウサイズを指定するオプションであり、実際の**ビューポート(描画領域)**は若干小さくなります。Headless モードでもこのズレは発生します。
つまり:
--window-size=1920,1080→ 実際のビューポートは約 1920×1058 程度- HTML は 1080px の高さで描画しようとする
- ビューポートに収まらない下部のコンテンツが見切れる
- スクリーンショットは 1920×1080 で出力されるが、下部はデフォルト背景色(白)で埋まる
html や body に height: 1080px を指定しても、Chrome が実際に確保するビューポート高さとは一致しないため、CSS だけでは解決できません。
解決方法
ウィンドウサイズを大きめに設定し、スクリーンショット後に Pillow でクロップするのが最も確実です。
1. Chrome のウィンドウサイズを大きくする
google-chrome --headless --screenshot=output.png \
--window-size=1920,1280 \
--hide-scrollbars \
--force-device-scale-factor=1 \
file:///path/to/slide.html
高さを 1280 に変更することで、1080px のコンテンツが確実にビューポート内に収まります。
2. Pillow で正確にクロップする
from PIL import Image
img = Image.open("output.png")
img = img.crop((0, 0, 1920, 1080))
img.save("output.png")
これにより、余分な下部領域が除去され、正確に 1920×1080 の画像が得られます。
Python での実装例
import subprocess
import tempfile
import os
from PIL import Image
def render_html_to_png(html_content: str, output_png: str):
"""HTML を Chrome Headless で 1920x1080 の PNG にレンダリングする。"""
with tempfile.NamedTemporaryFile(
suffix=".html", mode="w", encoding="utf-8", delete=False
) as f:
f.write(html_content)
html_path = f.name
try:
subprocess.run(
[
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"--headless",
"--disable-gpu",
"--no-sandbox",
f"--screenshot={os.path.abspath(output_png)}",
"--window-size=1920,1280", # 大きめに設定
"--hide-scrollbars",
"--force-device-scale-factor=1",
f"file://{os.path.abspath(html_path)}",
],
capture_output=True,
timeout=120,
)
# 正確に 1920x1080 にクロップ
if os.path.exists(output_png):
img = Image.open(output_png)
img = img.crop((0, 0, 1920, 1080))
img.save(output_png)
finally:
os.unlink(html_path)
HTML 側の推奨構成
html / body のサイズ指定に頼らず、固定サイズの div コンテナですべてのコンテンツを囲むのが安全です。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { margin: 0; padding: 0; overflow: hidden; }
#stage {
width: 1920px;
height: 1080px;
position: relative;
overflow: hidden;
background: linear-gradient(135deg, #e8f5e9, #a5d6a7);
}
.footer {
position: absolute;
bottom: 30px;
left: 30px;
right: 30px;
/* bottom 配置の要素も確実に表示される */
}
</style>
</head>
<body>
<div id="stage">
<!-- すべてのコンテンツをここに配置 -->
<div class="footer">テロップなど</div>
</div>
</body>
</html>
ポイント:
#stageに固定のwidth/heightを設定し、position: relativeで基準にするhtml/bodyのサイズには依存しないoverflow: hiddenで余分な描画を防止
まとめ
| 対策 | 内容 |
|---|---|
| Chrome 側 | --window-size=1920,1280 で高さを余裕を持たせる |
| 後処理 | Pillow で (0, 0, 1920, 1080) にクロップ |
| HTML 側 | 固定サイズの #stage コンテナを使う |
この組み合わせにより、--screenshot での白帯問題を確実に回避できます。動画生成パイプラインでスライド画像やテロップフレームを大量に生成する場合にも安定して動作します。