简介
这个文章主要讨论在mxGraph进行放大缩小的时候遇到的性能和中心点计算问题。
Pinch 性能问题
MxGraph 在mxPanningHandler 中实现的Pinch功能直接调用的zoom
// mxPanningHandler 文件
this.gestureHandler = mxUtils.bind(this, function(sender, eo)
{
if (this.isPinchEnabled())
{
var evt = eo.getProperty('event');
if (!mxEvent.isConsumed(evt) && evt.type == 'gesturestart')
{
this.initialScale = this.graph.view.scale;
// Forces start of panning when pinch gesture starts
if (!this.active && this.mouseDownEvent != null)
{
this.start(this.mouseDownEvent);
this.mouseDownEvent = null;
}
}
else if (evt.type == 'gestureend' && this.initialScale != null)
{
this.initialScale = null;
}
if (this.initialScale != null)
{
this.zoomGraph(evt);
}
}
});
每次手指移动缩放zoomGraph 都会遍历每一个node进行计算位置和绘制,几十个结点的时候,缩放操作就会很卡顿,图形越大越卡顿。
drawio 使用的是官方缩放算法,结点过多的时候掉帧严重(180ms一帧),操作卡顿。
drawIo在缩放上面写了上百行代码对mxGraph进行优化,lazyZoom缩放操作,scheduleZoom 方法进行调度缩放任务但是效果一般。
实现自定义Pinch算法
-
当缩放的时候
-
计算当前scale和鼠标为中心的偏移值 3. Canvas 元素设置样式 transform: scale translate // 注意scale 和translate的前后顺序影响很大,transfrom的执行顺序是从右到左
-
当缩放结束
-
移除画布上的transform: scale
transform: scale translate 注意scale 和translate的前后顺序影响很大,transfrom的执行顺序是从右到左
自定义算法性能
可以看到各种缩放操作下性能面板程序绿色。没有出现drawio那样的大范围掉帧。
不过性能中依然有一定频率的掉帧。原因就是缩放结束的时候,同步给mxGraph进行绘制的时候,mxGraph遍历了每个结点进行位置计算和重新绘制
Pinch 中心点计算
每次缩放,都要围绕着鼠标当前位置进行缩放,需要重新计算画布中结点的位置和scale。MxGraph不支持此操作。
Issue 中的方案使用官方缩放算法,性能极差。
mxEvent.addMouseWheelListener((evt, up) => {
if (mxEvent.isConsumed(evt)) {
return;
}
let gridEnabled = graph.gridEnabled;
// disable snapping
graph.gridEnabled = false;
let p1 = graph.getPointForEvent(evt, false);
if (up) {
graph.zoomIn(); //
} else {
graph.zoomOut();
}
let p2 = graph.getPointForEvent(evt, false); // 通过缩放前后的差异来获取偏移值,性能特别差
let deltaX = p2.x - p1.x;
let deltaY = p2.y - p1.y;
let view = graph.view;
view.setTranslate(view.translate.x + deltaX, view.translate.y + deltaY); // 每次缩放都进行大量结点计算
graph.gridEnabled = gridEnabled;
mxEvent.consume(evt);
}, container);
let {
origin: [ox, oy],
first,
movement: [ms],
memo,
offset: [offsetScale]
} = state
const { translateX, translateY, scale: pScale } = getTransform(canvas)
if (first) {
const { x: containerX, y: containerY } =
container.getBoundingClientRect()
const tx = ox - containerX // 缩放中心点ox 减去画布左边x获得画布坐标系中中心点x值
const ty = oy - containerY
memo = [tx, ty]
}
if (state.delta[0] > 0) {
ms = 0.1 * SCALE_SPEED
} else {
ms = -1 * 0.1 * SCALE_SPEED
}
const scale = offsetScale
const factor = scale / pScale // 本次缩放比率
let x = ((1 - factor) * memo[0]) / scale + translateX // 缩放中心点计算核心算法
let y = ((1 - factor) * memo[1]) / scale + translateY // 由于mxGraph计算的位置的时候先加translate然后乘以scale,所以这里必须处以scale
canvas.style.transform = `scale(${scale},${scale}) translate(${x}px,${+y}px) `
store.commit('workflowStore/updateCanvasScale', scale * view.scale)
return memo
边界计算借用use-gesture 的bounds 来实现。计算bounds要明确知道边界是根据state上面的offset来进行判定的。offset是一个手势整个生命周期的偏移量。
我们在一次手势例如pinch和pinchend的时候要对offset进行重置,这样边界计算才是正确的。