Files
dusuniot/scripts/forensic-posts-cards.ts

65 lines
3.1 KiB
TypeScript

/**
* # 脚本 取证 posts 卡片缩略图(本地 vs 原站,390px 移动视口)
* 对比每张 .elementor-post__thumbnail img 的渲染盒与当前选中资源(srcset 候选),
* 定位遗留表 #6 移动端个别卡 ±5px 差异。
* 用法:bun scripts/forensic-posts-cards.ts <path>
*/
import { chromium } from "playwright-core";
const CHROME = process.env.HOME + "/.cache/ms-playwright/chromium-1243/chrome-linux64/chrome";
const LOCAL = "http://127.0.0.1:3312";
const LIVE = "https://www.dusuniot.com";
const path = process.argv[2] || "/";
/** # 函数 抓取页面上全部 posts 卡片缩略图盒 */
async function measure(base: string) {
const browser = await chromium.launch({ executablePath: CHROME, headless: true });
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
await page.goto(base + path, { waitUntil: "networkidle", timeout: 60000 });
// 逐步滚到底触发懒加载图片,再滚回首个 posts 区块停稳(避免离开视口后浏览器取消加载)
await page.evaluate(async () => {
for (let y = 0; y < document.body.scrollHeight; y += 600) {
window.scrollTo(0, y);
await new Promise((r) => setTimeout(r, 120));
}
const t = document.querySelector(".elementor-posts-container");
if (t) t.scrollIntoView({ block: "start" });
});
await page.waitForTimeout(3000);
const data = await page.evaluate(() => {
return Array.from(document.querySelectorAll(".elementor-post__thumbnail img")).map((img) => {
const r = img.getBoundingClientRect();
const card = img.closest(".elementor-post__card")!.getBoundingClientRect();
const title = (img.closest("article")?.querySelector(".elementor-post__title")?.textContent || "").trim().slice(0, 40);
return {
title,
img: { x: +r.x.toFixed(1), y: +r.y.toFixed(1), w: +r.width.toFixed(1), h: +r.height.toFixed(1) },
cardH: +card.height.toFixed(1),
cur: (img as HTMLImageElement).currentSrc.split("/uploads/")[1] || (img as HTMLImageElement).currentSrc,
nw: (img as HTMLImageElement).naturalWidth,
nh: (img as HTMLImageElement).naturalHeight,
};
});
});
await browser.close();
return data;
}
const [local, live] = await Promise.all([measure(LOCAL), measure(LIVE)]);
console.log("=== 本地 ===");
for (const d of local) console.log(JSON.stringify(d));
console.log("=== 原站 ===");
for (const d of live) console.log(JSON.stringify(d));
console.log("=== 差异 ===");
const n = Math.max(local.length, live.length);
for (let i = 0; i < n; i++) {
const a = local[i], b = live[i];
if (!a || !b) { console.log(i, "缺侧", !!a, !!b); continue; }
const dh = +(a.img.h - b.img.h).toFixed(1);
const dch = +(a.cardH - b.cardH).toFixed(1);
const same = a.cur === b.cur;
if (Math.abs(dh) > 0.5 || Math.abs(dch) > 0.5 || !same)
console.log(`#${i} [${a.title}] imgH ${a.img.h} vs ${b.img.h} (${dh > 0 ? "+" : ""}${dh}) cardH ${a.cardH} vs ${b.cardH} (${dch > 0 ? "+" : ""}${dch}) 资源${same ? "同" : "异: " + a.cur + " <-> " + b.cur} nat ${a.nw}x${a.nh} vs ${b.nw}x${b.nh}`);
}
console.log("完成");