Files
app-261004/scripts/screenshot.mjs
T
2026-10-08 11:09:37 +08:00

87 lines
2.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 通过 CDP 在精确的视口尺寸下截图(含整页截图)。
*
* 为什么不直接用 `chrome --headless --screenshot --window-size`:
* 该模式下截图位图宽度与 CSS 视口宽度可能不一致,导致移动端截图
* 看起来"内容被截断",实际测量(document.scrollWidth)却是正常的。
* 这里用 Emulation.setDeviceMetricsOverride 明确指定视口,
* 保证截图与真实设备表现一致。
*
* 用法:CDP_WS=<ws地址> node scripts/screenshot.mjs <url> <width> <height> <输出路径> [--full]
*/
import { writeFileSync } from "node:fs";
const [, , targetUrl, widthArg, heightArg, outputPath, ...flags] = process.argv;
const width = Number(widthArg ?? 1440);
const height = Number(heightArg ?? 900);
const fullPage = flags.includes("--full");
const wsUrl = process.env.CDP_WS;
if (!wsUrl || !targetUrl || !outputPath) {
console.error(
"用法:CDP_WS=<ws地址> node scripts/screenshot.mjs <url> <width> <height> <输出路径> [--full]",
);
process.exit(1);
}
const socket = new WebSocket(wsUrl);
let messageId = 0;
const pending = new Map();
function send(method, params = {}) {
const id = ++messageId;
socket.send(JSON.stringify({ id, method, params }));
return new Promise((resolve, reject) => {
pending.set(id, { resolve, reject });
setTimeout(() => {
if (pending.has(id)) {
pending.delete(id);
reject(new Error(`CDP 超时: ${method}`));
}
}, 40000);
});
}
socket.addEventListener("message", (event) => {
const message = JSON.parse(event.data);
if (message.id && pending.has(message.id)) {
const { resolve } = pending.get(message.id);
pending.delete(message.id);
resolve(message.result);
}
});
socket.addEventListener("open", async () => {
await send("Page.enable");
await send("Runtime.enable");
await send("Emulation.setDeviceMetricsOverride", {
width,
height,
deviceScaleFactor: 1,
mobile: width < 768,
});
await send("Page.navigate", { url: targetUrl });
// 等待字体加载与首屏动画稳定
await new Promise((resolve) => setTimeout(resolve, 4500));
// 让所有延迟出现的元素(滚动动画)进入最终状态
await send("Runtime.evaluate", {
expression:
"window.scrollTo(0, document.body.scrollHeight); window.scrollTo(0, 0);",
});
await new Promise((resolve) => setTimeout(resolve, 1200));
const screenshot = await send("Page.captureScreenshot", {
format: "png",
captureBeyondViewport: fullPage,
optimizeForSpeed: false,
});
writeFileSync(outputPath, Buffer.from(screenshot.data, "base64"));
console.log(`已保存 ${outputPath} (${width}x${height}${fullPage ? ", 整页" : ""})`);
socket.close();
process.exit(0);
});