npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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());

每个元素包含文字框、内容尺寸、逐字位置、staticBoundsanimatedBounds。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 展示层。