1
0

gen_deck_thumbs.mjs 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. #!/usr/bin/env node
  2. /**
  3. * gen_deck_thumbs.mjs — 为多文件 deck 每页生成缩略图(给 deck_index.html 的「无限画廊」概览用)。
  4. *
  5. * 背景:deck_index.html 有两种概览——
  6. * · 网格 grid(默认 60%):用 iframe 渲染真实子页面,清晰、所见即所得,无需缩略图。
  7. * · 无限画廊 gallery(40%):把所有页无缝无限平铺 + 缓慢漂移,几十~上百个瓦片若都用 iframe 会很卡,
  8. * 所以画廊改用 <img> 缩略图——同一张图复用多次浏览器只解码一次,流畅。
  9. * 本脚本就是给画廊准备这批缩略图。grid 模式不需要它。
  10. *
  11. * 用法(复制到 deck 项目根目录,装依赖后运行):
  12. * npm install playwright sharp
  13. * node gen_deck_thumbs.mjs --slides slides --out thumbs [--width 1600] [--quality 86]
  14. *
  15. * 然后在 index.html 的 MANIFEST 给每项加 thumb(与 file 同名 .jpg):
  16. * { file: "slides/01-cover.html", thumb: "thumbs/01-cover.jpg", label: "封面" }
  17. * deck_index.html 仅在画廊模式用 thumb;网格模式始终用 file(iframe)。没有 thumb 时画廊回退 iframe。
  18. *
  19. * 提示:缩略图分辨率别太低(默认 1600px),否则画廊里卡片 hover 放大后会发虚。
  20. */
  21. import { chromium } from 'playwright';
  22. import sharp from 'sharp';
  23. import fs from 'fs';
  24. import path from 'path';
  25. const arg = (n, d) => { const i = process.argv.indexOf('--' + n); return i > -1 && process.argv[i + 1] ? process.argv[i + 1] : d; };
  26. const slidesDir = arg('slides', 'slides');
  27. const outDir = arg('out', 'thumbs');
  28. const width = parseInt(arg('width', '1600'), 10);
  29. const quality = parseInt(arg('quality', '86'), 10);
  30. const W = parseInt(arg('canvas-w', '1920'), 10);
  31. const H = parseInt(arg('canvas-h', '1080'), 10);
  32. if (!fs.existsSync(slidesDir)) { console.error('找不到 slides 目录: ' + slidesDir); process.exit(1); }
  33. fs.mkdirSync(outDir, { recursive: true });
  34. const files = fs.readdirSync(slidesDir).filter(f => f.endsWith('.html')).sort();
  35. if (!files.length) { console.error('slides 目录里没有 .html'); process.exit(1); }
  36. const browser = await chromium.launch();
  37. const page = await browser.newPage({ viewport: { width: W, height: H }, deviceScaleFactor: 1 });
  38. let ok = 0;
  39. for (const f of files) {
  40. const base = f.replace(/\.html$/, '');
  41. const out = path.join(outDir, base + '.jpg');
  42. try {
  43. await page.goto('file://' + path.resolve(slidesDir, f), { waitUntil: 'load' });
  44. await page.waitForTimeout(2800); // 等 webfont / 图片 paint
  45. const buf = await page.screenshot({ type: 'png', clip: { x: 0, y: 0, width: W, height: H } });
  46. await sharp(buf).resize(width).jpeg({ quality }).toFile(out);
  47. ok++; console.log('[ok] ' + out);
  48. } catch (e) { console.error('[FAIL] ' + f + ': ' + e.message); }
  49. }
  50. await browser.close();
  51. console.log(`\n=== ${ok}/${files.length} 张缩略图 → ${outDir}/ ===`);
  52. console.log('在 index.html 的 MANIFEST 每项加 thumb: "' + outDir + '/<同名>.jpg"(仅画廊模式用到)');