背景
最近用Antv-g6做树图开发时,需要做滚动的表格,查了g6的文档,发现没有滚动,只能自己来做了。主要思路是,监听鼠标滚轮,判断当前鼠标是否在表格节点位置,然后不断截取数组,然后更新当前节点。
实现
首先,需要使用g6
的自定义复合交互Behavior
,去自定义实现wheel
事件逻辑,同时在自定义节点中也要配合渲染对应的数据。主要实现代码如下:
import G6 from '@antv/g6'
/**
* 判断坐标是否在对应的节点范围内
* @param {object} point 需要判断的位置
* @param {object} bbox 节点的几何信息
*/
function isInBBox(point, bbox) {
const { x, y } = point;
const { minX, minY, maxX, maxY } = bbox;
return x < maxX && x > minX && y < maxY && y > minY;
}
// 画布缩放比例
let ratio = 1;
// 行高
const rowHeight = 50;
// 一次最多显示的行数
const rowCount = 10;
// 自定义交互事件
G6.registerBehavior('my-table-scroll', {
getEvents() {
return {
// 事件映射
wheel: 'scroll',
}
},
// 自定义scroll事件,
scroll: (ev) => {
// 阻止默认事件,默认会缩放画布,
// 由于自定义了wheel事件,所以缩放事件下面也需要自己实现
ev.preventDefault();
// 将屏幕/页面坐标转换为渲染坐标。
const point = graph.getPointByClient(e.clientX, e.clientY);
// 找到当前需要滚动位置的节点
const nodes = graph.getNodes().filter((node) => {
const bbox = node.getBBox();
const group = node.getContainer();
// 自定义节点时,给需要滚动的节点shape自定义了name='scroll-box'
const shape = group.findAllByName('scroll-box')[0];
if (!shape) return false;
return isInBBox(point, bbox);
})
// 优化滚动性能
requestAnimationFrame(() => {
// 垂直位置滚动的距离
const y = ev.deltaY || ev.movementY;
// 如果在需要滚动的节点上
if (nodes && nodes.length) {
const node = nodes[0];
// 拿到当前节点的数据
const model = node.getModel();
// 如果当前的数据较少,不需要滚动,这里的rowCount根据需求自定义
if ((model.tablesData || []).length <= rowCount) return;
// 开始截取的位置,获取数据中保存的截取位置,如果还没滚动过取0
const index = model.startIndex || 0;
// 计算出开始的索引数
// y / rowHeight代表偏移行数,如y是2px,表示0.04行,方便自定义节点中使用
let startIndex = index + y / rowHeight;
if (startIndex < 0) startIndex = 0;
// 可以取的最大maxIndex值
const maxIndex = model.tablesData.length - rowCount;
if (startIndex > maxIndex) startIndex = maxIndex;
// 更新节点,将新的startIndex值合并到节点中,代替旧的
graph.update(node, { startIndex });
return;
}
// 如果不是在滚动节点,需要缩放画布,每次缩放变化的比例
if (y > 0) {
ratio -= 0.05;
} else {
ratio += 0.05
}
// 控制最大和最小的缩放比例
if (ratio < 0.25) {
ratio = 0.25
} else if (ratio > 1.75) {
ratio = 1.75;
}
// 四舍五入ratio
ratio = Math.round(ratio * 100) / 100;
// 将渲染坐标转换为 Canvas 画布坐标。
const canvasPoint = graph.getCanvasByPoint(point.x, point.y);
// 根据鼠标所在的canvas画布位置为中心进行缩放
graph.zoomTo(ratio, canvasPoint)
})
}
});
// 自定义节点部分
G6.registerNode('my-node-card', {
draw: (cfg, group) => {
// 省略其余节点的代码,主要看滚动节点的部分代码
const { tablesData, startIndex = 0 } = cfg;
// 需要渲染的数据
let list = tablesData
// 滚动偏移值
let offsetY = 0;
// 渲染的行数
let len = tablesData.length;
if (len > rowCount) {
len = rowCount
// 向下取整
const index = Math.floor(startIndex);
// 这里多取一行,是为了滚动时看起来比较连续
// 当第一行还没完全滚出去时,最后面需要多一行
list = list.slice(index, index + rowCount + 1);
// 偏移值用索引的小数部分进行计算,同时需要取反
offsetY = -(startIndex % 1) * rowHeight;
}
// 节点高度,这里节点高度需要多两行
// 因为滚动的上下边界需要有遮挡条,
const nodeHeight = (len + 2) * rowHeight
// ...省略后续的节点渲染逻辑,不同的项目千差万别
// 节点框的渲染位置不变
// 里面滚动部分的位置根据数据和偏移值来确定,就可以达到滚动效果
}
})
// 树图实例,具体配置省略
const graph = new G6.TreeGraph({
// 大部分配置省略
modes: {
default: ['my-table-scroll']
},
// 自定义节点
defaultNode: {
type: 'my-node-card'
}
})
最后,如果追求完美,可以加上滚动条,这样看起来会更加直观。还可以加上拖动滚动条进行滚动等效果(待完善。。。)