2

I am currently building some sort of meme-editor in react and for that I am using konvajs to function similar to a Canvas. My problem is that I am unable to center the items inside the canva stage, as there seems to be some property that just overrides my styling. This is (part of) the return statement in my react-component:

<div className="mycanvas">
    <Stage width={500} height={500} className="stage">
        <Layer>
            <Image image={image} className="meme" />
            {textFields.map((text) => (
                <Text
                    text={text}
                    draggable
                    fontFamily="Impact"
                    fontSize="30"
                    stroke="white"
                />
            ))}
        </Layer>
    </Stage>
</div>

And this is how the output gets rendered.

current rendering output

I have coloured the background of the wrapper blue, to show in which box the image should be centered.

I have already tried CSS on the classes "mycanvas", "stage" and "meme" and also on "konvajs-content" (as that showed up in my inspector for some reason). I have used align-items: center, margin: auto and a couple others, but I think normal CSS does not really apply here. I think it is an issue regarding the generaly styling of konvajs components, but unfortunately I could not find any solution on stackoverflow or the konva documentation.

1 Answers1

3

This is an instance where CSS can't help. When the image is applied to the canvas using its height and width at the x and y coordinates you supply, the pixels of the image become part of the rasterized canvas. In other words, the image doesn't exist independent of the canvas.

Therefore, if you want to center the image inside of your canvas, you need to do a little math to calculate the x and y coordinates that will place the image centered inside the canvas.

Demo

For example, if your canvas size is 500px tall and your image has a height of 350px, then you need to set the y position to 75px (i.e., (500 - 350) / 2).

The demo code below shows how to replicate the behavior of CSS object-fit: contain. This will adjust the image to fill the canvas in one direction, and then center the image in the other direction.

import { useState, useEffect } from "react";
import { Stage, Layer, Image, Text } from "react-konva";

function Example() {
  const w = window.innerWidth;
  const h = window.innerHeight;
  const src = "https://konvajs.org/assets/yoda.jpg";

  const [image, setImage] = useState(null);
  const [pos, setPos] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const image = new window.Image();
    image.src = src;
    image.addEventListener("load", handleLoad);

    function handleLoad(event) {
      const image = event.currentTarget;
      /* after the image is loaded, you can get it's dimensions */
      const imgNaturalWidth = image.width;
      const imgNaturalHeight = image.height;

      /* 
        calculate the horizontal and vertical ratio of the 
        image dimensions versus the canvas dimensions
      */
      const hRatio = w / imgNaturalWidth;
      const vRatio = h / imgNaturalHeight;

      /*
        to replicate the CSS Object-Fit "contain" behavior,
        choose the smaller of the horizontal and vertical 
        ratios

        if you want a "cover" behavior, use Math.max to 
        choose the larger of the two ratios instead
      */
      const ratio = Math.min(hRatio, vRatio);
      /* 
        scale the image to fit the canvas 
      */
      image.width = imgNaturalWidth * ratio;
      image.height = imgNaturalHeight * ratio;

      /* 
        calculate the offsets so the image is centered inside
        the canvas
      */
      const xOffset = (w - image.width) / 2;
      const yOffset = (h - image.height) / 2;

      setPos({
        x: xOffset,
        y: yOffset
      });
      setImage(image);
    }

    return () => {
      image.removeEventListener("load", handleLoad);
    };
  }, [src, h, w]);

  return (
    <Stage width={w} height={h} style={{ background: "black" }}>
      <Layer>
        <Image x={pos.x} y={pos.y} image={image} />
        <Text
          text="I am centered"
          fontFamily="Impact"
          fontSize={50}
          stroke="white"
          strokeWidth={1}
          x={pos.x}
          y={pos.y}
        />
      </Layer>
    </Stage>
  );
}

jme11
  • 17,134
  • 2
  • 38
  • 48
  • Thank you so much! I will look into that later today. I had already started trying to adjust the position of the image with the x and y attributes, but had issues because the point x and y refer to are the upper left corner of the picture and I did not find a way to get the height and width of the pic, so I could not calculate an offset. – Philipp Thalhammer Dec 30 '22 at 12:07
  • I could pinpoint the problem now: I receive my images as a base64 string and do not know their dimensions. I am now trying to send an array that also contains width and height. – Philipp Thalhammer Dec 31 '22 at 15:14
  • I think you can use the [`use-image`](https://github.com/konvajs/use-image) hook from react-konva to get the image dimensions. – Anjan Biswas Feb 18 '23 at 20:52
  • 1
    PS: This is probably the best solution i've seen about centering and maintain Aspect ratio with react-konva out there – Anjan Biswas Feb 18 '23 at 21:49