Search code examples
html5-canvaskonvajsreact-konva

Best strategy to mimic <img> object-fit / object-position property with konva images


Assume we have a 1000x1000 image drawn onto canvas, and set konva image size to something like 500x700.

Is it possible to mimic html <img /> tags object-fit property in konva so we can tell image object to fit image in cover / contain mode. Similarly it would be useful to mimic object-position.

Thus far the only way I can think of doing this is to clip image with a rectangle with some custom logic added to handle use cases above. I was wondering if there is a better way?


Solution

  • You can emulate similar CSS behavior with cropX, cropY, cropWidth and cropHeight properties of Konva.Image.

    You can use this function for calculations:

    function getCrop(image, size, clipPosition = 'center-middle') {
      const width = size.width;
      const height = size.height;
      const aspectRatio = width / height;
    
      let newWidth;
      let newHeight;
    
      const imageRatio = image.width / image.height;
    
      if (aspectRatio >= imageRatio) {
        newWidth = image.width;
        newHeight = image.width / aspectRatio;
      } else {
        newWidth = image.height * aspectRatio;
        newHeight = image.height;
      }
    
      let x = 0;
      let y = 0;
      if (clipPosition === 'left-top') {
        x = 0;
        y = 0;
      } else if (clipPosition === 'left-middle') {
        x = 0;
        y = (image.height - newHeight) / 2;
      } else if (clipPosition === 'left-bottom') {
        x = 0;
        y = (image.height - newHeight);
      } else if (clipPosition === 'center-top') {
        x = (image.width - newWidth) / 2;
        y = 0;
      } else if (clipPosition === 'center-middle') {
        x = (image.width - newWidth) / 2;
        y = (image.height - newHeight) / 2;
      } else if (clipPosition === 'center-bottom') {
        x = (image.width - newWidth) / 2;
        y = (image.height - newHeight);
      } else if (clipPosition === 'right-top') {
        x = (image.width - newWidth);
        y = 0;
      } else if (clipPosition === 'right-middle') {
        x = (image.width - newWidth);
        y = (image.height - newHeight) / 2;
      } else if (clipPosition === 'right-bottom') {
        x = (image.width - newWidth);
        y = (image.height - newHeight);
      } else if (clipPosition === 'scale') {
        x = 0;
        y = 0;
        newWidth = width;
        newHeight = height;
      } else {
        console.error(
          new Error('Unknown clip position property - ' + clipPosition)
        );
      }
    
      
      return {
        cropX: x,
        cropY: y,
        cropWidth: newWidth,
        cropHeight: newHeight
      }
    }
    
    // usage:
    const crop = getCrop(img.image(), { width: img.width(), height: img.height()}, pos);
    img.setAttrs(crop);
    img.getLayer().batchDraw();
    

    Demo: https://konvajs.org/docs/sandbox/Scale_Image_To_Fit.html