StLa
StLa

Reputation: 13

Unable to create a "zoom-in and center object" effect

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.

Upvotes: 0

Views: 173

Answers (1)

Vanquished Wombat
Vanquished Wombat

Reputation: 9525

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/

Upvotes: 1

Related Questions