text-effect-core
v0.2.0
Published
Browser WebAssembly text layout, animation, and RGBA renderer
Readme
text-effect-core
用于浏览器文字排版和动画渲染的 WebAssembly 包。它接收临时 TextScene,输出可直接写入 Canvas、Fabric 或 WebAV 图片源的 RGBA 像素。
支持逐字弹入、波浪、打字机、Glitch、霓虹和粒子六种效果,也支持无动画的静态文字。文字换行、字间距、行高、对齐、填充、渐变、描边、阴影、背景、基础变换和动画帧均由同一个 renderer 计算。
安装
pnpm add text-effect-core也可以使用 npm:
npm install text-effect-core包使用原生 ES module 和 WebAssembly,适用于 Vite 等现代前端构建工具。
初始化
import initTextEffectCore from "text-effect-core";
await initTextEffectCore();应用生命周期内只需要初始化一次。创建 renderer 前必须先完成初始化和字体注册。
注册字体
TextScene 通过 font.fileName 查找字体。浏览器不会自动使用本机同名字体,前端需要下载字体文件并注册字节:
import {
registerTextSceneFont,
registeredTextSceneFontNamesJson,
unregisterTextSceneFont,
} from "text-effect-core";
const response = await fetch(fontUrl);
if (!response.ok) {
throw new Error(`字体下载失败: ${response.status}`);
}
const bytes = new Uint8Array(await response.arrayBuffer());
registerTextSceneFont("微软雅黑", bytes);
const registeredNames = JSON.parse(registeredTextSceneFontNamesJson());
console.log(registeredNames);
// 项目释放时可移除单个字体。
unregisterTextSceneFont("微软雅黑");相同 fileName 再次注册会替换原字体。也可以使用 clearTextSceneFonts() 清空全部字体。
创建场景 Renderer
业务数据应在前端适配层转换为临时 TextScene,不需要在项目数据中持久化新的 textScene 字段。
import { TextSceneRenderer } from "text-effect-core";
const scene = {
version: 1,
canvas: {
width: 1080,
height: 1920,
fps: 25,
coordinateSpace: "video-pixels",
},
elements: [
{
id: "title-001",
text: "豹变文字引擎",
font: {
fileName: "微软雅黑",
size: 64,
weight: 700,
italic: false,
},
paragraph: {
boxWidth: 680,
widthMode: "fixed",
boxHeight: null,
letterSpacing: 0,
lineHeight: 1.2,
horizontalAlign: "center",
verticalAlign: "middle",
wrapMode: "grapheme",
maxLines: 0,
},
paint: {
fill: { type: "solid", color: "#FFFFFFFF" },
stroke: { enabled: false, width: 0, color: "#000000FF" },
shadow: {
enabled: true,
offsetX: 6,
offsetY: 8,
blur: 10,
color: "#00000080",
},
background: {
enabled: false,
color: "#00000000",
paddingX: 0,
paddingY: 0,
radius: 0,
border: { enabled: false, width: 0, color: "#00000000" },
},
decoration: {
underline: false,
underlineWidth: 2,
underlineOffset: 2,
},
},
transform: {
x: 540,
y: 960,
originX: 0.5,
originY: 0.5,
scaleX: 1,
scaleY: 1,
rotationDeg: 0,
opacity: 1,
flipX: false,
flipY: false,
},
timeline: { startMs: 0, durationMs: 5000 },
effect: {
id: "typewriter",
version: 1,
durationMs: 3000,
seed: 1357,
playMode: "loop",
params: { revealRatio: 0.68, cursorBlink: 4, lift: 8 },
},
zIndex: 0,
},
],
};
const renderer = new TextSceneRenderer(JSON.stringify(scene));静态文字使用 effect: null。场景中的位置和尺寸始终使用最终视频像素,预览缩放不应写回场景。
渲染到 Canvas
const canvas = document.createElement("canvas");
canvas.width = renderer.width;
canvas.height = renderer.height;
const context = canvas.getContext("2d");
if (!context) throw new Error("Canvas 2D context 不可用");
const rgba = renderer.renderFrameRgba(900);
const imageData = new ImageData(
new Uint8ClampedArray(rgba),
renderer.width,
renderer.height,
);
context.putImageData(imageData, 0, 0);renderFrameRgba(timeMs) 的时间是整个 TextScene 的全局毫秒时间。返回值是连续排列的 sRGB straight-alpha RGBA8,长度固定为 renderer.height * renderer.stride。
不计算动画、只验证基础文字样式时使用:
const staticRgba = renderer.renderStaticRgba();获取布局和选框
const metrics = JSON.parse(renderer.metricsJson());每个元素包含文字框、内容尺寸、逐字位置、staticBounds 和 animatedBounds。Fabric/WebAV 的图片源或选框应使用稳定的 animatedBounds,避免动画过程中因逐帧扫描透明像素而改变尺寸。
Renderer 生命周期
文字、字体、字号、排版、样式、变换、时间线或效果配置变化后,应释放旧 renderer 并重新创建。只有播放时间变化时复用同一个实例:
let renderer = new TextSceneRenderer(JSON.stringify(scene));
function replaceScene(nextScene: unknown) {
renderer.free();
renderer = new TextSceneRenderer(JSON.stringify(nextScene));
}
function dispose() {
renderer.free();
clearTextSceneFonts();
}动画能力与 Unicode
import {
evaluateTextEffectJson,
splitTextGraphemesJson,
textEffectDefinitionsJson,
} from "text-effect-core";
const effects = JSON.parse(textEffectDefinitionsJson());
const graphemes = JSON.parse(splitTextGraphemesJson("你好👨👩👧👦"));
const framePlan = JSON.parse(
evaluateTextEffectJson(
JSON.stringify({
spec: scene.elements[0].effect,
timeMs: 900,
glyphCount: graphemes.length,
}),
),
);通常直接使用 TextSceneRenderer 即可。上述 API 适合生成效果配置面板、检查 Unicode grapheme 数量或调试单独的动画帧计划。
常见问题
font bytes not registered:确保registerTextSceneFont()的名称与font.fileName完全一致,并且注册发生在创建 renderer 之前。does not provide an export named ...:删除 Vite 的依赖预构建缓存并重新安装当前包版本。- 颜色校验失败:协议颜色使用
#RRGGBBAA,不能只传#RRGGBB。 - 预览位置或大小不一致:场景必须使用最终视频像素,预览缩放只应用在 Fabric/WebAV 展示层。
