/** * 通过 CDP 在精确的视口尺寸下截图(含整页截图)。 * * 为什么不直接用 `chrome --headless --screenshot --window-size`: * 该模式下截图位图宽度与 CSS 视口宽度可能不一致,导致移动端截图 * 看起来"内容被截断",实际测量(document.scrollWidth)却是正常的。 * 这里用 Emulation.setDeviceMetricsOverride 明确指定视口, * 保证截图与真实设备表现一致。 * * 用法:CDP_WS= node scripts/screenshot.mjs <输出路径> [--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= node scripts/screenshot.mjs <输出路径> [--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); });