# HTMLElement
使用 [Takumi](https://takumi.kane.tw/) 渲染任意 HTML/CSS 为 FKbuilder 视频元素。
## 安装依赖
```bash
npm install @takumi-rs/core @takumi-rs/helpers
```
## 基础用法
```javascript
import { VideoBuilder } from 'fkbuilder';
const builder = new VideoBuilder({ width: 1280, height: 720, fps: 30 });
builder.createTrack({ zIndex: 0 })
.createScene({ duration: 5 })
.addBackground({ color: '#1e1b4b' })
.addHtml({
x: 0, y: 0, width: 1280, height: 720, anchor: [0, 0],
html: `
Hello World
`,
duration: 5,
});
await builder.render('output/video.mp4', { parallel: true });
builder.destroy();
process.exit(0);
```
## 核心概念
### timeMs 注入(CSS 动画与时间轴同步)
HTMLElement 通过 `timeMs` 参数驱动 CSS @keyframes 动画。Takumi 每帧接收当前视频时间(毫秒),使 CSS 动画与视频时间轴同步。
```javascript
.addHtml({
html: `
`,
duration: 5,
// timeOffset: 0 // 可选,默认 0,动画起始偏移(秒)
})
```
### 多场景与时间轴
每个 `addHtml()` 的 `duration` 决定元素在场景内的显示时长,`startTime` 由 Scene 自动计算(默认 0)。
```javascript
// 场景 1: 0-4 秒
builder.createTrack({ zIndex: 0 })
.createScene({ duration: 4 })
.addHtml({ html: `Scene 1
`, duration: 4 });
// 场景 2: 4-8 秒(startTime 自动计算为 4)
builder.createTrack({ zIndex: 0 })
.createScene({ duration: 4, startTime: 4 })
.addHtml({ html: `Scene 2
`, duration: 4 });
```
## 配置选项
| 选项 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `html` | `string` | - | HTML 字符串(与 `node` 二选一) |
| `node` | `object` | - | Takumi node tree 对象 |
| `x` | `number\|string` | `0` | X 坐标 |
| `y` | `number\|string` | `0` | Y 坐标 |
| `width` | `number\|string` | `800` | 元素宽度 |
| `height` | `number\|string` | `600` | 元素高度 |
| `anchor` | `[number, number]` | `[0.5, 0.5]` | 锚点 `[0,0]`=左上角,`[0.5,0.5]`=中心 |
| `opacity` | `number` | `1` | 透明度 0-1 |
| `rotation` | `number` | `0` | 旋转角度(度) |
| `duration` | `number` | - | 元素显示时长 |
| `timeOffset` | `number` | `0` | CSS 动画起始偏移(秒) |
| `fonts` | `Array` | 自动中文字体 | 字体配置 |
| `stylesheets` | `Array` | - | 外部样式表 URL |
| `keyframes` | `object` | - | 结构化 keyframe 定义 |
| `devicePixelRatio` | `number` | - | 设备像素比 |
| `autoDefaultFont` | `boolean` | `true` | 未声明 `font-family` 时自动注入跨平台 CJK 字体栈 |
| `tailwind` | `boolean\|object` | `false` | 自动 Tailwind 支持,见下方"Tailwind 集成" |
| `emoji` | `boolean\|string` | `'twemoji'` | 关闭设为 `false`;切换风格用 `'noto'\|'openmoji'\|...` |
---
## 🎯 三大亮点
### 1. 中文自动支持(autoDefaultFont)
为了"开箱即用就能渲染中文",HTMLElement 在渲染前会扫描 HTML 字符串:
- **HTML 中已声明任何 `font-family`** → 保持原样,不注入(尊重用户选择)
- **未声明 `font-family`** → 自动在 HTML 顶部插入:
```html
```
字体加载(`getDefaultFonts()`)在构造函数里完成,覆盖 Windows/macOS/Linux 三平台:
- **Windows**: 微软雅黑 / 黑体 / 宋体 / 楷体 / 仿宋
- **macOS**: 苹方 / 华文黑体 / 宋体 / Apple Color Emoji
- **Linux**: Noto Sans CJK / 文泉驿微米黑 / Noto Color Emoji
关闭自动注入:
```javascript
.addHtml({ html: '完全自定义
', autoDefaultFont: false });
```
> ⚠️ `getDefaultFonts()` 返回的 `family` 字段当前是**死代码**(没传给 Takumi),见下方"已知限制"。
### 2. Emoji 彩色渲染
HTML 里直接写 emoji 字符,会被自动替换成 **Twemoji 彩色 SVG**:
```javascript
.addHtml({
html: `
🚀 🎨 ⚡
FKbuilder 真棒 🎬
`,
duration: 5,
});
```
**emoji 样式**:`'twemoji' | 'noto' | 'openmoji' | 'blobmoji' | 'fluent' | 'fluentFlat'`,默认 `'twemoji'`:
```javascript
.addHtml({ html: '🚀', emoji: 'noto' }); // 切到 Noto 风格
.addHtml({ html: '🚀', emoji: false }); // 关闭,用文字 emoji
```
> 💡 emoji SVG 首次会从 CDN 拉取(jsDelivr/twemoji),缓存在渲染器里。同 URL 第二次零成本。
### 3. Tailwind CSS(零依赖)
`tailwind: true` 直接用 FKbuilder 自带的 158KB 通用 Tailwind CSS,**不需要安装 `tailwindcss`、不调任何 CLI、不写缓存文件**。
```javascript
.addHtml({
html: `
零配置 Tailwind
`,
tailwind: true, // ← 就这一行
duration: 5,
});
```
覆盖 1000+ utility:`colors/spacing/typography/flex/grid/responsive/hover/dark mode/blur/gradient/...`,详见 `src/assets/tailwind-defaults.css`。
#### 自定义主题:传预编译 CSS
需要自定义主题(`bg-brand` 等)时,自己写 `tailwind-input.css` 预编译,再传进来:
```bash
npm install -D tailwindcss # 装一次,只是为了预编译
```
```css
/* tailwind-input.css */
@import "tailwindcss";
@theme {
--color-brand: #FF6B6B;
--color-accent: #06b6d4;
}
```
```bash
npx tailwindcss --input tailwind-input.css --output my-tailwind.css --minify
```
```javascript
// 方式 1: 读文件路径
.addHtml({ html: '...', tailwind: { input: './my-tailwind.css' } });
// 方式 2: 直接传字符串
.addHtml({ html: '...', tailwind: { css: fs.readFileSync('./my-tailwind.css', 'utf8') } });
```
**API 总结**:
| 用法 | 行为 |
|---|---|
| `tailwind: true` | 用打包的通用 CSS(158KB) |
| `tailwind: { css: '...' }` | 用传入的 CSS 字符串 |
| `tailwind: { input: '/path' }` | 读本地 CSS 文件 |
**已知限制**:
- 通用版 CSS 不含极少数冷门 utility → 自己编译一份即可
- Takumi 不支持的 CSS 特性(`bg-clip-text`、部分 backdrop-filter)即使 CSS 写对了也无效
完整示例:[`examples/html-element-tailwind-zero.js`](../examples/html-element-tailwind-zero.js)(零配置)、[`examples/html-element-tailwind-custom-theme.js`](../examples/html-element-tailwind-custom-theme.js)(自定义主题)
---
## 字体配置
```javascript
// 1. URL(自动 fetch)
fonts: ['https://fonts.googleapis.com/css2?family=Roboto']
// 2. 系统字体路径(自动读盘 + 注入到 Takumi)
fonts: [{ path: 'C:/Windows/Fonts/simhei.ttf', family: 'SimHei' }]
// 3. Buffer
fonts: [{ data: fontBuffer }]
// 4. 不指定 → 用默认中文栈(getDefaultFonts)
fonts: undefined // 默认行为
```
### ⚠️ 字重不要用 800/900(fake-bold 会糊)
**约定**:
```css
/* ✅ 用这些 */
font-weight: 400; /* Regular — 细 */
font-weight: 700; /* Bold — 粗,真粗体字形,笔画清楚 */
/* ❌ 避开这些 */
font-weight: 800; /* 触发 fake-bold,笔画粗但字形模糊 */
font-weight: 900; /* fake-bold 合成最重,中文字几乎糊成一片 */
```
**为什么**:微软雅黑只内置 300/400/700 三个字重,没有 800/900。CSS 请求这些不存在的字重时,渲染器会用最接近的 Bold(700)字形额外"描粗"——对拉丁字符看起来还行,对**笔画密集的中文会糊成一团**(相邻横竖笔画相撞)。
实测对比(同一段"微软雅黑粗体"):
| font-weight | 视觉效果 |
|---|---|
| 400 | 笔画细、清晰 |
| 700 | 笔画粗、清晰 ← 推荐 |
| 900 | 笔画合并、字形糊掉 ← 千万别用 |
**想要"特别粗"怎么办**:
```css
/* 方案 1: 换 SimHei,本身就重 */
font-family: 'SimHei';
font-weight: 400; /* SimHei 只有一个字重,天然就是粗的 */
/* 方案 2: 用粗描边模拟 */
h1 {
font-family: 'Microsoft YaHei';
font-weight: 700; /* 真 Bold */
-webkit-text-stroke: 2px white; /* 加描边视觉上加粗 */
}
```
### 已知限制 ⚠️
`getDefaultFonts()` 返回 `{ path, family, weight }` 形态的字体对象,但**`family` 字段没传给 Takumi**。当前实现下:
- 如果 CSS 写 `font-family: 'Microsoft YaHei'` → 仍然能匹配,因为 Takumi 会从字体文件内部 name 表里读名字
- 如果用户想用 `family` 字段自定义 CSS 引用名 → 暂不支持
- TTC 集合(`msyh.ttc`)通常只注册第一个成员;粗体变体(`msyhbd.ttc`)和 Light(`msyhl.ttc`)作为独立文件注册
## 结构化 Keyframes
通过 `keyframes` 选项声明 CSS 动画。**FKbuilder 自动生成 `@keyframes` 定义 + `animation` 属性**,不用手动写两遍。
### 推荐写法(Rich 格式,自动应用 animation)
```javascript
addHtml({
html: `
`,
tailwind: true, // ← Tailwind 工具类
keyframes: {
'.badge': { // ← CSS 选择器
duration: '1s', // 动画时长(默认 1s)
easing: 'ease-in-out', // 缓动函数(默认 ease-in-out)
iteration: 'infinite', // 播放次数(默认 infinite)
delay: '0s', // 延迟(默认 0s)
direction: 'normal', // 方向(默认 normal)
fill: 'none', // 填充模式(默认 none)
keyframes: { // ← 关键帧定义
'0%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-30px)' },
'100%': { transform: 'translateY(0)' },
},
},
},
})
```
**效果**:自动注入 `` 到 HTML 顶部,并生成对应的 `@keyframes`。开箱即用。
### 简化写法(裸格式)
如果用默认值(1s ease-in-out infinite),可以直接写 keyframes 规则:
```javascript
keyframes: {
'.badge': {
'0%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-30px)' },
'100%': { transform: 'translateY(0)' },
},
}
```
同样会自动应用 `animation`。
### 高级写法(多 selector / 自定义 timing)
```javascript
keyframes: {
'.title': { duration: '2s', easing: 'ease-out', iteration: '1',
keyframes: { '0%': {opacity: 0, transform: 'translateY(-20px)'}, '100%': {...} } },
'.card': { duration: '.5s', easing: 'ease-in-out', iteration: 'infinite',
keyframes: { '0%, 100%': {transform: 'scale(1)'}, '50%': {transform: 'scale(1.05)'} } },
'#hero': { duration: '3s', iteration: 'infinite', easing: 'linear',
keyframes: { '0%': {transform: 'rotate(0deg)'}, '100%': {transform: 'rotate(360deg)'} } },
}
```
### 底层数组格式(向后兼容)
如果想完全控制,可直接传 Takumi 原生数组格式:
```javascript
keyframes: [
{ name: 'spin', keyframes: [
{ offsets: [0], declarations: { transform: 'rotate(0deg)' } },
{ offsets: [1], declarations: { transform: 'rotate(360deg)' } },
]},
],
```
但要自己写 ``。
### 完全自控(HTML 内写全部)
如果想完全手动控制,写在 HTML `
手动
`,
// 不传 keyframes 选项
})
```
### 注意事项
- **Rich 格式下默认值**:duration=1s, easing=ease-in-out, iteration=infinite
- **selector 可用 `.class`、`#id`、`tag`**(只要是合法 CSS 选择器)
- **HTML 里写的 `animation:` 会覆盖自动注入的**(CSS 级联,优先级生效)
- **不会与 `tailwind: true` 冲突**:keyframes 的 CSS 在 Tailwind CSS 之后注入
## 与 FKbuilder 动画结合
HTMLElement 同时支持 FKbuilder 预设动画:
```javascript
addHtml({
html: `Welcome
`,
animations: [
{ type: 'transform', from: { opacity: 0, x: -100 }, to: { opacity: 1, x: 0 }, duration: 1, easing: 'easeOut' },
'fadeIn', // 也可混用预设
],
})
```
---
## 完整示例
参见 `examples/`:
- `html-element-basic.js` - 基础 HTML 元素
- `html-element-chinese-default.js` - 默认中文支持(`font-weight:700`)
- `html-element-emoji.js` - 多场景彩色 Emoji
- `html-element-keyframes.js` - CSS @keyframes 动画
- `html-element-with-font.js` - 自定义字体
- `html-element-tailwind-zero.js` - **零配置** Tailwind(推荐先看)
- `html-element-tailwind-auto.js` - Tailwind CLI + 自定义主题
- `html-element-with-tailwind.js` - Tailwind v4 手动编译(旧示例,参考用)
## API
### 导出
```javascript
import {
HTMLElement, // 元素类
getTakumiRenderer, // 获取 Takumi 渲染器单例
destroyTakumiRenderer, // 销毁渲染器
renderHtmlFrame, // 直接渲染 HTML 帧
registerFontsToTakumi, // 注册字体到 Takumi
parseHtml, // 解析 HTML 为 node tree
} from 'fkbuilder';
```
### 直接渲染帧(脱离 VideoBuilder)
```javascript
import { renderHtmlFrame } from 'fkbuilder';
const rgba = await renderHtmlFrame({
html: 'Test
',
width: 1280, height: 720,
timeMs: 0,
});
// rgba: Buffer(width * height * 4, RGBA)
```
---
## 已知限制
| 限制 | 影响 | 临时绕过 |
|---|---|---|
| Takumi 不支持 `bg-clip-text` | 渐变文字看不到 | 改用 `linear-gradient` + `background-clip:text` 别名或纯色 |
| Takumi 不支持部分 backdrop-filter | backdrop-blur 等失效 | 用半透明背景替代 |
| `getDefaultFonts()` 的 `family` 字段未传给 Takumi | 自定义 CSS 引用名失效 | CSS 直接用 "Microsoft YaHei" 等标准名 |
| 通用 Tailwind CSS 不含极冷门 utility | 个别 class 无样式 | 切到 CLI 模式 (`tailwind-input.css`) |
| `font-weight: 800 / 900` 走 fake-bold 合成 | 中文笔画合并、字形糊掉 | 用 700 (Bold) / 400 (Regular),或换 SimHei |
## 注意事项
1. **每个场景必须独立 `createTrack()`**:多个场景合到同一 track 会导致时间累加错误
2. **Worker 模式**:`parallel: true` 时正常,每个 Worker 独立初始化 Takumi
3. **字体文件必须在 `fonts` 中注册**:Takumi 不自动读取系统字体(虽然默认自动注入了 Windows/macOS/Linux 字体)
4. **CSS 动画每帧重渲染**:无动画时可考虑关闭 `autoDefaultFont` 之外的二次处理
5. **不要多次调用 `build()`**
6. **render 完成后调 `process.exit(0)`**:确保进程退出