@codehz/draw-call 是一个声明式 Canvas 绘图库,提供 Flexbox 布局引擎和组件化渲染系统。
- Flexbox 布局引擎: 支持完整的 Flexbox 布局,包括方向、对齐、间距等
- 组件化渲染: 提供 Box、Text、RichText、Image、Svg、Stack、CustomDraw、Transform 等组件
- 丰富的样式支持: 渐变、阴影、边框、圆角等
- 文本排版: 自动换行、省略号、行高控制、富文本支持等
- SVG 图形: 支持矩形、圆形、椭圆、路径等 SVG 图形
- 2D 变换: 支持旋转、倾斜等 2D 变换操作
- 自定义绘制: 支持自定义 Canvas 绘制逻辑
- 跨平台: 支持浏览器和 Node.js 环境(通过 @napi-rs/canvas)
src/
├── canvas.ts # Canvas 创建和管理(浏览器环境)
├── node.ts # Node.js 环境支持
├── components/ # 组件定义
│ ├── Box.ts # Box 容器组件
│ ├── Stack.ts # Stack 堆叠组件
│ ├── Text.ts # Text 文本组件
│ ├── RichText.ts # RichText 富文本组件
│ ├── Image.ts # Image 图片组件
│ ├── Svg.ts # SVG 图形组件
│ ├── Transform.ts # Transform 2D 变换组件
│ └── CustomDraw.ts # CustomDraw 自定义绘制组件
├── layout/ # 布局引擎
│ ├── engine.ts # 布局计算核心
│ ├── components/ # 组件固有尺寸测量
│ └── utils/ # 布局工具函数
├── render/ # 渲染引擎
│ ├── components/ # 组件渲染实现
│ └── utils/ # 渲染工具函数
├── __tests__/ # 集成测试
├── types/ # 类型定义
│ ├── base.ts # 基础类型(颜色、尺寸、样式等)
│ ├── components.ts # 组件类型
│ └── layout.ts # 布局类型
└── compat/ # 兼容层
├── DOMMatrix.ts # DOMMatrix polyfill
└── Path2D.ts # Path2D polyfill
examples/ # 示例代码
scripts/ # 构建脚本
drafts/ # 临时文档和设计稿(不提交到 git)
默认使用 Bun 作为运行时和包管理器。
- 使用
bun <file>代替node <file>或ts-node <file> - 使用
bun test代替jest或vitest - 使用
bun build <file.html|file.ts|file.css>代替webpack或esbuild - 使用
bun install代替npm install或yarn install或pnpm install - 使用
bun run <script>代替npm run <script>或yarn run <script>或pnpm run <script> - 使用
bunx <package> <command>代替npx <package> <command> - Bun 自动加载 .env,无需使用 dotenv
Bun.serve()支持 WebSockets、HTTPS 和路由。不要使用expressbun:sqlite用于 SQLite。不要使用better-sqlite3Bun.redis用于 Redis。不要使用ioredisBun.sql用于 Postgres。不要使用pg或postgres.jsWebSocket内置。不要使用ws- 优先使用
Bun.file而不是node:fs的 readFile/writeFile - 使用
Bun.$ls`` 代替 execa
- 使用
@/*路径别名指向src/* - 启用严格模式
- 目标为 ESNext
- 模块解析为 bundler 模式
bun installbun test测试文件使用 *.test.ts 命名,使用 Bun 内置的测试框架:
import { test, expect } from "bun:test";
test("hello world", () => {
expect(1).toBe(1);
});# 检查代码
bun run lint
# 自动修复
bun run lint:fix
# 格式化代码
bun run format项目使用 Husky 和 lint-staged,在提交前自动运行:
- Prettier 格式化
- ESLint 检查和修复
# 构建项目
bun run release发布流程:
- 使用 tsdown 构建项目
- 复制 package.json、README.md、LICENSE 到 dist/
- 运行 release-rewrite.ts 脚本处理发布配置
- 在
src/components/中创建组件文件 - 在
src/types/components.ts中定义组件类型 - 在
src/layout/components/中实现固有尺寸测量 - 在
src/render/components/中实现渲染逻辑 - 在
src/components/index.ts中导出组件
组件类型应继承相应的 Props 接口:
// 基础布局属性
interface LayoutProps {
width?: Size;
height?: Size;
margin?: number | Spacing;
padding?: number | Spacing;
// ...
}
// 容器布局属性
interface ContainerLayoutProps extends LayoutProps {
direction?: FlexDirection;
justify?: JustifyContent;
align?: AlignItems;
gap?: number;
// ...
}布局引擎在 src/layout/engine.ts 中实现,遵循以下流程:
- 测量固有尺寸(
measureIntrinsicSize) - 解析宽高(
resolveSize) - 应用 min/max 约束
- 计算子元素布局
- 应用偏移和对齐
渲染引擎在 src/render/index.ts 中实现,遵循以下流程:
- 根据元素类型调用对应的渲染函数
- 处理裁剪(clip)
- 递归渲染子元素
- 恢复上下文
type Size = number | `${number}%` | "auto" | "fill";number: 固定像素值${number}%: 百分比值"auto": 自动计算(基于内容)"fill": 填充可用空间
type Color = string | CanvasGradient | CanvasPattern | GradientDescriptor;支持:
- CSS 颜色字符串(如
#ff0000,rgb(255,0,0),red) - CanvasGradient 对象
- CanvasPattern 对象
- 渐变描述符(
linearGradient或radialGradient)
// 线性渐变
linearGradient(angle: number, ...stops: (string | [number, string])[])
// 径向渐变
radialGradient(options, ...stops: (string | [number, string])[])测试文件应与源文件放在同一目录下,使用 *.test.ts 命名。
- 使用
describe组织相关测试 - 使用
test定义单个测试用例 - 使用
expect进行断言 - 测试边界情况和错误处理
import { test, expect } from "bun:test";
import { Box, Text, createCanvas } from "@/index";
test("Box renders correctly", () => {
const canvas = createCanvas({ width: 100, height: 100 });
const node = canvas.render(
Box({
width: 50,
height: 50,
background: "#ff0000",
})
);
expect(node.layout.width).toBe(50);
expect(node.layout.height).toBe(50);
});使用 drafts/ 目录存放临时文档和设计稿,该目录不会被提交到 git。
- 设计文档
- 临时计划
- 实验性代码
- 笔记和想法
在 Node.js 环境中使用需要安装可选依赖:
bun install @napi-rs/canvas然后使用 createNodeCanvas 代替 createCanvas。
- 避免频繁创建 Canvas 实例
- 复用布局计算结果
- 使用
clip时注意性能影响 - 大量文本时考虑使用
maxLines限制行数
- 使用
canvas.getContext()获取原生 Canvas 上下文 - 检查布局节点的
layout属性查看计算结果 - 使用
canvas.toDataURL()导出图片查看渲染结果