基于 Canvas 实现的高性能 Excel 表格引擎组件 [OpenHarmonySheet]。
由于大部分前端项目渲染层是使用框架根据排版模型树结构逐层渲染的,整棵渲染树也是与排版模型树一一对应。因此,整个渲染的节点也非常多。项目较大时,性能会受到较大的影响。
为了提升渲染性能,提供更优质的编辑体验从 DOM 更换成 Canvas 渲染,方便开发者构建重前端大型在线文档项目,在国内外实现类似引擎的公司仅仅只有几家,如:腾讯文档,金山文档和谷歌文档等。
在项目中引入 组件即可,使用方法如下:
<element name="Sheet" src="../../components/index.hml"></element>
<Sheet
sheet="{{sheet}}"
@sheet-show="sheetShow"
@sheet-hide="sheetHide"
@click-cell-start="clickCellStart"
@click-cell-end="clickCellEnd"
@click-cell-longpress="clickCellLongpress"
@change="change"
></Sheet>
比如,我们在示例中可以监听 长按 事件,当用户 长按 的时候弹出 对话框,示例代码如下:
clickCellLongpress(evt) {
prompt.showDialog({
buttons: [{
text: '测试',
color: '#666666',
}],
});
}
以上所有的接口都会返回一个详细的 sheet 对象,里面含有以下信息:
sheetShow(sheet) {
this.el = sheet.detail.el;
this.textarea = sheet.detail.textarea;
this.viewport = sheet.detail.viewport;
this.table = sheet.detail.table;
}
渲染引擎封装好了常用的表格数据操作等接口。
用于帮助你操作单元格的所有数据和格式,也极大方便你自定义一个功能完整的工具栏:
用于帮助你操作单元格上层的高亮选框。
this.textarea 是对鸿蒙的原生 组件的封装接口,用于帮助你接受用户在界面中的输入,然后配合 this.table.xx 将数据层的数据渲染到表格渲染层,这里的输入需要真机调试,因为真机有自带输入法,实测 Previewer 无效。
import Table from "./sheet/";
this.el = this.$refs.canvas;
this.table = Table.create(this.el, 850, 800).render();
viewport 用于创建和控制单元格高亮选框,绘制在单元格上层,输入框下层,支持列选择,行选择和范围选择。
this.viewport = new Viewport(this.table).render();
在任何情况,你都可以使用 .cell 方法全局更新任一位置的数据。
this.table.cell((ri, ci) => `${ri}-${ci}`).render();
在表格中这是一个常用的方法,我们可以打碎局部单元格做合并操作。
this.table.merges(["G9:H11", "B9:D11"]).render();
可以设置你的列表行头和其高度。
this.table.colHeader({ height: 50, rows: 2 }).render();
某些情况,我们在查阅表格的时候,我们可能需要固定某些行和某些列的单元格来提高表格阅读性,此时 .freeze 就可以派上用场。
this.table.freeze("C6").render();
一般配合冻结区域使用,让冻结区域以外的选区可以做滚动操作。
this.table.scrollRows(2).scrollCols(1).render();
非特殊情况你不需要花费时间去操作单元格选框,正常情况选框接受你单元格的相对位置来绘制。
const range = this.viewport.range(
evt.changedTouches[0].localX,
evt.changedTouches[0].localY
);
this.table.selection(range);
this.viewport.render(this.table.$draw);
单元格,行和列表格结构如下:
col 列 | col 列 | |
row 行 | cell 单元格 | cell 单元格 |
row 行 | cell 单元格 | cell 单元格 |
我们可以使用以下方法更新单元格第二行第二列的数据为 8848,颜色为红色:
this.table
.cell((ri, ci) => {
if (ri === 2 && ci === 2) {
return {
text: "8848",
style: {
color: "red",
},
};
}
return this.sheet?.[ri]?.[ci] || "";
})
.render();
当然你可以精心定制每一个单元格的数据,这些数据可以来自于你的后端服务器,也可以来自于客户端的输入,配合客户端和服务端的存储能力,将数据持久化保存。
this.sheet = [
["💣", "💣", "💣"],
["💣", "🙉", "💣"],
["💣", "💣", "💣"],
];
this.table.cell((ri, ci) => this.sheet?.[ri]?.[ci] || "").render();
如果想操作更多单元格,行和列的数据和样式结构,比如行高度,列高度,单元格边框,字体排版,内外边距,下划线,背景色和旋转角度等,具体可以参考以下接口,支持各种丰富的多样的改动:
{
row: { height, hide, autoFit },
col: { width, hide, autoFit },
cell: {
text,
style: {
border, fontSize, fontName,
bold, italic, color, bgcolor,
align, valign, underline, strike,
rotate, textwrap, padding,
},
type: text | button | link | checkbox | radio | list | progress | image | imageButton | date,
}
}
除此之外还提供其他完整的表格操作接口等待你的探索:
我们将上面常见的接口做了一些演示,运行 [OpenHarmonySheet],长按任一单元格弹出对话框并点击对应选项即可查看常用接口的运行结果,此演示仅供参考,更多实际使用场景请参考文档实现:
在谈谈实现方案之前,我们先讲讲表格渲染有多复杂,表格的渲染一般来说有两种实现方案:
业界比较出名的 handsontable 开源库就是基于 DOM 实现渲染,同等渲染结果,需要对 DOM 节点进行精心的设计与构造,但显而易见十万、百万单元格的 DOM 渲染会产生较大的性能问题。因此,如今很多在线表格实现都是基于 Canvas 和叠加 DOM 来实现的,但使用 Canvas 实现需要考虑可视区域、滚动操作、画布层级关系,也有 Canvas 自身面临的一些性能问题,包括 Canvas 如何进行直出等,对开发的要求较高,但为了更好的用户体验,更倾向于 Canvas 渲染的实现方案。
我们通过分类收集视图元素,再进行逐类别渲染的方式,减少 Canvas 绘图引擎切换状态机的次数,降低性能损耗,优化渲染耗时,整个核心引擎代码控制在 1500 行左右,另补充演示代码 300 行,方便大家理解阅读和进行二次开发。
顶层 | ||
---|---|---|
↑ | DOM | 容器插件输入框等 |
↑ | Canvas | 高亮选区等 |
↑ | Canvas | 内容字体背景色等 |
底层 |
本文是对鸿蒙开发中OpenHarmony技术Sheet 表格渲染引擎的简单运用,更多的鸿蒙开发学习可以前往主页或者思心。下面是鸿蒙的学习路线图(略缩版)
高清完整版,可主页《鸿蒙开发4.0基础-高阶文档》找保存。(附鸿蒙文档)