# 可编辑 PPTX 导出:HTML 硬约束 + 尺寸决策 + 常见错误 本文档讲的是**用 `scripts/html2pptx.js` + `pptxgenjs` 把 HTML 逐元素翻译成真·可编辑 PowerPoint 文本框**的路径。和 `export_deck_pptx.mjs --mode image`(截图铺底、文字变图片、不可编辑)是两回事。 > **核心前提**:要走这条路,HTML 必须从第一行就按下面 4 条约束写。**不是写完再转**——事后补救会触发 2-3 小时返工(2026-04-20 期权私董会项目实测踩坑)。 --- ## 画布尺寸:用 960×540pt(LAYOUT_WIDE) PPTX 单位是 **inch**(物理尺寸),不是 px。决策原则:body 的 computedStyle 尺寸要**匹配 presentation layout 的 inch 尺寸**(±0.1",由 `html2pptx.js` 的 `validateDimensions` 强制检查)。 ### 3 个候选尺寸对比 | HTML body | 物理尺寸 | 对应 PPT layout | 何时选 | |---|---|---|---| | **`960pt × 540pt`** | **13.333″ × 7.5″** | **pptxgenjs `LAYOUT_WIDE`** | ✅ **默认推荐**(现代 PowerPoint 16:9 标配) | | `720pt × 405pt` | 10″ × 5.625″ | 自定义 | 仅当用户指定「老版 PowerPoint Widescreen」模板时 | | `1920px × 1080px` | 20″ × 11.25″ | 自定义 | ❌ 非标尺寸,投影后字体显得异常小 | **别把 HTML 尺寸当分辨率想。** PPTX 是矢量文档,body 尺寸决定的是**物理尺寸**不是清晰度。超大 body(20″×11.25″)不会让文字更清晰——只会让字号 pt 相对画布变小,投影/打印时反而更难看。 ### body 写法三选一(等价) ```css body { width: 960pt; height: 540pt; } /* 最清晰,推荐 */ body { width: 1280px; height: 720px; } /* 等价,px 习惯 */ body { width: 13.333in; height: 7.5in; } /* 等价,英寸直觉 */ ``` 配套的 pptxgenjs 代码: ```js const pptx = new pptxgen(); pptx.layout = 'LAYOUT_WIDE'; // 13.333 × 7.5 inch, 无需自定义 ``` --- ## 4 条硬约束(违反会直接报错) `html2pptx.js` 把 HTML 的 DOM 逐元素翻译成 PowerPoint 对象。PowerPoint 的格式约束投射到 HTML 上 = 下面 4 条规则。 ### 规则 1:DIV 里不能直接写文字 — 必须用 `
` 或 `
新用户是主要驱动力
重点内容
重点内容
` 只翻译成 text frame,背景/边框/阴影属于 shape——必须在**包裹 text 的 div** 上写。
### 规则 4:DIV 不能用 `background-image` — 用 `` 标签
```html
```
**为什么**:`html2pptx.js` 只从 `副标题补充说明
简短说明文字
```
---
## 常见错误速查
| 错误信息 | 原因 | 修复方法 |
|---------|------|---------|
| `DIV element contains unwrapped text "XXX"` | div 里有裸文字 | 把文字包进 `` 或 `
has background` | `
` 标签加了背景色 | 外套 `
` 只写文字 |
| `Background images on DIV elements are not supported` | div 用了 background-image | 改为 `` 标签 |
| `HTML content overflows body by Xpt vertically` | 内容超出 540pt | 减少内容或缩小字号,或 `overflow: hidden` 截断 |
| `HTML dimensions don't match presentation layout` | body 尺寸和 pres layout 对不上 | body 用 `960pt × 540pt` 配 `LAYOUT_WIDE`;或 defineLayout 自定义尺寸 |
| `Text box "XXX" ends too close to bottom edge` | 大字号 `
` 距离 body 底边 < 0.5 inch | 往上挪,留足下边距;PPT 底部本身就会被遮住一部分 |
---
## 基本工作流(3 步出 PPTX)
### Step 1:按约束写每页独立 HTML
```
我的Deck/
├── slides/
│ ├── 01-cover.html # 每个文件都是完整 960×540pt HTML
│ ├── 02-agenda.html
│ └── ...
└── illustration/ # 所有 引用的图片
├── chart1.png
└── ...
```
### Step 2:写 build.js 调用 `html2pptx.js`
```js
const pptxgen = require('pptxgenjs');
const html2pptx = require('../scripts/html2pptx.js'); // 本 skill 脚本
(async () => {
const pres = new pptxgen();
pres.layout = 'LAYOUT_WIDE'; // 13.333 × 7.5 inch,匹配 HTML 的 960×540pt
const slides = ['01-cover.html', '02-agenda.html', '03-content.html'];
for (const file of slides) {
await html2pptx(`./slides/${file}`, pres);
}
await pres.writeFile({ fileName: 'deck.pptx' });
})();
```
### Step 3:打开检查
- PowerPoint/Keynote 打开导出 PPTX
- 双击任意文字应能直接编辑(如果是图片说明第 1 条违反了)
- 验证 overflow:每页应该在 body 范围内,没有被截
---
## 这条路径 vs 其他选项(什么时候选什么)
| 需求 | 选什么 |
|------|------|
| 同事会改 PPTX 里的文字 / 发给非技术人员继续编辑 | **本文路径**(editable,需从头按 4 条约束写 HTML) |
| 只是演讲用 / 发存档,不再改 | `export_deck_pdf.mjs`(多文件)或 `export_deck_stage_pdf.mjs`(单文件 deck-stage),出矢量 PDF |
| 视觉自由度优先(动画、web component、CSS 渐变、复杂 SVG),接受不可编辑 | `export_deck_pptx.mjs --mode image`(图片铺底 PPTX) |
**绝不要在视觉自由写好的 HTML 上硬跑 html2pptx**——实测视觉驱动的 HTML pass 率 < 30%,剩下的逐页改造比重写还慢。
---
## 为什么 4 条约束不是 Bug 而是物理约束
这 4 条不是 `html2pptx.js` 作者偷懒——它们是 **PowerPoint 文件格式(OOXML)本身的约束**投射到 HTML 上的结果:
- PPTX 里文字必须在 text frame(`