Search code examples
canvashtml5-canvaskonvajsreact-konvakonva

Emulate "object-fit: contain" for Image in Konva.js


I am trying to make resizable image with content fully fitted in rect. I found solution how to emulate object-fit: cover, but I need contain. I made some solution:

const width = size.width;
const height = size.height;

var scalex = image.width / width;
var scaley = image.height / height;
var scale = Math.max(scalex, scaley);

return {
  cropWidth: width * scale,
  cropHeight: height * scale,
};

But it not centered the image inside

enter image description here

Have any ideas how to make it centered?


Solution

  • You can't do that with just Konva.Image and crop. Konva can cut content with cropping. But it can't render it with empty spaces. For that case, we can use external canvas.

    // Create a new Konva stage with the dimensions of the window
    const stage = new Konva.Stage({
      container: 'container',
      width: window.innerWidth,
      height: window.innerHeight
    });
    
    // Create a new layer to hold the image and transformer
    const layer = new Konva.Layer();
    // Add the layer to the stage
    stage.add(layer);
    
    // Define a function to fit and center an image within a canvas element
    function fitContain({canvas, image, canvasWidth, canvasHeight}) {
        // Get the 2D rendering context for the canvas
        const ctx = canvas.getContext('2d');
        
        // Set the dimensions of the canvas
        canvas.width = canvasWidth;
        canvas.height = canvasHeight;
        
        // Calculate the aspect ratios of the image and the canvas
        const imageAspect = image.naturalWidth / image.naturalHeight;
        const canvasAspect = canvasWidth / canvasHeight;
        
        let renderWidth, renderHeight;
        
        // Determine the dimensions to render the image at to maintain aspect ratio
        if (imageAspect > canvasAspect) {
            renderWidth = canvasWidth;
            renderHeight = canvasWidth / imageAspect;
        } else {
            renderWidth = canvasHeight * imageAspect;
            renderHeight = canvasHeight;
        }
        
        // Calculate the position to center the image in the canvas
        const offsetX = (canvasWidth - renderWidth) / 2;
        const offsetY = (canvasHeight - renderHeight) / 2;
        
        // Draw the image on the canvas at the calculated dimensions and position
        ctx.drawImage(image, offsetX, offsetY, renderWidth, renderHeight);
        
        // Return the canvas element
        return canvas;
    }
    
    // Create a new image element
    const image = new window.Image();
    // Set the source of the image
    image.src = 'https://i.imgur.com/ktWThtZ.png';
    // Define an onload handler to execute once the image has loaded
    image.onload = () => {
      // Create a new canvas element
      const canvas = document.createElement('canvas');
      
      // Create a new Konva Image to hold the canvas
      const img = new Konva.Image({
        image: canvas,
        x: 50,
        y: 60,
        width: 200,
        height: 100,
        draggable: true
      });
      // Add the Konva Image to the layer
      layer.add(img);
      // Call fitContain to fit and center the image in the canvas
      fitContain({canvas, image, canvasWidth: img.width(), canvasHeight: img.height() });
      
      // Create a new transformer to allow the Konva Image to be resized
      const tr = new Konva.Transformer({
        flipEnabled: false,
        nodes: [img]
      });
      // Add the transformer to the layer
      layer.add(tr);
      
      // Define a transform event handler to update the canvas when the Konva Image is resized
      img.on('transform', () => {
        // Update the dimensions of the Konva Image to reflect the scaling transformation
        img.setAttrs({
          width: img.width() * img.scaleX(),
          height: img.height() * img.scaleY(),
          scaleX: 1,
          scaleY: 1
        });
        // Call fitContain to fit and center the image in the canvas again
        fitContain({canvas, image, canvasWidth: img.width(), canvasHeight: img.height() });
      })
    }
      <script src="https://unpkg.com/konva@^9/konva.min.js"></script>
        <div id="container"></div>