0

we (team & I) discovered Konva and it helped us tremendously so far, thanks a lot. However we can't figure how out to create a zoom&center effect.

We added several object to a Layer itself added to a Stage, both defined as such:

let newStageArea = new Konva.Stage({
            container: "mapContainer",
            width: canvasWidth ,
            height: canvasHeight,
            x: 0,
            y: 0,
            draggable: true,
        });

let newLayerArea = new Konva.Layer({
            width: canvasWidth,
            height: canvasHeight,
            x: 0,
            y: 0,
            //since we parse object coordinates from an external source 
            //that uses a centered origin we have to offset the layer
            offset: {
                x: -(canvasWidth / 2),
                y: -(canvasHeight / 2),
            },
        });

We are now tying to implement a method that allows, when an object belonging to the layer is clicked, to zoom on it so that it's centered and fully visible. This is is what we have:

 const handleAreaClick = (e) => {
            e.evt.preventDefault();
            let parkingSpace = e.target;

            //Get middle of object no matter its shape
            let parkingSpaceBox = parkingSpace.getClientRect({  
                relativeTo: layerArea,
            });
            let parkingSpaceCenterX = parkingSpaceBox.x + parkingSpaceBox.width / 2;
            let parkingSpaceCenterY = -1 * (parkingSpaceBox.y + parkingSpaceBox.height / 2);
      

            //Zoom&center on object
            const padding = 10;
            const focusZoomScale = Math.min(
                windowWidth / (parkingSpaceBox.width + padding * 2),
                canvasHeight/ (parkingSpaceBox.height + padding * 2));
            
            newLayerArea.setAttrs({
                x: -parkingSpaceBox.x * focusZoomScale + padding * focusZoomScale,
                y: parkingSpaceBox.y * focusZoomScale + padding * focusZoomScale,
                scaleX: focusZoomScale,            
                scaleY: focusZoomScale
            })
}

No matter how we change the x and y formulas (positive/negative...), the layer does not move to the correct position, but to the left and top of the object. However the coordinates for the middle of the object are correct.

Thanks a lot for taking the time to read through.

StLa
  • 3
  • 2
  • There is an article about center & zooming groups [here](https://longviewcoder.com/2022/07/13/konva-making-a-shape-fill-the-view/) which might be of use. The approach works on any shape. – Vanquished Wombat Aug 26 '22 at 13:26

1 Answers1

1

Here is a function to scale and centre a shape in the viewport.

/* Function to scale & position the stage as needed to have 
** the target shape fill the viewport. 
** padding parameter is optional - if given this number 
** of pixels is used as padding around the target shape.
*/
function centerAndFitShape(shape, padding) {
   
  // set padding if not provided.
  padding = padding ? padding : 0; 
   
  const 
   
    // raw rect around shape, no padding applied.  
    // Note: getClientRect gives size based on scaling, but 
    // we want the unscaled size so use 'relativeTo: stage' param 
    // to ensure consistent measurements.
    shapeRectRaw = shape.getClientRect({relativeTo: stage}), 
     
    // Add padding to make a larger rect - this is what we want to fill the view
    shapeRect = {
        x: shapeRectRaw.x - padding, 
        y: shapeRectRaw.y - padding, 
        width: shapeRectRaw.width + (2 * padding),
        height: shapeRectRaw.height + (2 * padding)
      },
     
    // Get the space we can see in the web page = size of div containing stage 
    // or stage size, whichever is the smaller
    viewRect = {
      width: stage.width() <  stage.container().offsetWidth ?  stage.width() : stage.container().offsetWidth, 
      height: stage.height() < stage.container().offsetHeight ? stage.height() : stage.container().offsetHeight},        
       
    // Get the ratios of target shape v's view space widths and heights
    widthRatio = viewRect.width / shapeRect.width,
    heightRatio = viewRect.height / shapeRect.height,
 
    // decide on best scale to fit longest side of shape into view
    scale = widthRatio > heightRatio  ? heightRatio : widthRatio,
         
    // calculate the final adjustments needed to make 
    // the shape centered in the view
    centeringAjustment = {
      x: (viewRect.width - shapeRect.width * scale)/2, 
      y: (viewRect.height - shapeRect.height * scale)/2      
    },
     
    // and the final position is...
    finalPosition = {
      x: centeringAjustment.x + (-shapeRect.x * scale), 
        y: centeringAjustment.y + (-shapeRect.y * scale)
    };
   
  // Apply the final position and scale to the stage.
  stage.position(finalPosition);
  stage.scale({x: scale, y: scale});
   
} 

There is an explanation of the approach in this blog post https://longviewcoder.com/2022/07/13/konva-making-a-shape-fill-the-view/

Vanquished Wombat
  • 9,075
  • 5
  • 28
  • 67
  • Hi Wombat, thanks a lot for your exhaustive answer! The thing is, we need to move and scale only the layer. We tried adding the layer offset (in positive) to finalPosition, but it still centers in the top-left of the object. Any ideas ? – StLa Aug 31 '22 at 11:10
  • (Edit layer offset * scale) It doesn't seem changing finalPostion changes where it centers. – StLa Aug 31 '22 at 11:19
  • Hi Wombat, sorry to follow up but we still struggle with the issue no matter what we try and which tutorial we follow, and your answer would basically save us – StLa Sep 13 '22 at 14:44