導入/背景
ウェブ画面の特定位置を正確にスクリーンショットとして保存したい場面は珍しくありません。UIの差分確認、デザインレビュー、バナーの掲出位置の確認、あるいはサポートのための再現画像作成など、用途は幅広いです。ところが、人手での操作は再現性が低く、ピクセル単位の位置合わせも難しいのが実情です。本稿では、Puppeteerによる自動化と、ブラウザだけで完結する小さな補助ツールを組み合わせ、狙った位置を簡潔に、かつ再現性高く撮影する手順を解説します。日々の運用で役立つ地味な工夫が、後の工数削減に効きます。
技術的な話
Puppeteerとは
PuppeteerはNode.jsでChromeやChromiumブラウザを自動操作するライブラリです。ページを開く、クリック、スクリーンショット撮影などを、プログラムから指示できます。人間の手作業と違って毎回同じ条件で実行できるため、テストや画像生成の自動化でよく使われています。
本節は「コピペでOK」を基本方針とします。以下の順に示します。
- HUD表示+クリックでY座標をコピー(ブックマークレット、トグル式)
- URLとY座標を指定して撮影するPuppeteerスクリプト(Node.js)

こちらは実際にスクリプトを実行し、撮った画像です。
1. HUD表示+クリックでコピー(ブックマークレット、トグル式)
以下をブックマークのURL欄に貼り付けて保存します。ブックマーク実行で左上にHUD(Y/H/VH)が表示され、任意の位置をクリックすると現在のスクロール量(window.scrollY)をコピーします。Shift+クリックで80px差し引き(固定ヘッダー想定)。もう一度ブックマークを押すとHUDと待受けが解除されます。リンク等の既定動作は抑止します。
javascript:(()=>{const M_KEY='__ys_hud_manager__';if(window[M_KEY]){const{hud,onScroll,onResize,onClick}=window[M_KEY];window.removeEventListener('scroll',onScroll);window.removeEventListener('resize',onResize);document.removeEventListener('click',onClick,true);if(hud){hud.remove()};delete window[M_KEY];return};const hud=document.createElement('div');Object.assign(hud.style,{position:'fixed',top:'8px',left:'8px',zIndex:2147483647,padding:'4px 8px',background:'rgba(0,0,0,.75)',color:'#fff',font:'12px/1.4 monospace',borderRadius:'4px',pointerEvents:'none',whiteSpace:'pre'});document.documentElement.appendChild(hud);let lastMsgAt=0;const metrics=()=>%60Y:${Math.round(window.scrollY)} H:${document.documentElement.scrollHeight} VH:${window.innerHeight}%60;const render=()=>{if(Date.now()-lastMsgAt>1200){hud.textContent=metrics()}};const copyText=(t)=>{try{navigator.clipboard.writeText(String(t));hud.textContent=%60Copied Y: ${t}%60}catch(e){prompt('Copy Y:',String(t));hud.textContent=%60Shown in prompt: ${t}%60}lastMsgAt=Date.now();setTimeout(render,1300)};const onScroll=()=>render();const onResize=()=>render();const onClick=(e)=>{e.preventDefault();e.stopPropagation();if(e.stopImmediatePropagation)e.stopImmediatePropagation();const off=e.shiftKey?80:0;const y=Math.round(window.scrollY-off);copyText(y)};window.addEventListener('scroll',onScroll,{passive:true});window.addEventListener('resize',onResize);document.addEventListener('click',onClick,true);render();window[M_KEY]={hud,onScroll,onResize,onClick};})()
安全上の注意/免責
- ブックマークレットは「開いているページの権限」で動きます。機密情報を含むページ(社内管理画面、銀行、認証中のタブ等)では実行しないでください。
- 掲載コードは短いので、実行前に必ず全文を自分で確認してください。ブックマークに保存した後も、内容が意図どおりか見直すと安心です。
- 当スニペットはクリップボードへの書き込みを行います(ブラウザが許可を求める場合があります)。
- 本コードの利用は自己責任でお願いします。実行によって生じたいかなる損害についても、筆者は責任を負いません。
2. URLとY座標を指定して撮影するPuppeteerスクリプト(Node.js)
次のファイルを shot.js として保存し、npm i puppeteer を行ったディレクトリで実行します。node shot.js <url> <y> [out] の形式で、URLとY座標(px)を指定できます。出力ファイル名を省略すると、YYYYMMDD_HHMMSS_<title>.png で自動命名します。
// shot.js
// shot.js
const puppeteer = require('puppeteer');
const {setTimeout: delay} = require('timers/promises');
// Usage: node shot.js <url> <y> [out]
// - <url>: target URL (required)
// - <y>: scroll position in px (optional)
// - [out]: output filename (optional). If omitted, it becomes
// YYYYMMDD_HHMMSS_<title>.png based on the page title.
(async () => {
const argv = process.argv.slice(2);
if (argv.includes('--help') || argv.includes('-h')) {
console.log('Usage: node shot.js <url> <y> [out]\n <url>: target URL (required)\n <y>: scroll Y in px (optional)\n [out]: output filename (optional). If omitted, it becomes YYYYMMDD_HHMMSS_<title>.png');
process.exit(0);
}
const urlArg = argv[0];
const yArg = argv[1] !== undefined ? Number(argv[1]) : undefined;
const outArg = argv[2];
if (!urlArg) {
console.error('URL is required.\nUsage: node shot.js <url> <y> [out]');
process.exit(1);
}
const url = urlArg;
// outPath will be decided after the page title is available
let outPath;
let browser;
try {
browser = await puppeteer.launch({
headless: true,
args: [
// CI/コンテナ環境での起動安定化(必要ない環境では無視されます)
'--no-sandbox',
'--disable-setuid-sandbox',
],
});
const page = await browser.newPage();
await page.setViewport({ width: 1600, height: 1200 });
await page.goto(url, { waitUntil: 'networkidle2', timeout: 60_000 });
if (!Number.isNaN(yArg) && yArg !== undefined) {
// ページの最大スクロール量にクランプしてから移動
await page.evaluate((y) => {
const max = Math.max(0, document.documentElement.scrollHeight - window.innerHeight);
const yy = Math.max(0, Math.min(max, Math.round(y)));
window.scrollTo(0, yy);
}, yArg);
}
await delay(200);
// Decide output filename: use arg/ENV or auto-generate: YYYYMMDD_HHMMSS_Title.png
if (!outPath) {
const title = (await page.title()) || 'untitled';
const safeTitle = title
.replace(/[\n\r\t]/g, ' ')
.replace(/[\\/:*?"<>|]/g, '')
.replace(/\s+/g, ' ')
.trim()
.replace(/ /g, '_')
.slice(0, 60);
const pad = (n) => String(n).padStart(2, '0');
const d = new Date();
const name = `${d.getFullYear()}${pad(d.getMonth()+1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}_${safeTitle || 'untitled'}`;
outPath = outArg || process.env.OUT || `${name}.png`;
}
await page.screenshot({ path: outPath, fullPage: false });
console.log(`Saved: ${outPath}`);
} catch (err) {
console.error('shot.js error:', err);
process.exitCode = 1;
} finally {
if (browser) {
await browser.close();
}
}
})();
実行例:
node shot.js https://example.com 1200 example.png
Y座標は前節の手段で取得すると効率が良いです。Puppeteer側では最大スクロール範囲にクランプしてから移動しているため、ページの長さが想定より短い場合でも安全です。
おまけ:ふつうに撮る手順(Linux Mint)
Puppeteerを使わない通常の撮影手順を簡潔にまとめておきます。
-
キーボード
- PrtScn: 全画面撮影
- Alt + PrtScn: アクティブウィンドウ
- Shift + PrtScn: 範囲選択
-
Chrome/Chromium で撮る(DevTools 標準)
- F12 または Ctrl+Shift+I で開発者ツールを開く
- Ctrl+Shift+P(Run command)→「screenshot」(日本語にしている方はカタカナで)
- Full size / Visible / Node から選んで保存
- ヒント: DevTools を別ウィンドウ(Dock side: undock)にするとページがフル幅のまま撮影できます。
-
コマンドラインから撮る
- gnome-screenshot(入っていれば)
gnome-screenshot -d 5 -f ~/Pictures/screenshot.png gnome-screenshot -a -d 5 -f ~/Pictures/selected.png - Mintの他エディションでよくあるコマンド
※ xfce4-screenshooter は —fullscreen / —window / —region のいずれか指定が必須。mate-screenshot -d 5 -f ~/Pictures/screenshot.png xfce4-screenshooter --fullscreen --delay 5 --save ~/Pictures/xfce4-screenshot.png
- gnome-screenshot(入っていれば)
-
Chrome/Chromium をヘッドレスで
google-chrome --headless --disable-gpu --window-size=1600,1200 \ --screenshot=output.png https://example.comchromium --headless --disable-gpu --window-size=1600,1200 \ --screenshot=output.png https://example.com- ブラウザUIやスクロールバーが入らない綺麗なキャプチャ。特定のY位置からの撮影は未対応。
-
撮影後のトリミング
- GUI: Pix / GIMP
- CLI: ImageMagick
convert screenshot.png -crop 800x600+100+100 cropped.png
設計上のトレードオフ
- 位置の決め方
- 固定ピクセル(本稿の
<y>)は単純で再現性が高い一方、レスポンシブレイアウトでは崩れに弱いです。 - 要素基準(CSSセレクタで目的要素へスクロール)に拡張すれば、レイアウト変化に強くなりますが、セレクタの管理コストが増します。
- 固定ピクセル(本稿の
- 表示の安定化
- ネットワーク待機を
networkidle2と短い待機(例: 200ms)で組み合わせて、遅延描画の揺らぎを抑えています。 - 画像や広告などの外部要素が多いページでは、さらに明示的な待機(特定セレクタの
waitForSelector)が有効です。
- ネットワーク待機を
- 実行環境
- コンテナ/CIでの安定動作のため
--no-sandboxを付けています。不要な環境では削除して構いません。
- コンテナ/CIでの安定動作のため
実運用での留意点
- 画面サイズは要件に合わせて固定(例:
1600x1200)するのが無難です。レビュー文化がある組織では、撮影基準の明文化が効果的です。 - フォントやレンダリングの差異が差分に影響するため、OSやヘッドレスのバージョンを固定します。
- 重要なビューはフルページ撮影の併用も検討します。遅延読み込みが強いページでは段階スクロール+複数撮影が安定します。
まとめ
狙った位置のスクリーンショットを再現性高く得るには、位置取得と撮影を分けて設計するのが近道です。ブラウザ側でY座標を素早く取得し、Puppeteerで同一条件のビューポートと待機条件を整えて撮影すれば、手戻りの少ない運用が実現します。小さな自動化の積み重ねが、最終的にはレビュー効率と品質の安定につながります。
ここまでこだわったのは、ただただスクロールバーが入るのが嫌だからという、しょうもない理由が燃料です。特に不揃いなのは好きではなく、4:3の比率は見やすいと思います。美しくあれ。
参考文献
- https://pptr.dev/ (Puppeteer公式ドキュメント)
- https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY (MDN:
window.scrollY) - https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollTo (MDN:
window.scrollTo) - https://nodejs.org/api/timers.html#timerspromises (Node.js: timers/promises)