Keep Labels and Handles the Same Size While Zooming a Canvas
When you scale a stage, everything scales — including the parts you meant as interface. Labels become unreadable at 20% and enormous at 400%. Drag handles grow until they cover the shape they are meant to resize.
The fix is to scale those nodes by the inverse of the stage scale, so the two cancel out.
const s = stage.scaleX();
label.scale({ x: 1 / s, y: 1 / s });
The node keeps its position in scene coordinates, so it still travels with the shape it belongs to. Only its size stops changing.
When to recompute it
Do it in the same handler that changes the zoom. There is no event called
scale change — Konva names attribute events <attr>Change, so the real one
is 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);
Listening to scaleXChange works, but calling your update function directly
from the wheel handler is usually simpler, and it keeps the redraw in one place.
Labels pinned to shapes
Scroll to zoom. The labels stay readable at every level, while the shapes they belong to scale normally.
- Vanilla
- React
Stroke width is a special case
A stroke has its own switch, so you do not need to counter-scale for it:
shape.strokeScaleEnabled(false); // the stroke keeps its pixel width when zoomed
Konva.Transformer anchors are ordinary shapes, so they do scale with the
stage. To keep them a usable size while zooming, drive anchorSize from the
scale instead of counter-scaling the transformer:
tr.anchorSize(10 / stage.scaleX());
A layer that ignores the zoom entirely
Counter-scaling keeps a node's size fixed while its position still follows the scene. For a HUD — a toolbar, a scale bar, a coordinate readout — you want both fixed, in screen space.
Every layer is a child of the stage, so it inherits the stage transform. Cancel the whole transform rather than just the scale:
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 });
}
That maps layer coordinates back onto screen coordinates: a node at (10, 10)
on that layer stays 10 pixels from the top left however the stage is zoomed or
panned. Call it from the same place you call the counter-scale.
Set listening: false on a HUD layer if it is purely decorative, so it does not
absorb clicks meant for the scene below.
Related
- Zoom relative to pointer — the zoom maths used above
- Infinite canvas — pan and zoom over an unbounded scene
- Canvas minimap — a second view of a large stage