Your app allows zooming and panning and now you need to find the center of the current canvas view to add a new shape. How do you do that...
TLDR: See the CodePen demo below.
See also this related post about scribing a rect onto rotated, translated and scaled layer which uses the same inverse transform technique.
The mission
Our Konva diagram has a stage and layers which both start at position (x: 0, y: 0). Knowing the center of the visible area we see in the canvas (the viewport) is straightforward. Then we zoom the stage, or maybe we allow the user to drag the layer. Zooming at a point moves the stage origin away from (0, 0), and panning the layer changes the layer position. These things can happen independently. It's also possible, though less common, to rotate layer and stage!
Now maybe we need to let the user add a shape in the center of the viewport. Working out that center point is our mission.
Note: Shapes can be in groups and both can be independently scaled, positioned, and rotated. This solution can work to find their position too.
The solution- transforms
2D vector graphics engines - like Konva and the HTML5 canvas - use affine transformations. There's a great video explainer of affine transformations here by Steve Seitz.
The solution to our mission is to be able to reverse the transformations experienced by the layer. Here's the code for a function to get the point on the layer that is in the center of the viewport.
// get the visible layer rect.
function getLayerRect() {
// Get viewport dimensions
const canvas = layer.getCanvas(),
// Get the layer's transform
transform = layer.getAbsoluteTransform().copy(),
// Invert the transform to go from viewport space to layer space
inverseTransform = transform.invert(),
// Convert viewport corners to layer space
c1 = inverseTransform.point({ x: 0, y: 0 }),
c2 = inverseTransform.point({ x: canvas.width, y: canvas.height }),
// combine into a useful rectangle object
viewRect = {
x: c1.x,
y: c1.y,
width: c2.x - c1.x,
height: c2.y - c1.y,
centerX: c1.x + (c2.x - c1.x) / 2,
centerY: c1.y + (c2.y - c1.y) / 2,
};
// For demo put the cross in the center of the visible rect.
centerCross.position({
x: viewRect.centerX,
y: viewRect.centerY,
})
return viewRect
} These lines do the magic.
// Get the layer's transform
transform = layer.getAbsoluteTransform().copy(),
// Invert the transform to go from viewport space to layer space
inverseTransform = transform.invert(),
// Convert viewport corners to layer space
c1 = inverseTransform.point({ x: 0, y: 0 }),
c2 = inverseTransform.point({ x: canvas.width, y: canvas.height }),When we get the absolute transform of any object, we are gathering the full mathematical transform matrix - that is the effect of all and any transforms of the object and the object's parents. The good news is that we don't need to worry about the complex internals of that!
Quick note: we must copy this transform otherwise we screw up the original with what comes follows.
Next we invert that transform. This gives us a magic spell that we can apply to any point in view port space and be told where that point is on the layer with all of the layer transforms applied.
Et voila - all that remains is to get the center from the corner points and we know the point on the layer that is in the center of the canvas.
Summary
We've used the technique of inverting an object's absolute transform to find the position on the layer that is at the center of the canvas viewport. We did this for a layer - we had a specific mission - but the technique is viable with any Konva shape, even in shapes that are deep into group hierarchies.
Thanks for reading.
VW. April 2025