import { spawn } from "node:child_process"; import { writeFile } from "node:fs/promises"; import { setTimeout as wait } from "node:timers/promises"; const CHROME = "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"; const PORT = 9333; const PROFILE = "C:\\Users\\10419\\AppData\\Local\\Temp\\cs-narrow-shot"; const OUT = process.env.SHOT_OUT || "d:/ClaudeProjects/虚拟明星/UI-UX/docs/screenshots/nav-overlap-narrow.png"; const WIDTH = Number(process.env.SHOT_WIDTH || 740); const HEIGHT = Number(process.env.SHOT_HEIGHT || 1000); const proc = spawn(CHROME, [ `--headless=new`, `--disable-gpu`, `--remote-debugging-port=${PORT}`, `--user-data-dir=${PROFILE}`, `--hide-scrollbars`, `--no-first-run`, `about:blank`, ], { stdio: "ignore", detached: true }); proc.unref(); for (let i = 0; i < 30; i++) { try { if ((await fetch(`http://127.0.0.1:${PORT}/json/version`)).ok) break; } catch {} await wait(300); } const t = await (await fetch(`http://127.0.0.1:${PORT}/json/new?about:blank`, { method: "PUT" })).json(); const ws = new WebSocket(t.webSocketDebuggerUrl); await new Promise(r => ws.addEventListener("open", () => r(), { once: true })); let id = 0; const pending = new Map(); ws.addEventListener("message", (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const { resolve, reject } = pending.get(m.id); pending.delete(m.id); if (m.error) reject(new Error(m.error.message)); else resolve(m.result); } }); const cmd = (method, params = {}) => new Promise((resolve, reject) => { pending.set(++id, { resolve, reject }); ws.send(JSON.stringify({ id, method, params })); }); await cmd("Emulation.setDeviceMetricsOverride", { width: WIDTH, height: HEIGHT, deviceScaleFactor: 1, mobile: false, }); await cmd("Page.navigate", { url: "http://localhost:3000" }); await wait(3500); await cmd("Runtime.evaluate", { expression: `document.querySelectorAll('video').forEach(v => { try { v.pause(); } catch{} });`, }); await wait(500); const r = await cmd("Page.captureScreenshot", { format: "png" }); await writeFile(OUT, Buffer.from(r.data, "base64")); // 同时取关键元素的位置信息 const layout = await cmd("Runtime.evaluate", { returnByValue: true, expression: `(() => { const get = sel => { const el = document.querySelector(sel); if (!el) return null; const r = el.getBoundingClientRect(); return { top: Math.round(r.top), left: Math.round(r.left), width: Math.round(r.width), height: Math.round(r.height), text: el.textContent?.trim().slice(0, 30) ?? '' }; }; return { headerNav: get('header > nav'), mobileNavLinks: get('header ul.md\\\\:hidden') || get('header [class*="md:hidden"] ul') || (() => { // mobile NavLinks 在 header 下方第二行,找 header 内最后一个 ul const uls = document.querySelectorAll('header ul'); const last = uls[uls.length - 1]; if (!last) return null; const r = last.getBoundingClientRect(); return { top: Math.round(r.top), left: Math.round(r.left), width: Math.round(r.width), height: Math.round(r.height), text: last.textContent?.trim().slice(0, 30) ?? '' }; })(), heroEyebrow: (() => { const els = Array.from(document.querySelectorAll('p')); const m = els.find(p => /Top 12.*Cyber Star/i.test(p.textContent || '')); if (!m) return null; const r = m.getBoundingClientRect(); return { top: Math.round(r.top), left: Math.round(r.left), width: Math.round(r.width), height: Math.round(r.height), text: m.textContent?.trim().slice(0, 40) ?? '' }; })(), heroProgress: get('[data-hero-vote-progress]'), }; })()`, }); console.log("\n=== 关键元素位置(viewport 坐标)==="); for (const [k, v] of Object.entries(layout.result.value)) { if (v) console.log(` ${k.padEnd(16)} top=${String(v.top).padStart(3)}px left=${String(v.left).padStart(3)}px w=${v.width} h=${v.height} "${v.text}"`); else console.log(` ${k.padEnd(16)} (未找到)`); } console.log(`\n✓ ${OUT}`); ws.close(); try { process.kill(proc.pid); } catch {} spawn("taskkill", ["/F", "/PID", String(proc.pid), "/T"], { stdio: "ignore" });