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

@qfei-design/canvas-table

v1.1.5

Published

A high-performance canvas-based table library for browser web apps

Readme

@qfei-design/canvas-table

基于 Canvas 的高性能表格库,适合大数据量、复杂单元格绘制和高交互场景。

适用场景

  • 数据量大,DOM 表格渲染成本高
  • 单元格样式复杂,需要图形化渲染能力
  • 需要固定列、汇总、选择、编辑、拖拽等交互
  • 需要分组表格或二次扩展渲染能力

核心能力

  • 基于 Canvas 渲染,适合高密度表格场景
  • 支持虚拟滚动和分页加载
  • 支持左侧固定列
  • 支持表头列拖拽重排
  • 支持行拖拽排序
  • 支持汇总行与汇总 loading 状态
  • 支持单元格编辑和自定义编辑器
  • 支持自定义单元格/表头渲染
  • 支持分组表格
  • 提供事件总线,便于接入方监听滚动、加载、编辑、选择等行为
  • 提供 Shape 与动画能力,用于复杂视觉扩展

安装

pnpm add @qfei-design/canvas-table

这是一个浏览器端运行的 client-only 包,核心实例可以用于 React、Vue 或原生 DOM 场景。

默认是本地数据模式:不传 virtualOptions 时,可直接调用 setData(rows) 渲染数据。 只有显式传入 virtualOptions.enabled = true 时,才进入虚拟分页加载模式。

AI / 自动集成说明

如果这个包需要被另一个 AI 项目自动安装和接入,请优先阅读:

推荐读取顺序:

  1. package.ai.json
  2. docs/agent-usage.md
  3. recipes.json
  4. capabilities.json
  5. PUBLIC_API.md
  6. 按场景选择最小示例

最快成功路径:

最快成功路径:先读 docs/agent-usage.md 选择接入路径,再从对应 recipe 指向的最小示例起步。

能力目录

核心能力文档:

进阶文档:

核心导出

import {
  CanvasTableComponent,
  GroupTableComponent,
  TextShape,
  globalEventBus,
} from '@qfei-design/canvas-table'

import type {
  IColumn,
  TableCanvasProps,
  GroupTableProps,
} from '@qfei-design/canvas-table'

还可以直接使用库内导出的 Shape、工具函数和图标资源。

快速开始

1. 普通表格

import { CanvasTableComponent } from '@qfei-design/canvas-table'
import type { IColumn } from '@qfei-design/canvas-table'

const container = document.getElementById('table-root')!

const columns: IColumn[] = [
  { key: 'id', title: 'ID', width: 80, headerAlign: 'center', align: 'center', fixed: 'left' },
  { key: 'name', title: '姓名', width: 160, editType: 'input', headerAlign: 'left', align: 'left' },
  { key: 'email', title: '邮箱', width: 240, headerAlign: 'right', align: 'right' },
]

const table = new CanvasTableComponent(container, {
  columns,
  canvasWidth: 960,
  canvasHeight: 600,
  rowKey: 'id',
  style: {
    rowHeight: 40,
    headerHeight: 40,
    fontSize: 14,
    fontFamily: 'Arial, sans-serif',
  },
})

table.setData([
  { id: 1, name: '张三', email: '[email protected]' },
  { id: 2, name: '李四', email: '[email protected]' },
])

说明:这条路径默认是“本地数据直出”,不需要 data:load 事件,也不需要传 page

2. 虚拟滚动分页加载

启用分页加载后,推荐先请求一次 count,再用总数初始化 virtualOptions.totalRowCount。随后外部监听 data:load 事件,并在回调中调用 setData(data, page) 回填数据。

const table = new CanvasTableComponent(container, {
  columns,
  canvasWidth: 960,
  canvasHeight: 600,
  rowKey: 'id',
  style: {
    rowHeight: 40,
    headerHeight: 40,
    fontSize: 14,
    fontFamily: 'Arial, sans-serif',
  },
  virtualOptions: {
    enabled: true,
    totalRowCount: 10000,
    pageSize: 100,
  },
})

const off = globalEventBus.onWithNamespace('data:load', table.tableId, async (page) => {
  const rows = await fetchRows(page)
  table.setData(rows, page)
})

推荐分页接入顺序:

  1. 先调用 fetchCount() 获取总数
  2. 用返回值设置 virtualOptions.totalRowCount
  3. 再监听 data:load
  4. 在回调中按页请求并 setData(rows, page)

如果是 React / Next.js 客户端组件,优先直接参考:

这个示例明确包含:

  • fetchTotalCount()totalRowCount 的初始化关系
  • dataPagesetData(rows, page) 的回填关系
  • data:load 的订阅方式
  • page + 1 的后端分页转换位置

3. 分组表格

import { GroupTableComponent } from '@qfei-design/canvas-table'

const groupTable = new GroupTableComponent(container, {
  columns,
  groups: ['department', 'team'],
  canvasWidth: 960,
  canvasHeight: 600,
  rowKey: 'id',
  style: {
    rowHeight: 40,
    headerHeight: 40,
    fontSize: 14,
    fontFamily: 'Arial, sans-serif',
  },
  groupVirtualOptions: {
    enabled: true,
    totalRowCount: 50,
    pageSize: 20,
  },
  virtualOptions: {
    enabled: true,
    totalRowCount: 0,
    pageSize: 100,
  },
})

globalEventBus.onWithNamespace('group:load', groupTable.tableId, async (group, page) => {
  const groups = await fetchGroups(group, page)
  groupTable.setGroup(groups, group, page)
})

框架适配

React

推荐做法是封装一个 React 组件,在 useEffect 中创建和销毁 CanvasTableComponent

可直接参考:

Vue

推荐在挂载后创建实例,在卸载前销毁实例,并通过外部状态管理数据加载和事件订阅。

可直接参考:

Vanilla DOM

如果不是 React 或 Vue,也可以直接传入真实 DOM 容器来创建 CanvasTableComponentGroupTableComponent

常见接入方式

普通表格接入

适合非分组场景。通常流程是:

  1. 创建 CanvasTableComponent
  2. 传入列定义和画布尺寸
  3. 不传 virtualOptions,首次直接调用 setData
  4. 按需监听滚动、选择、编辑和加载事件

分页加载接入

适合大数据量场景。通常流程是:

  1. 开启 virtualOptions
  2. 监听 data:load
  3. 按页请求数据
  4. 使用 setData(data, page) 回填

如果是 React 接入,建议额外保留一个 dataPage 状态,确保回填时传回表格请求的同一页码。

注意:分页模式下,setData 必须传 page,否则会抛错。

分组表格接入

适合分层展示场景。通常流程是:

  1. 创建 GroupTableComponent
  2. 配置 groupsgroupVirtualOptions
  3. 监听 group:load
  4. 调用 setGroup(data, group, page) 回填分组
  5. 组内明细数据通过 setData(data, group, page) 回填

自定义渲染

列上可以通过 renderheaderRender 进行扩展。

import { TextShape } from '@qfei-design/canvas-table'

const columns: IColumn[] = [
  {
    key: 'status',
    title: '状态',
    width: 120,
    render(cell, group) {
      group.addChild(
        new TextShape({
          name: 'status-text',
          x: 12,
          y: 24,
          attrs: {
            text: String(cell.rowData.status ?? ''),
            fill: '#1f2937',
            fontSize: 14,
          },
        }),
      )
    },
  },
]

自定义编辑器

当列设置 editType: 'custom' 时,可以通过 customEdit 自定义编辑行为。

const table = new CanvasTableComponent(container, {
  columns: [
    { key: 'name', title: '姓名', width: 160, editType: 'custom' },
  ],
  canvasWidth: 960,
  canvasHeight: 600,
  style: {
    rowHeight: 40,
    headerHeight: 40,
    fontSize: 14,
    fontFamily: 'Arial, sans-serif',
  },
  customEdit({ value, updateValue, commit, cancel }) {
    const input = document.createElement('input')
    input.value = String(value ?? '')
    input.addEventListener('input', () => {
      updateValue(input.value)
    })

    return {
      element: input,
      getValue: () => input.value,
      autoClose: true,
      destroy: () => {
        input.remove()
      },
    }
  },
})

复杂编辑器可以让 canvasTable 继续管理关闭和键盘行为,只把弹窗根节点声明给表格:

customEdit({ value, commit, cancel }) {
  const editor = document.createElement('div')
  const popupRoot = document.createElement('div')
  document.body.appendChild(popupRoot)

  return {
    element: editor,
    getValue: () => value,
    autoClose: {
      outsideClick: 'commit',
      escape: 'cancel',
      enter: 'commitAndMove',
      tab: 'commitAndMove',
    },
    relatedElements: () => [popupRoot],
    overlayOptions: {
      overflow: 'visible',
    },
    destroy: () => {
      popupRoot.remove()
    },
  }
}

编辑写入默认由表格自动完成。若业务需要先走保存/草稿层,再由宿主在成功后回填数据,可设置 editApplyMode: 'controlled',此时表格只发出编辑事件,不主动改写行数据。

核心 API

IColumn

常用字段:

  • key: 列唯一标识
  • title: 表头标题
  • width: 列宽
  • minWidth: 最小列宽
  • align: 单元格对齐方式
  • headerAlign: 表头对齐方式
  • fixed: 当前支持 left
  • editType: input | custom
  • render: 自定义单元格渲染
  • headerRender: 自定义表头渲染
  • prefixRender / suffixRender: 表头前后缀图标
  • suffixRender 返回值可设置 displayMode: 'hover',让后缀图标仅在鼠标悬浮表头单元格时展示;默认常显
  • suffixRender.onClick 接收 (column, cell, event),可用 cell 和原生点击事件定位宿主弹窗;点击后缀图标不会继续触发表头列点击
  • stateChange / headerStateChange: 状态变化回调

对齐规则:

  • headerAlign 只影响表头内置文本布局
  • align 影响表体与汇总行内置文本布局
  • 两者都支持 left | center | right
  • 未传时默认左对齐

TableCanvasProps

常用字段:

  • columns: 列配置
  • canvasWidth / canvasHeight: 画布尺寸
  • style: 表格样式
  • rowKey: 行唯一标识
  • virtualOptions: 虚拟滚动配置
  • showSummary: 是否展示汇总行
  • summaryRowHeight: 汇总行高度
  • summaryData: 汇总数据
  • summaryRenderer: 汇总渲染函数
  • showSN: 序号列配置
  • rowSortable: 行拖拽排序配置
  • selectable: 行选择配置
  • rowStyleOptions: 行样式计算
  • emptyStateOptions: 空状态配置
  • customEdit: 自定义编辑器
  • editApplyMode: 编辑写入模式,支持 auto | controlled
  • showHeader / showBody: 是否渲染表头或表体

CanvasTableComponent

常用方法:

  • updateProps(newProps)
  • updateCanvasSize(width, height, refresh?)
  • refreshCanvas()
  • setData(data, page?)
  • scrollTo(x, y)
  • scrollToRow(rowIndex)
  • getScrollState()
  • setColumnOption(colKey, key, value)
  • setRowColors(rowKeys, color?)
  • setRowTip(rowKey, params?)
  • setRowData(rowKey, data)
  • setCellData(rowKey, columnKey, data)
  • clearData(params?)
  • getPagesInView()
  • updateDataByRowKey(dataMap)
  • getTableData()
  • refreshEmptyState()
  • updateSummaryData(newSummaryData)
  • setSummaryLoading(loading, columnKey?)
  • getSelectionInfo()
  • clearSelection()
  • destroy()

GroupTableComponent

在基础表格能力之外,额外提供:

  • setGroup(data, group?, page?)
  • setData(data, groups, page?)
  • scrollTo(x, y, animate?)
  • scroll(x, y, animate?)
  • setRowTip(groupValue, rowKey, params?)
  • setRowData(groupValue, rowKey, data)
  • setCellData(groupValue, rowKey, columnKey, data)
  • extendGroup(groupValue, expand)
  • clearData()
  • clearSelection()
  • getSelectionInfo()
  • destroy()

事件与扩展

事件总线

通过 globalEventBus 可以监听实例级事件。

常见事件包括:

  • data:load: 请求某一页数据
  • group:load: 请求某个分组的子分组数据
  • group:expand: 分组展开/收起
  • scroll:change: 滚动变化
  • selection:change: 选择变化
  • edit:start: 编辑开始
  • edit:change: 编辑值变更
  • edit:cancel: 编辑取消
  • edit:end: 编辑结束
  • paste: 粘贴完成
  • row:* / cell:* / column:*: 行、单元格、列的鼠标交互事件
  • shape:*: Shape 鼠标交互事件

示例:

const off = globalEventBus.onWithNamespace('selection:change', table.tableId, (payload) => {
  console.log(payload)
})

// 取消监听
off()

Shape 与动画

库内导出了 Shape 系统,可用于更复杂的单元格图形绘制和动画。

进阶说明请参考:

已知约束

  • 当前包是 client-only,不支持 SSR 直接实例化。
  • 虽然核心库不依赖 Vue 运行时,但现有仓库 demo 主要是 Vue 页面。
  • 分页虚拟滚动模式下,setData 必须传 page
  • 分组模式下,外部需要分别维护分组加载和组内数据加载。
  • 编辑、粘贴、选择等业务提交逻辑需要接入方基于事件自行承接。
  • 当前仓库中 demo 较完整,但自动化测试仍偏少,更适合作为业务底座持续演进。

Demo 与源码参考