Skip to main content

缩放画布时保持标签和控制柄大小不变

当你缩放 stage 时,所有内容都会缩放——包括那些原本作为界面的部分。标签在 20% 时变得难以阅读,在 400% 时又变得巨大。拖动控制柄会不断变大,直到遮住它们原本要调整大小的图形。

解决方法是按照 stage 缩放比例的倒数缩放这些节点,使两者相互抵消。

const s = stage.scaleX();
label.scale({ x: 1 / s, y: 1 / s });

节点会保留其在场景坐标中的位置,因此它仍会随所属图形一起移动。只有它的大小不再变化。

何时重新计算

在修改缩放的同一个处理程序中执行。不存在名为 scale change 的事件——Konva 将属性事件命名为 <attr>Change,因此真正的事件是 scaleXChange

// Correct: fires when scaleX is set.
stage.on('scaleXChange', updateLabels);

// Not a real event. This handler will never run, silently.
stage.on('scale change', updateLabels);

监听 scaleXChange 是可行的,但通常直接在滚轮处理程序中调用更新函数更简单,并且可以将重绘集中在一个位置。

固定在图形上的标签

滚动以缩放。标签在任何缩放级别下都保持清晰易读,同时它们所属的图形仍会正常缩放。

import Konva from 'konva';

const stage = new Konva.Stage({
container: 'container',
width: 500,
height: 300,
draggable: true,
});

const layer = new Konva.Layer();
stage.add(layer);

const shapes = [
{ x: 90, y: 90, radius: 45, fill: '#2f6df6', name: 'Alpha' },
{ x: 250, y: 170, radius: 35, fill: '#10784f', name: 'Beta' },
{ x: 390, y: 100, radius: 40, fill: '#d6336c', name: 'Gamma' },
];

const labels = [];

shapes.forEach((s) => {
layer.add(new Konva.Circle({ x: s.x, y: s.y, radius: s.radius, fill: s.fill }));

// The label sits in scene coordinates, so it moves with its circle.
const label = new Konva.Text({
x: s.x,
y: s.y + s.radius + 6,
text: s.name,
fontSize: 14,
fill: '#333',
});
label.offsetX(label.width() / 2);
labels.push(label);
layer.add(label);
});

// Undo the stage scale on every label.
function keepLabelsReadable() {
const s = stage.scaleX();
labels.forEach((label) => label.scale({ x: 1 / s, y: 1 / s }));
}

const hint = new Konva.Text({
x: 10,
y: 10,
text: 'scroll to zoom, drag to pan',
fontSize: 12,
fill: '#888',
});
layer.add(hint);

stage.on('wheel', (e) => {
e.evt.preventDefault();

const oldScale = stage.scaleX();
const pointer = stage.getPointerPosition();

const pointTo = {
x: (pointer.x - stage.x()) / oldScale,
y: (pointer.y - stage.y()) / oldScale,
};

const direction = e.evt.deltaY > 0 ? -1 : 1;
const newScale = Math.max(0.2, Math.min(5, oldScale * (direction > 0 ? 1.08 : 1 / 1.08)));

stage.scale({ x: newScale, y: newScale });
stage.position({
x: pointer.x - pointTo.x * newScale,
y: pointer.y - pointTo.y * newScale,
});

keepLabelsReadable();
});

描边宽度是一个特殊情况

描边有自己的开关,因此你不需要为它进行反向缩放:

shape.strokeScaleEnabled(false);   // the stroke keeps its pixel width when zoomed

Konva.Transformer 的锚点是普通形状,因此会随舞台一起缩放。要在缩放时保持锚点大小可用,请根据缩放比例设置 anchorSize,而不是对 transformer 进行反向缩放:

tr.anchorSize(10 / stage.scaleX());

完全忽略缩放的图层

反向缩放可以保持节点的大小不变,同时让其位置仍然跟随场景。对于 HUD——工具栏、比例尺、坐标读数——你希望大小和位置都固定在屏幕空间中。

每个图层都是舞台的子项,因此会继承舞台变换。不要只抵消缩放,而是抵消整个变换:

function pinLayerToScreen(layer) {
const s = stage.scaleX();
layer.scale({ x: 1 / s, y: 1 / s });
layer.position({ x: -stage.x() / s, y: -stage.y() / s });
}

这会将图层坐标映射回屏幕坐标:无论舞台如何缩放或平移,该图层上位于 (10, 10) 的节点都会保持在距离左上角 10 像素的位置。请在调用反向缩放的同一位置调用它。

如果 HUD 图层纯粹用于装饰,请将其设置为 listening: false,这样它就不会吸收原本应传递给下方场景的点击。

相关内容