你不知道的 Canvas 表格交互

本文深入探讨了使用Canvas绘制表格时的交互实现,包括单选高亮、行列联动高亮、刷选高亮、行高列高动态调整等,通过事件委托和Canvas图形数据结构,实现了类似DOM的交互体验。S2,基于AntV的2D渲染引擎G,提供了丰富的内置交互,并支持自定义交互,打造高效的大数据表格分析解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

S2 是 AntV 在多维交叉分析表格领域的解决方案,主要用于看数分析, S2 采用 canvas 来进行表格绘制 (基于 易用、高效、强大的 2D 可视化渲染引擎 G ) , 同时内置大量的交互能力来辅助用户看数, 如 行列联动高亮 单选/多选高亮 刷选高亮 行高列宽动态调整 列头隐藏 等, 同时还支持 自定义交互, 本文主要介绍 S2 是如何实现这些交互的。

DOM 交互和 Canvas 交互的区别

以单元格点击为例, 得益于强大的 CSS3选择器, 我们可以准确的监听任意 dom 元素的点击事件

<ul class="cell">
  <li id="cell1">我是第一个单元格</li>
  <li id="cell2">我是第二个单元格</li>
</ul>
const cell = document.querySelector('.cell > li:first-child');

cell.addEventListener('click', () => {
  console.log('第一个单元格: 别点我!');
})

但是 canvas 就只有一个 <canvas/> dom 元素

<canvas />

如何准确的知道点击的是哪个单元格呢? 答案是 事件委托+ 鼠标坐标

const canvas = document.querySelector('canvas');

canvas.addEventListener('click', () => {
  console.log('我点的是哪个单元格?');
})

在 dom 中, 有一个很经典的事件冒泡应用场景, 那就是 事件委托, 还是以上面的例子, 我们可以只监听父级的 ul元素, 根据当前的 event.target 来判断当前点击的是哪一个单元格

const cell = document.querySelector('.cell');

cell.addEventListener('click', (event) => {
  const CELL_ID = 'cell1'
  if (event.target?.id === CELL_ID) {
    console.log('我是第一个单元格');
  }
});

所以在 canvas中, 我们也可以依葫芦画瓢, 不同点是, 单元格不再是一个个的 dom 节点, 而是一个个 canvas 图形 对应的数据结构, 类似于虚拟dom

const cell = new Shape({ type: 'rect' })
public getCell<T extends S2CellType = S2CellType>(event): T {
  let parent = event.target;
  // 判断当前 target 属于哪一个实例
  while (parent && !(parent instanceof Canvas)) {
    if (parent instanceof BaseCell) {
      // 在单元格中,返回true
      return parent as T;
    }
    parent = parent.get?.('parent');
  }
  return null;
}

// antv/g 提供的 Canvas 构造器
const canvas = new Canvas()

canvas.on('click', (event) => {
  const cell = this.getCell(event)
})

事件分类

通过事件委托, 能够获取到具体触发事件的单元格 ( 具体实现 )

  • 角头单元格点击: S2Event.CORNER_CELL_CLICK
  • 列头单元格点击: S2Event.COL_CELL_CLICK
  • 行头单元格点击: S2Event.ROW_CELL_CLICK
  • 数据单元格点击: S2Event.DATA_CELL_CLICK
  • 单元格双击
  • 单元格右键
  • ...

在监听到对应事件后, 通过内部的 event emitter 分发出去, 从而触发对应的单元格事件

 private onCanvasMousedown = (event: CanvasEvent) => {
    const cellType = this.spreadsheet.getCellType(event.target);
    switch (cellType) {
      case CellTypes.DATA_CELL:
        this.spreadsheet.emit(S2Event.DATA_CELL_MOUSE_DOWN, event);
        break;
      case CellTypes.ROW_CELL:
        this.spreadsheet.emit(S2Event.ROW_CELL_MOUSE_DOWN, event);
        break;
      case CellTypes.COL_CELL:
        this.spreadsheet.emit(S2Event.COL_CELL_MOUSE_DOWN, event);
        break;
      case CellTypes.CORNER_CELL:
        this.spreadsheet.emit(S2Event.CORNER_CELL_MOUSE_DOWN, event);
        break;
      case CellTypes.MERGED_CELL:
        this.spreadsheet.emit(S2Event.MERGED_CELLS_MOUSE_DOWN, event);
        break;
      default:
        break;
    }
  };

this.spreadsheet.on(S2event.DATA_CELL_MOUSE_DOWN, (event) => {
  console.log('数值单元格点击')
})

交互分类

有了分好类的单元格事件, 我们就可以将其排列组合。 比如刷选高亮, 就对应 数值单元格的 mousedown+ mousemove+ mouseup 事件, 再将获取到的单元格 meta 信息存储在状态机, 最后根据交互状态进行 canvas 重绘

交互类型

名称

适用场景

全选

ALL_SELECTED

复制

选中

SELECTED

单选/多选/行列批量选中

未选中

UNSELECTED

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值