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

@machete-jhun/canvas-studio

v0.2.3

Published

一个基于 React 和 Konva 的画布编辑组件库,支持多种图形和媒体元素的添加与编辑,适用于构建复杂的图形应用。

Readme

Canvas Studio

基于 React + Konva 的高性能画布编辑与渲染引擎。采用 Context + Store 独立引擎架构,提供与 Figma/Sketch 类似的画布编辑体验。

安装

npm install canvas-studio
# 或
pnpm add canvas-studio

核心架构 (Engine & Provider)

为了解决 React 渲染画布时的性能瓶颈,Canvas Studio 采用了 引擎模型分离 架构:

  • CanvasEditorEngine: 独立于 React 存在的纯状态控制器,负责调度画布的所有数据流(图形增删改、历史记录、选中状态等)。
  • CanvasEditorProvider: 基于 React Context 注入引擎实例。
  • useCanvasStudio 及一系列 Hooks: 基于 useSyncExternalStore,允许组件只订阅自己关心的数据切片,避免整个画布树的灾难性 Re-render。

快速使用

1. 顶层包裹 Provider

使用 <CanvasEditorProvider> 包裹你的编辑器视图。

import { CanvasEditorProvider, CanvasStudio } from 'canvas-studio'

function App() {
  return (
    // ignoredHistoryShapeTypes 用于声明哪些类型的图形【不进入撤销/重做栈】
    // 例如背景板类型,不会因为用户的撤销操作而丢失当前状态
    <CanvasEditorProvider ignoredHistoryShapeTypes={['BACK_SCREEN']}>
      <EditorView />
    </CanvasEditorProvider>
  )
}

2. 渲染画布与使用 Hooks 操作状态

在子组件中,渲染 <CanvasStudio /> 视图组件,并通过暴漏的 Hooks 获取和修改引擎数据。

import { 
  CanvasStudio, 
  useCanvasStudio, 
  useShapeList, 
  useSelectedShapeIds,
  useHistoryState 
} from 'canvas-studio'

function EditorView() {
  // 获取引擎实例,用于执行操作(增删改、撤销重做等)
  const engine = useCanvasStudio()
  
  // 订阅响应式状态数据
  const shapes = useShapeList()
  const selectedIds = useSelectedShapeIds()
  const { canUndo, canRedo } = useHistoryState()

  // 添加图形
  const handleAddRect = () => {
    engine.addShapes([{ 
      id: 'rect-1', 
      type: 'RECT', 
      x: 100, y: 100, width: 100, height: 100, fill: '#ff0000' 
    }])
  }
  
  // 获取底层 Konva 的 Transformer 实例(例如想要强制刷新包围盒尺寸)
  const handleRefreshBox = () => {
    const tr = engine.getTransformer()
    tr?.forceUpdate()
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
      <div className="toolbar">
        <button onClick={handleAddRect}>添加矩形</button>
        <button onClick={engine.undo} disabled={!canUndo}>撤销</button>
        <button onClick={engine.redo} disabled={!canRedo}>重做</button>
        <button onClick={engine.fitToContent}>居中适应画面</button>
      </div>
      
      <div style={{ flex: 1 }}>
        {/* 画布纯视觉容器 */}
        <CanvasStudio 
          width={800} 
          height={600} 
          backgroundType="grid" 
        />
      </div>
    </div>
  )
}

API 参考

<CanvasStudio /> Props

负责纯视觉与交互层面的容器组件配置:

| 属性 | 类型 | 默认值 | 说明 | |------|------|--------|------| | width | number | 800 | 画布初始宽度 | | height | number | 600 | 画布初始高度 | | className | string | - | 自定义容器类名 | | renderMode | boolean | false | 开启后进入只读渲染模式:关闭辅助线、游标、选中态与变形控制框等所有交互 | | backgroundType | 'grid' \| 'point' \| 'none' | 'grid' | 画布背景底纹样式 | | hideRuler | boolean | false | 是否隐藏周边的坐标标尺 | | disableRulerAuxiliary | boolean | false | 是否禁用从标尺拖拽生成辅助线的功能 | | ppmm | number | 1 | 物理像素映射比:每毫米单位等于多少级像素 | | auxiliaryLineColor | string | - | 辅助线的全局颜色 | | auxiliaryLineDashType | 'solid' \| 'dash1' \| 'dash2' | - | 辅助线的全局虚线类型 | | boundary | BoundaryRect | - | 约束矩形区域(内容坐标系)。设置后图形拖拽/缩放将限制在该区域内,舞台缩放不低于初始适配比例,舞台平移也会被夹拢使边界始终在屏幕可见范围内 | | boundaryStyle | BoundaryStyle | - | 边界矩形的视觉样式,支持 stroke / strokeWidth / dash 自定义虚线外框 | | boundarySvgMask | string[] | - | SVG 路径字符串数组,用于在边界区域上层叠加一层设备形状遮罩预览(如圆形屏幕轮廓) |

CanvasEditorEngine (引擎实例接口)

通过 const engine = useCanvasStudio() 获取,提供了丰富的底层命令:

视图控制

  • getStage(): 获取底层的 Konva.Stage 实例。
  • getTransformer(): 获取底层的 Konva.Transformer 实例(常用于在外部修改图形尺寸后主动调用其 forceUpdate() 重算包围盒)。
  • fitToContent(): 根据当前画布中的内容元素自动缩放、居中平移视图,使所有内容都展示在最佳视野内。

集合 / 元素管理

  • setShapes(shapes): 覆盖所有图形(会清空选中状态并记录一次历史)。
  • addShapes(shapes): 增加新的图形。
  • updateShape(shape) / updateShapes(shapes): 更新选定图形的数据。
  • removeShapes(ids): 移除特定 ID 的图形。
  • clearShapes(): 清空画布。
  • duplicateShape(shape): 克隆图形到右下方错开的位置,并自动选中。

背景管理 (挂载在不同层,永远垫底)

同上提供了一套操作底层背景节点的方法:setBgShapes, addBgShapes, removeBgShapes, removeBgShape, clearBgShapes

选中与状态

  • setSelectedShapeIds(ids): 覆盖设定当前选中的节点 ID 集合。
  • addSelectedShapeId(id): 将某组节点设为激活。
  • removeSelectedShapeId(id): 取消某个节点的激活。

历史栈

  • undo(): 撤销。
  • redo(): 重做。

运行时数据字典

  • updateRuntimeVariables(variables, mode = 'merge' | 'replace'): 更新运行时动态注入在文本、状态机动画等上的变量依赖字典。该过程纯数据化向视图推流,防止由于数据更迭引起的 React 组件树销毁重叠。

Hooks 切片获取器 (Selectors)

| Hook | 返回值 | 作用描述 | |------|--------|----------| | useCanvasStudio() | CanvasEditorEngine | 获取执行命令的引擎单例。 | | useShapeList() | MyShapeConfig[] | 响应式获取操作区的图形集合列表。 | | useBgShapeList() | MyShapeConfig[] | 响应式获取背景区的图形集合列表。 | | useSelectedShapeIds() | string[] | 响应式获取当前处于选中聚焦状态的节点 ID 数组。 | | useRuntimeVariables() | Record<string, unknown> | 响应式获取当前的运行时动态变量字典。 | | useHistoryState() | { canUndo: boolean, canRedo: boolean } | 响应式获取当前撤销重做按钮的状态。 |

.your-canvas-wrapper {
  /* 画布容器整体背景,可使用颜色或 CSS gradient */
  --background: linear-gradient(180deg, #0e0e44 0%, #3b2863 100%);

  /* backgroundType="grid" 棋盘格模式 */
  --grid-color: rgba(0, 0, 0, 0.08); /* 棋盘深色格颜色 */
  --grid-bg-color: #ffffff;           /* 棋盘浅色格(底色)颜色 */

  /* backgroundType="point" 点阵模式 */
  --point-color: #2f3542;   /* 点的颜色 */
  --point-bg-color: #ffffff; /* 点阵背景色 */
}

所有变量均有内置的安全默认值,未注入时自动回退。示例:

import './inject.css' // 包含上述 CSS 变量定义

<div className="your-canvas-wrapper">
  <CanvasStudio backgroundType="grid" />
</div>

核心 Hooks 与 Engine 调度中心 (Core Pipeline)

不要使用常规状态变量,你需要的所有下发操作句柄全由 const engine = useCanvasStudio() 获取调度抛出:

  • engine.addShapes(shapes) / engine.removeShapes(ids): 压入与剔除正常渲染层的组件节点,动作入史。
  • engine.updateShape(shape) / engine.updateShapes([shape]): 注入节点更替,属性深度重置。
  • engine.setShapes(shapes): 一键替换当前渲染层的全部节点,动作入史。
  • engine.addBgShapes(shapes) / engine.removeBgShape(id): 操作脱离标准渲染层的网格底层安全底色图形。
  • engine.setSelectedShapeIds(ids): 直接下发控制指令将目标设定为框选焦点。
  • engine.setBgShapes(shapes): 一键替换底部层的全部节点,动作入史。
  • engine.undo() / engine.redo(): 后退与重做。底层自建隔离带跳过且清扫由 ignoredHistoryShapeTypes 圈定的免疫目标层。
  • engine.fitToContent(): 一键扫描全局边界,将最大包含框按比例吸附拉放至视野正中。
  • engine.updateRuntimeVariables(variables, mode): 向下派发非反应式变量对象池,只走 Konva 树进行局部字段的底层复写,无额外内存穿透影响。

对外的视图响应式 Hooks

  • useShapeList() : 获取主显示图层 MyShapeConfig[] 实例数据。
  • useBgShapeList(): 获取底部层 MyShapeConfig[] 数据。
  • useSelectedShapeIds(): 获取活跃选择目标的 string[] 集合。
  • useActiveShape(): 快捷获取单目标下该选中的 Config。
  • useHistoryState(): 返回操作栈上下界限:{ canUndo: boolean, canRedo: boolean }
  • useRuntimeVariables(): 获取渲染层当前的全局变量体。

🛠️ 导出路径结构说明

由于包体细分打包,你需要去往各个精确口读取能力:

  • 主组件/类型层@machete-jhun/canvas-studio
  • 后台纯计算离屏导出域@machete-jhun/canvas-studio/render
  • 杂项工具域@machete-jhun/canvas-studio/utils
  • 无状态原子生成器@machete-jhun/canvas-studio/utils/shapeTools
  • 静态枚举与常量字面@machete-jhun/canvas-studio/constants

Boundary Constraint(边界约束系统)

通过向 <CanvasStudio /> 传入 boundary prop,可以激活一套完整的边界约束体系:

  • 图形拖拽约束:所有可拖拽节点的 dragBoundFunc 均被实时接管,图形超出边界时自动夹拢回区域内(节点尺寸大于边界时固定在边界起点)。
  • Transformer 缩放约束:多选/单选的包围盒变换(boundBoxFunc)同样限制在边界内,保证 resize 不会越界。
  • 最小缩放保护:舞台缩放不会低于 boundary 首次适配画布时的初始比例,防止边界缩到画布以外。
  • 舞台拖拽夹拢:平移画布时,边界矩形的任意一侧都不会被拖出屏幕可见区域。
  • BoundaryLayer 可视化:自动在底部渲染虚线边框;若传入 boundarySvgMask,则额外叠加 SVG 形状遮罩(适用于圆形屏幕等异形设备预览)。

相关类型

type BoundaryRect = {
  x: number
  y: number
  width: number
  height: number
}

type BoundaryStyle = {
  stroke?: string      // 虚线框颜色,默认 '#4096ff'
  strokeWidth?: number // 线宽,默认 1
  dash?: number[]      // 虚线节奏,默认 [6, 3]
}

使用示例

import { CanvasStudio } from 'canvas-studio'

function EditorView() {
  const [boundary, setBoundary] = useState<BoundaryRect | undefined>()

  return (
    <CanvasStudio
      width={800}
      height={600}
      boundary={boundary}
      boundaryStyle={{ stroke: '#4096ff', strokeWidth: 1, dash: [6, 3] }}
      // 圆形屏幕遮罩示例 (SVG 路径字符串,内容坐标系,边界适配后自动缩放)
      boundarySvgMask={[
          `<svg width="480" height="480" viewBox="0 0 480 480" xmlns="http://www.w3.org/2000/svg"> <circle cx="240" cy="240" r="240" fill="none" stroke="red" stroke-width="2"/> </svg>`,
      ]}
    />
  )
}

boundary 使用内容坐标系(与图形的 x / y / width / height 同一单位),引擎内部会自动将其转换为实时的 screen-space 后再参与夹拢计算,因此画布缩放和平移均不影响约束精度。


BackScreen(设备背景板)

MyBackScreenShape 是专为多屏硬件设备设计的特殊图形类型,用于在画布上标记一块物理屏幕的占位区域。

| 属性 | 类型 | 必填 | 说明 | |------|------|------|------| | screenId | string | ✓ | 设备唯一标识,对应硬件 chip ID | | width | number | ✓ | 屏幕编码宽度(像素) | | height | number | ✓ | 屏幕编码高度(像素) | | fill | string | ✓ | 占位色(十六进制),用于编辑器预览 | | screenShape | 'rect' \| 'circle' | - | 屏幕形状,默认矩形;圆形屏幕传 'circle' |

import { createBackScreen } from '@machete-jhun/canvas-studio/utils/shapeTools'

engine.addBgShapes([
  createBackScreen({
    screenId: 'A3F2C1',
    width: 480,
    height: 480,
    fill: '#1a1a2e',
    screenShape: 'circle', // 2.1 寸圆形屏
    x: 100,
    y: 100,
  })
])

screenShape 只影响编辑器内的视觉预览,不影响推流逻辑(设备硬件决定最终显示形状)。

License

MIT © Machete Jhun