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

ice-render

v2.14.1

Published

A canvas engine for interactive graphics.

Readme

ICERender 是一款 Canvas 2D 交互图形渲染引擎,面向 ER 图 / 流程图 / 拓扑图等图表编辑场景。它借鉴 React 的组件模型与 W3C 的事件模型,提供嵌套坐标系、序列化、动画、Visio 风格连接线等能力,同时以「极简依赖 + 多运行时兼容 + 高性能」为设计约束。

⭐ 差异化能力

以下三点是本引擎在同类 Canvas 图形引擎中较少同时具备的能力,且都有回归测试或基准数据支撑。

1. 极端规模下的内存与构建效率

  • 默认配置不复制(实例侧) —— 所有实例原型继承同一份默认 props / state,只有显式传入的字段才落到实例上;嵌套对象在合并时才做写时复制。例外是 style:它是每实例按当前主题派生的对象(共享会串味),实例的 style 各有一份。
  • 挂载去重为 O(1) —— 用 WeakSet,批量挂载不再有 indexOf 的 O(n²) 放大。
  • 实测(2026-09-15,Apple Silicon 开发机,npm run bench:mem:node + 每档独立进程 + 造对象前后各两次 gc 的真增量): 100 万个最小矩形的堆增量约 2.3GB(2.4KB/图元;10 万 ≈232MB、50 万 ≈1.16GB); 同一场景把整份默认表显式传给每个实例约 7.4GB(3.3×)——而"默认配置"(props + state)那部分只占 0.98KB/图元。 这类数字跨机器会差数倍,以本机 npm run bench:mem 为准;100 万图元构建约 6s 是 2026-09-10 示例页的数据(本轮未复测)。
  • 回归tests/graphic/ICEComponent.props-sharing.test.tstests/ICE.add-child.test.ts;内存基准 npm run bench:mem(已纳入 verify:full),其余微基准见 bench/micro/

2. 局部重绘是一条可证明的像素契约

  • 默认渲染路径为脏矩形局部重绘,不满足局部条件时自动回退全量;ICE.init(ctx, { renderMode: 'full' }) 可强制全量。
  • 为保证两条路径逐像素一致,每个组件在 render() 末尾把自身污染过的 ctx 全局状态(阴影 / globalAlpha / 合成模式 / 虚线等)归位,使组件渲染自包含。
  • 用 golden image 做像素一致性回归(e2e/visual/dirty-rect-pixel.spec.ts),覆盖文本、参数化图元、半透明落墨等场景。
  • 配套优化:组件级离屏缓存(含纯平移复用位图)、渲染队列缓存、矩阵零分配。 性能数字请以本机 npm run bench 5000 的输出为准(引擎 JS 逻辑开销,不含光栅化): 2026-09-11 在 Apple Silicon 开发机上实测 约 2.2ms/帧(场景 A 静态重绘,5000 图元、多层嵌套)。 这里刻意不再写一个固定数字 —— 这类数字跨机器可差数倍,写死就会像本文旧版本那样变成不可复现的宣称。

3. 小程序是一等公民

  • 一套代码同时面向 Web 浏览器各类小程序:所有全局对象访问收敛到 cross-platform/root 适配层。
  • 无全局 Path2D 的运行时自动降级PolyfillPath2D 记录路径命令、渲染时重放,与原生 Path2D 逐像素一致(老版本小程序基础库可用)。
  • 字体、图片、离屏画布、像素比全部有平台适配(FontFace / 小程序 loadFontImage / 小程序 createImagedocument.createElement('canvas') / 小程序 createOffscreenCanvasdevicePixelRatio / 小程序系统信息)。
  • ICE.init(ctx) 支持直接传入 Canvas 上下文,完全绕开 DOM。
  • 每次提交都在「小程序形状」的运行时里回归tests/mini-program/ 摘掉 document / window / Path2D / requestAnimationFrame / FontFace / OffscreenCanvas,只留 wx.*,画布对象只有 width / height / getContext —— 覆盖启动、出帧、路径重放、离屏缓存降级、文本量测降级、触摸输入、序列化与 SVG 导出。
  • 接入示例与宿主契约examples/mini-program/,含可直接拷进小程序项目的页面与触摸坐标适配层。

✨ 核心特性

架构与组件模型

  • 声明式渐变(可序列化) —— style.fillGradient / style.strokeGradient 用纯对象描述 linear / radial / conic 渐变({ type, from/to | center/radius | startAngle, stops }), 渲染时构造 CanvasGradient 并按描述对象引用缓存。与手搓 CanvasGradient 的关键差别是 能进 JSON(存盘不丢)且能写进主题 preset(随 setTheme 重新展开)。
  • 主题与样式:四层 token + 主题引用 + 状态样式 —— ① base(色 ramp / spacing / radius / fontSize)→ ② semantic(primary / text / border / palette / motion) → ③ chrome(选中框 / 手柄 / 插槽 / 引导线 / 连线标签 / 选区 / 阴影色)→ ④ preset(card / panel / button …)。 样式里可以直接引用 token:style: { fillStyle: token('primary') }在绘制那一刻解析, 所以 setTheme() 之后任意组件(不只是用了 preset 的)都会跟着换; 交互状态用 states: { hover, active, selected, disabled, focus } 声明, 引擎提供 setInteractionState() 与可选的自动驱动(ice.enableInteractionStates()); 主题支持深合并{ motion: { duration: { fast: 50 } } } 不会抹掉 easing)、 子树作用域new ICEGroup({ theme: {...} }))、进快照theme: { name | patch })、 变更通知ice.onThemeChange(fn):上层能被动跟随,不必等下一次重建)、 结构化校验ice.validateTheme():拼错内置 token / 类型不对 / WCAG 对比度不足; 应用自带词汇只给 info,因为 $app.highlight 这类引用是能被解析的)。 命名主题注册有护栏:内置 default / dark 不可覆盖、重复注册抛错(要覆盖显式传 { overwrite: true })。 细节见 docs/architecture/21-theme-and-style.md
  • display: false 是整棵子树隐藏 —— 隐藏父容器后子组件不再被绘制、也不参与命中 (判定收敛在 isEffectivelyVisible(),渲染/命中/a11y/离屏缓存共用)。
  • 变换手柄支持修改键约束 —— Shift 拖角手柄保持宽高比、Shift 拖旋转手柄吸附 15°。 输入层会把 DOM 事件的修饰键显式透传到组件事件(shiftKey 是原型上的不可枚举 getter, 默认拷贝带不过来)。
  • 脏矩形局部重绘在缩放/平移与高分屏下同样生效 —— 脏区按「世界坐标收集、渲染坐标裁剪」 (dpr · viewport 一次换算),并把分散脏区聚合成多块裁剪区,而不是并成一个把干净区域也圈进去的大盒。
  • 零运行时依赖 —— gl-matrix 在构建时被内联进产物(它只列在 devDependencies,产物里没有任何 import/require),安装后开箱即用,不需要额外装包。内联的第三方代码保留其许可声明,见 dist/THIRD-PARTY-NOTICES.txt
  • 纯 TypeScript —— 100% TS 源码,产出完整的 .d.ts 类型声明,tsc --noEmit 零错误。
  • React 式组件模型 —— props(不可变构造入参)/ state(可变运行时状态)分离,render() 模板方法 + 清晰的类继承体系。
  • 无限嵌套容器 —— ICEGroup 可任意嵌套,形成组件树。

文本与国际化

  • 断行策略 —— wrap 开启后按 wordBreak: 'normal'(默认)断行:拉丁词不被硬拆、 CJK 逐字断并做禁则(行首不放闭标点、行尾不放开标点)、泰/老/高棉/缅甸这类无空格脚本 按词典分词断行(复用运行时的 Intl.Segmenter word 粒度,宿主不支持则退回逐字), 单个词整行放不下时才硬拆;'break-all' 保留逐字贪心(代码 / 艺术字场景)。
  • 文字方向(RTL / BiDi) —— direction: 'ltr' | 'rtl' | 'auto'textAlign: 'start' | 'end''auto' 按首个强方向字符判定,写 ctx.direction 前做特性检测、渲染完归位; SVG 导出同口径(direction + 按方向映射的 text-anchor)。
  • 溢出截断(不变形) —— 文本放不下盒子时按宽度截断并追加省略号(textOverflow: 'ellipsis',默认), 也可以用 textOverflow: 'clip' 允许溢出交给调用方裁;多行配合 maxLines 截末行。 绝不压字形:以前把盒子宽度当 fillText(..., maxWidth) 传下去,canvas 会把文字横向挤扁 (长中文尤其明显),现在这条路径已经去掉。
  • i18n 边界 —— 引擎不做 i18n(没有词条表、没有 locale 状态):词条、复数与 Intl 格式化归应用层,组件库的内置文案可配置且不持全局状态;引擎只负责断行、方向、输入法, 并让错误带稳定错误码ICE_ERROR_CODES / getICEErrorCode(err),应用据此映射自己的语言包)。 完整契约见 docs/architecture/17-i18n-boundary.md

坐标系与变换

  • 完整仿射变换 —— 平移 / 缩放 / 旋转 / 错切(skew),基于 gl-matrix 的列向量 mat2d 约定。
  • 嵌套坐标系 —— 子组件自动复合祖先变换,localToGlobal / globalToLocal 双向换算;支持在嵌套场景下做全局位移与旋转。
  • 容器移动时后代自动跟随 —— setPosition() 会向所有后代递归派发 AFTER_MOVE(只派发事件、 不改任何 state):容器移动后,订阅了宿主事件的组件(如 ICEPolyLine 监听两端图元重算折点) 会自动跟上,应用层不必手动遍历子树。BEFORE_MOVE 仍只给被移动的组件自己。
  • HiDPI —— ICE.init(el, { dpr }) 把 backing store 放大到内容盒尺寸 × dpr(默认 1,行为与旧版一致)。

交互与连接线

  • 统一输入层 —— 鼠标 / 触控 / 触控笔 / 滚轮收敛到 Pointer 事件族(无 PointerEvent 的运行时自动回退 mouse* + touch*);完整事件系统(on/off/once/trigger 及 W3C 别名),支持拖拽与方向键微调。
  • 变换控制面板 —— 选中组件后出现旋转 / 缩放手柄。
  • 连接线形态可切换 —— 同一套「插槽吸附」之上可选 Visio 正交折线(默认)或普通贝塞尔曲线linkShape: 'visio' | 'bezier'):贝塞尔沿插槽法线出/入,控制点长度随两端距离自适应。
  • Visio 风格连接线 —— 端点插槽吸附(上 / 右 / 下 / 左 / 中心五个方向),建立组件间的连线关系; 端点箭头默认实心(用线色填充),arrowStyle: 'hollow' 可切回空心描边。
  • 视口缩放 / 平移 —— setViewport() 与锚点缩放 zoomAt(screenX, screenY, factor);「视图缩放」与「图元缩放」严格分离。
  • 对齐吸附 —— 边缘 / 中心 / 等间距吸附与提示线,默认关闭、按需 enable()(零开销)。

扩展与可访问性

  • 插件机制 —— ICE.use(plugin) 开放三层注册点:自定义图元类型(components 的键是 canonical typeId,形如 'my-app:Badge',格式非法或与已注册类型冲突会明确抛错;自动获得 typeId 反查,因此可序列化)、每帧渲染回调、自定义交互工具。
  • 无障碍原语 —— getAccessibilityTree() 产出可访问节点快照(角色 / 可读名称 / 屏幕坐标盒 / tab 顺序),setFocusedComponent() 让键盘事件派发给焦点组件。引擎不自建 DOM 镜像层:镜像结构、ARIA 与文案由应用层决定(参考实现见 examples/a11y/)。

序列化与动画

  • 整图序列化 —— 组件树可序列化为 JSON 字符串并无损反序列化;类型键用稳定 typeId(格式为 namespace:Type,如 ice-render:Rectice-chart:PlotArea;由构造函数反查得到,与类的 JS 名解耦,压缩改名不影响已存数据),带 version 字段与可扩展迁移表;未注册类型跳过并记录而不是整份数据打不开。自定义组件用 registerType('my-app:Badge', Badge) 注册后才能持久化与加载 —— 同一个 typeId 注册不同构造函数、或同一个构造函数注册第二个 typeId 都会明确抛错,不再静默覆盖。
  • 产物自带文档时间戳 —— createTime / lastModifyTime 是 ISO 8601 UTC(与运行环境的语言、时区无关,可直接排序与解析);createTime 表示「这份文档首次创建的时刻」,载入时读回、ice.clearAll() 后重新计,所以「打开 → 编辑 → 保存」里只有 lastModifyTime 在变。
  • 关键帧动画 —— 动画配置类似 CSS keyframes:单段 { from, to, duration } 或 多段 { keyframes: [{ offset, value, easing? }], duration }easing 写在段起始帧上,只作用于该段; offset 缺省按顺序均分、超界夹紧)。内置线性 / 缓入 / 缓出等缓动函数与弹簧类缓动spring / springSoft / springSnappy,自带过冲);支持 delayloopiterationCountround;动画键可为 'transform.rotate' 这类点路径,取值可为数组transform.scale 等逐元素补间)。

性能与工程质量

  • 高性能 —— 脏标记 + 脏矩形局部重绘(默认,不满足局部条件时自动回退全量),配合组件级离屏缓存、渲染队列缓存与矩阵零分配。性能数字以本机 npm run bench 5000 为准(2026-09-11 在 Apple Silicon 开发机上实测约 2.2ms/帧,见上文第 2 条「局部重绘」)。
  • 完整工程化 —— 140 个测试套件 / 1173 个用例(jest,带「只许上调」的覆盖率门槛,2026-09-17 实测)、Playwright 可视化回归(100 条:golden-image + 脏矩形像素一致性 + 视口/对齐/交互 + 92 个示例页冒烟)、发布包完整性门禁(publint + attw)、eslint、架构设计文档。

🚀 快速开始

浏览器(UMD)

<script src="https://unpkg.com/ice-render/dist/index.umd.js"></script>
<canvas id="canvas-1" width="1024" height="768"></canvas>
<script>
  const ice = new ICE.ICE().init('canvas-1');

  const rect = new ICE.ICERect({
    left: 100, top: 100, width: 50, height: 50,
    style: { strokeStyle: '#ff3300', fillStyle: '#00ff00' },
  });
  ice.addChild(rect);
</script>

npm 安装

npm i ice-render --save
import { ICE, ICERect, ICEGroup } from 'ice-render';

const ice = new ICE().init('canvas-1');
ice.addChild(new ICERect({ width: 100, height: 50 }));

发布包提供 ESM(dist/index.mjs)/ CJS(dist/index.cjs)/ UMD(dist/index.umd.js 三种格式。

接下来:写一个「页面」

上面的例子是引擎原语 —— 画一个图形、加一个子节点。但一个应用里真正要写的是页面: 若干控件、数据由宿主推给你、切换 / 刷新时只改值不重建结构。那种情况下别继续堆 ice.addChild(...),家族统一的写法是一页一个类

import { ICEContainer, ICELabel, ICETable } from 'ice-web-components';

class DataPage extends ICEContainer {
  private readonly table: ICETable;                 // ① 构造期建树,树只建一次

  constructor(ctx: { width: number; height: number }) {
    super({ left: 0, top: 0, width: ctx.width, height: ctx.height });
    this.addChild(new ICELabel({ left: 16, top: 12, text: '运行数据' }));
    this.table = new ICETable({ left: 16, top: 48, width: ctx.width - 32 });
    this.addChild(this.table);
  }

  /** ② 唯一改值入口:宿主在"数据换成新的"之后调它 */
  onUpdate(snapshot: { rows: any[] }): void {
    this.table.setData(snapshot.rows);
  }
}

三条判据说明"什么时候该从脚本升级成页面":有第二个页面数据由宿主推给你同一块结构要反复改值。页面自己不回调宿主(要宿主做事就声明 headerActions() / statusTags() / islandSpecs(),宿主来取)。

完整契约(宿主在什么时机调 onUpdate()、哪一层该用哪个入口、稳定结构与可变内容的边界、 验收清单、常见坑)见 应用层:一个页面怎么写。 组件库侧对应的容器契约见 ice-web-componentsdocs/guides/layout.md 第六节。

导出 SVG(矢量,不依赖 canvas)

画布的 toDataURL() / toBlob()光栅快照(分辨率写死、放大就糊)。引擎的路径对象是 Path2DRecorder:一边把命令写给原生 Path2D 上屏、一边留下命令流,所以同一份场景可以再生成 一份矢量描述——任意放大、进 Illustrator/Figma、走打印/PDF 流程,或者在 Node 里出图(不需要 canvas)。

const svg = ice.toSvg();                                    // 内容自适应 + 透明背景
const svg = ice.toSvg({ background: '#ffffff', padding: 16 }); // 白底 + 留白
const svg = ice.toSvg({ area: 'viewport' });                 // 当前视口所见即所得
const { svg, width, height } = ice.toSvgResult({ scale: 2 }); // 需要宽高(写文件/排版预览)

// 不在浏览器里也能用:Node 侧同样导出(路径命令流不依赖 canvas)
const svg = exportSvg(ice);   // 或 exportSvg(任意组件) 导出子树

导出镜像渲染口径而不是另起一套:绘制顺序(z 序稳定排序、工具层默认排除)、每个组件的 composeMatrix() 世界矩阵、props.style/state.style 的合并顺序、有效透明度(自身 × 祖先)、 祖先 clipChildren 裁剪、阴影预设(sm/md/lg)、线性/径向渐变、虚线都按同一份口径落到 SVG。

限制(都会明确写进 JSDoc):阴影用 feDropShadow 近似(stdDeviation = shadowBlur / 2,模糊观感 与 canvas 不会逐像素一致);雪碧图切图(sx/sy/sw/sh)暂不支持;文本导出的是静态瞬间, 且 SVG 与 canvas 的字形度量/基线定义不同,因此导出的文字位置是「对齐口径一致、逐像素允许微差」。

可运行示例:examples/export/svg-export.html(画布与 SVG 并排对比,可调背景/留白/倍数、勾选是否 包含工具层),以及 examples/node/export.mjs服务端出图ICE.headless() 建树 → toSvg() 落盘,装了 @resvg/resvg-js 时再转一张 2× PNG)。

// 服务端(Node,没有 document / canvas)
const { ICE, ICERect } = require('ice-render');
const ice = ICE.headless();
ice.addChild(new ICERect({ width: 240, height: 120, radius: 12, style: { fillStyle: '#4f46e5' } }));
const svg = ice.toSvg({ padding: 16, background: '#ffffff' });

PNG / PDF 不内置依赖:SVG 是通用中间格式,resvgsharprsvg-convert、headless Chrome 打印都能接着走 —— 引擎保持零运行时依赖。

📚 文档

  • 架构设计文档 —— docs/architecture/:共 21 篇 —— 运行时链路 / 组件模型 / 坐标系与矩阵 / 渲染性能 / 事件 / 序列化 / 交互动画 / 多运行时兼容 / 路线图与边界 / Worker 与离屏渲染 / 视口缩放 / 对齐吸附 / 能力缺口分析 / 无障碍 / 应用驱动复盘 / 连线端点(插槽)扩展评估 / 主题与样式机制。
  • 示例 —— examples/ 目录提供 92 个可直接在浏览器运行的示例(图形、容器、事件、拖拽、连接线、动画、布局、文本、视口、对齐、插件、无障碍、性能基准等)。

🧪 工程化

| 命令 | 说明 | |---|---| | npm test | 单元测试(jest,镜像 src/ 结构,见 tests/) | | npm run test:visual | Playwright:golden 可视化回归 + 脏矩形局部重绘像素一致性(dirty-rect-pixel.spec.ts)+ 真实画布/worker 性能采集 | | npm run lint / npm run lint:fix | 代码检查 / 自动修复 | | npm run types:check | TypeScript 类型检查 | | npm run bench | 场景基准(stub ctx,bench/render.cjs,改 src/ 后先 npm run build) | | npm run bench:micro | 微基准(mitata,bench/micro/,逐个测矩阵/渲染/命中/状态热函数,防 DCE,需先 npm run build) | | npm run pkg:check | 发布包完整性门禁:publint(exports/types/files 契约)+ attw(各解析模式下的类型是否正确) | | npm run test:visual:ci | CI 用的可视化回归子集(示例冒烟 + 交互 + 像素一致性),刻意不含跨平台会漂移的 golden 比对 | | npm run build | 构建(类型声明 + rollup) |

提交前会自动执行 lint-staged(husky);CI 配置在 .github/workflows/ci.yml,依次跑 lint + 类型检查 + 单测(含覆盖率门槛)+ 构建 + 包完整性 + 可视化回归。

代码同时托管在 GitHubhttps://github.com/ice-render/ice-renderpackage.jsonrepository 指向这里)与 Gitee 镜像;CI 跑在 GitHub 上(Gitee 侧没有 runner),因此徽章不声称 CI 状态。

🔧 二次开发

基于引擎的类接口即可扩展自定义图元。以 ice-entity-designer 中的连线组件为例:

import { ICEVisioLink } from 'ice-render';

export default class Relation extends ICEVisioLink {
  constructor(props) {
    super({ title: 'Relation', relationType: 'one-to-one', referencedColumnName: 'id', ...props });
  }

  toEntityObject() {
    const { title, relationType, referencedColumnName } = this.state;
    const resolveEndpoint = (linkKey, prefix) => {
      const id = this.state.links?.[linkKey]?.id;
      if (!id) return {};
      return { [prefix + 'Id']: id, [prefix + 'Name']: this.ice.findComponent(id).state.entityName };
    };
    return {
      title,
      relationType,
      referencedColumnName,
      ...resolveEndpoint('start', 'from'),
      ...resolveEndpoint('end', 'to'),
    };
  }
}

ice-entity-designer 是一款基于 ICERender 开发的 ER 图设计器,完整示范了引擎的二次开发方式;它已应用于 craft-codeless-designer 低代码项目。

📸 截图

截图由 examples/ 下的示例页直接采集(Playwright、2× 像素比、按内容包围盒裁切,不含浏览器外壳与页面留白)。 全部 92 个示例都可以在 examples/index.html 里点开运行。

图元与样式 —— 形状库、渐变、阴影、虚线等(examples/shapes/shapes-basic.html

卡片 / 网格布局 —— 容器嵌套 + 布局引擎(examples/layout/dashboard.html

Visio 风格连线 —— 端点插槽吸附 + 连线标签(examples/line-and-link/link-label.html

嵌套容器 —— ICEGroup 任意层级嵌套与坐标复合(examples/group/group-basic.html

视口缩放 / 平移 —— 视图缩放与图元缩放分离(examples/viewport/viewport-zoom.html

实例级主题 —— 同一页面两套主题互不污染(examples/theme/theme-multi-instance.html

插件机制 —— ICE.use() 三层注册点(examples/plugin/plugin-basic.html

极端规模 —— 密集小图元铺满画布;100 万图元构建约 6s、稳态整帧约 1.2s(2026-09-10 实测,examples/performance/max-elements.html

📄 License

MIT © 大漠穷秋