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

wang-canvas

v0.0.2

Published

Wang Canvas SDK - 通用 PC 与移动端 Canvas 白板 SDK

Readme

Wang Canvas SDK API

Wang Canvas 是一个原生 Canvas 白板 SDK,当前版本优先实现 Excalidraw 常用能力:无限画布、缩放/平移、选择/移动/缩放、矩形、圆角矩形、椭圆、菱形、线段/折线、箭头、自由绘制、文本自动换行、图片元素、精确橡皮、撤销/重做、复制/粘贴、层级调整、对齐吸附、复制/粘贴样式、元素锁定、统计信息、JSON/PNG 导出。

参考功能范围:Excalidraw README 中列出的 canvas whiteboard、hand-drawn style、rectangle/circle/diamond/arrow/line/free-draw/eraser、undo/redo、zoom and panning。

文件结构

sdk/
  config/defaults.js        默认工具、视口、样式、编辑器配置
  core/editor.js            SDK 主类和公开 API
  core/editorUtils.js       编辑器挂载、复制偏移、视口边界工具
  core/events.js            事件订阅工具
  core/history.js           撤销/重做历史栈
  elements/factory.js       元素创建、标准化、草稿更新
  elements/linear.js        线段/箭头点集、折线节点编辑工具
  elements/freehand.js      自由书写点采样、简化、边界同步
  elements/geometry.js      边界、缩放控制点、移动/缩放变换
  elements/hitTest.js       命中检测
  elements/image.js         图片元素标准化、加载缓存、文件读取
  elements/eraser.js        精确橡皮擦与笔迹分段
  elements/renderers.js     Canvas 渲染
  elements/selectionRenderer.js  单元素边界与多选公共变换框渲染
  elements/style.js         元素样式字段提取
  elements/text.js          文本测量与自动换行
  elements/types.js         元素类型常量
  core/stats.js             统计信息生成
  interaction/constraints.js Shift/Alt 移动与缩放约束
  interaction/controller.js Canvas 基础交互控制
  interaction/controllerUtils.js 交互几何、光标与绘制校验工具
  interaction/eraserSession.js 橡皮擦预览事务与连续路径采样
  interaction/keyboard.js   键盘微移工具
  interaction/snapping.js   元素边/中心对齐吸附
  interaction/textEditor.js 文本内联编辑覆盖层
  utils/                   通用工具

sdk/index.js 是对外入口,预览项目在 preview/main.js 中通过该入口使用 SDK。

快速接入

import { TOOL, createWangCanvas } from "wang-canvas";

const editor = createWangCanvas(document.querySelector("#canvas"), {
  defaultTool: TOOL.SELECT,
  gridVisible: true,
  defaultStyle: {
    strokeColor: "#172033",
    backgroundColor: "transparent",
    fillStyle: "solid",
    strokeStyle: "solid",
    startArrowhead: "none",
    endArrowhead: "triangle",
    strokeWidth: 2,
    roughness: 1,
    opacity: 1,
    roundness: 0,
    fontSize: 24,
  },
});

容器元素需要有明确宽高,SDK 会在容器内创建并管理 <canvas>。

工具

import { TOOL } from "wang-canvas";

| 工具 | 说明 | 快捷键 | | --- | --- | --- | | TOOL.SELECT | 选择、移动、缩放元素 | V | | TOOL.HAND | 平移画布 | H / Space | | TOOL.RECTANGLE | 矩形 | R | | TOOL.ELLIPSE | 椭圆 | O | | TOOL.DIAMOND | 菱形 | D | | TOOL.LINE | 线段 | L | | TOOL.ARROW | 箭头 | A | | TOOL.DRAW | 自由书写,支持连续书写 | P | | TOOL.TEXT | 文本 | T | | TOOL.ERASER | 精确擦除自由书写笔迹,其他图形按元素删除 | E |

通用快捷键:Delete/Backspace 删除选择,Ctrl/Cmd + Z 撤销,Ctrl/Cmd + Y 或 Ctrl/Cmd + Shift + Z 重做,Ctrl/Cmd + C/V 复制粘贴,Ctrl/Cmd + D 快速复制,Ctrl/Cmd + A 全选,方向键按 1px 微移,Shift + 方向键 按 10px 微移。

层级快捷键:Ctrl/Cmd + ] 上移一层,Ctrl/Cmd + [ 下移一层,Ctrl/Cmd + Shift + ] 置顶,Ctrl/Cmd + Shift + [ 置底。

样式快捷键:Ctrl/Cmd + Alt + C 复制所选元素样式,Ctrl/Cmd + Alt + V 粘贴样式到当前选择。

选中单个元素后,顶部圆点可拖拽旋转;按住 Shift 旋转时会按 15 度吸附。多选时每个元素会保留独立的旋转边界,并额外显示一个公共虚线变换框;按住 Shift 框选会在原选择上追加元素。鼠标悬停到选区边框或缩放控制点时会显示对应方向的双箭头光标,并可直接拖拽缩放。移动时按住 Shift 锁定水平/垂直方向;缩放时按住 Shift 保持比例,按住 Alt 从中心缩放。绘制矩形、椭圆、菱形、线段、箭头时也支持 Shift 约束与 Alt 从中心绘制。

拖拽移动或缩放时,元素边缘/中心会自动吸附到其他元素的边缘/中心,并显示对齐辅助线。SDK 只提供复制、粘贴、层级、锁定、命中检测等能力 API;右键菜单、长按菜单、工具栏等各端交互入口由调用者自行实现,预览项目在 preview/App.vue 中演示了 PC 右键菜单。

线段和箭头支持 points 折线点集。选中单个线段/箭头后会显示节点控制点,拖拽节点可编辑折线;双击线段可插入中间点,按住 Alt 点击中间点可删除。调用方也可以通过公开 API 管理折线点。

自由书写工具会采集 pointer 合并事件以提升笔迹连续性,收笔时会简化点集并同步 x/y/width/height 边界。画笔收笔后默认保持 TOOL.DRAW,便于像 Excalidraw 一样连续书写;单击也会生成一个笔点。橡皮擦会对自由书写按笔迹片段精确擦除,并在需要时拆分为多个笔迹元素;按住指针时只灰显待擦元素,松开后才一次性提交场景和历史记录。

文本元素按固定 width 自动换行,双击文本会进入内联 textarea 编辑态,显示原生光标并支持拖选。

构造配置

createWangCanvas(target, {
  readonly: false,
  autoFocus: true,
  keyboardShortcuts: true,
  backgroundColor: "#f8fafc",
  gridVisible: true,
  gridSize: 32,
  defaultTool: TOOL.SELECT,
  switchToSelectAfterCreate: true,
  alignmentSnapping: true,
  snapThreshold: 6,
  alignmentGuideColor: "#e03131",
  selectionColor: "#2563eb",
  confirmBeforeClear: true,
  clearConfirmMessage: "确定要清空当前画布吗?",
  eraser: {
    precise: true,
    size: 18,
    pendingOpacity: 0.2,
  },
  viewportBounds: null,
  scrollBoundsIndicatorColor: "rgba(37, 99, 235, 0.28)",
  defaultStyle: {
    strokeColor: "#172033",
    backgroundColor: "transparent",
    fillStyle: "solid",
    strokeStyle: "solid",
    startArrowhead: "none",
    endArrowhead: "triangle",
    strokeWidth: 2,
    roughness: 1,
    opacity: 1,
    roundness: 0,
    fontSize: 24,
    fontFamily: "Segoe Print, Comic Sans MS, cursive",
  },
  zoom: {
    min: 0.1,
    max: 4,
    step: 0.1,
  },
  historyLimit: 100,
});

常用实例方法

工具与样式

editor.setTool(TOOL.ARROW);
editor.getTool();

editor.setStyle({
  strokeColor: "#be123c",
  backgroundColor: "#dbeafe",
  fillStyle: "hachure",
  strokeStyle: "dashed",
  endArrowhead: "dot",
  roundness: 16,
  strokeWidth: 3,
});
editor.getStyle();

元素管理

editor.insertElement("rectangle", {
  x: 80,
  y: 120,
  width: 200,
  height: 120,
  angle: 0,
  strokeColor: "#172033",
  backgroundColor: "#dbeafe",
});

editor.addElement(element, { select: true });
editor.updateElement(elementId, { strokeColor: "#2563eb" });
editor.updateElements([elementId], { strokeWidth: 4 });
editor.removeElements([elementId], { confirmClear: true });
editor.clear();
editor.clear({ confirm: false });

const elements = editor.getElements();
editor.setElements(elements);

图片元素由 SDK 提供能力入口,具体按钮、文件选择器、粘贴入口由调用方实现:

editor.insertImage({
  src: "https://example.com/image.png",
  x: 120,
  y: 80,
  width: 320,
  height: 180,
  crossOrigin: "anonymous",
});

await editor.insertImageFromFile(file, { x: 120, y: 80 }, { select: true });
await editor.insertImageFromClipboardData(event.clipboardData, { x: 120, y: 80 });

层级、锁定、复制与样式

editor.duplicateSelection();
editor.duplicateElements(["element-id"], 32);

editor.bringForward();
editor.sendBackward();
editor.bringToFront(["element-id"]);
editor.sendToBack(["element-id"]);

editor.setElementsLocked(["element-id"], true);
editor.setElementsLocked(["element-id"], false);

editor.copySelectedStyle();
editor.copyElementStyle("element-id");
editor.pasteStyleToSelection();
editor.pasteStyleToElements(["element-id"]);

clear() 默认会根据 confirmBeforeClear 弹出二次确认;需要跳过确认时可传 clear({ confirm: false })。通过键盘删除当前全部元素时,也会触发清空确认。

选择

editor.selectElements(["element-id"]);
editor.toggleSelection("element-id");
editor.getSelectedElements();
editor.getElementAtPoint({ x: 100, y: 100 });
editor.getElementAtPoint({ x: 100, y: 100 }, { coordinate: "screen", includeLocked: true });

折线点编辑

editor.insertLinearPoint("line-id", { x: 180, y: 120 });
editor.updateLinearPoint("line-id", 1, { x: 200, y: 160 });
editor.removeLinearPoint("line-id", 1);

线段和箭头元素的 points 字段使用相对元素起点的坐标;旧版只有 x/y/width/height 的线段数据会在加载时自动标准化为两个点。

历史

editor.undo();
editor.redo();

视口

editor.setViewport({ x: 120, y: 80, zoom: 1.2 });
editor.getViewport();
editor.zoomBy(0.1);
editor.setZoomAt(1.5, { x: 400, y: 300 });
editor.zoomToFit({ padding: 90 });

const worldPoint = editor.screenToWorld({ x: 100, y: 100 });
const screenPoint = editor.worldToScreen({ x: 20, y: 20 });

viewportBounds 可选配置 { minX, maxX, minY, maxY },配置后平移会被夹取到边界;缩放到最小/最大或平移触边时,画布会显示短暂边界提示。

统计

const stats = editor.getStats();
// { total, selected, locked, unlocked, byType, viewport }

导入导出

const scene = editor.getScene();
const json = editor.exportToJSON();
editor.loadScene(scene);
editor.loadFromJSON(json);

const pngDataUrl = editor.exportToPNG({
  padding: 32,
  pixelRatio: 2,
  backgroundColor: "#ffffff",
});

exportToPNG() 返回 data URL,可直接赋给 <img src> 或用于下载。

事件

const offChange = editor.on("change", (scene) => {
  renderElementCount(scene.elements.length);
});

editor.on("toolChange", (tool) => {});
editor.on("selectionChange", (selectedElements) => {});
editor.on("styleChange", (style) => {});
editor.on("viewportChange", (viewport) => {});
editor.on("statsChange", (stats) => {});

offChange();

场景数据格式

{
  type: "wang-canvas",
  version: 1,
  elements: [],
  appState: {
    viewport: { x: 0, y: 0, zoom: 1 },
    style: {}
  }
}

元素核心字段:

{
  id: "element_xxx",
  type: "rectangle",
  x: 80,
  y: 120,
  width: 200,
  height: 120,
  angle: 0,
  strokeColor: "#172033",
  backgroundColor: "transparent",
  fillStyle: "solid",
  strokeStyle: "solid",
  startArrowhead: "none",
  endArrowhead: "triangle",
  strokeWidth: 2,
  roughness: 1,
  opacity: 1,
  roundness: 0,
  locked: false,
  seed: 123,
  version: 1,
  createdAt: 1710000000000,
  updatedAt: 1710000000000
}

angle 使用弧度。自由书写元素额外包含 points: [{ x, y }],点坐标为世界坐标,触控笔环境下可能带有可选 pressure。线段/箭头元素也包含 points,但坐标相对元素起点。文本元素额外包含 text、fontSize、fontFamily。图片元素额外包含 src、alt、fileName、crossOrigin。

销毁

editor.destroy();

销毁会移除事件监听、断开尺寸监听,并删除 SDK 创建的 canvas。

依赖说明

SDK 本身不引入第三方运行时依赖。当前项目已有 Vite 作为开发依赖,是否安装或运行由使用者自行决定。