Search code examples
htmlcanvashtml5-canvaskonvajsreact-konva

Clip an Image in Konva that uses Canvas path?


I saw a similar question here but I have an array of radius instead of a number so it couldn't use + operator.

The radius has 4 values: [top, right, bottom, left]

<Stage width={width} height={height}>
  <Layer>
    <Rect
      width={width / 2}
      height={height / 2}
      x={20}
      y={20}
      fill=""
      cornerRadius={10}
      shadowEnabled={true}
      shadowColor="#bada41"
      shadowBlur={50}
      shadowOffset={{ x: 10, y: 10 }}
      shadowOpacity={1}
      shadow={10}
    />
    <Rect
      width={width / 2}
      height={height / 2}
      x={20}
      y={20}
      cornerRadius={cornerRadius}
      fill="palevioletred"
    />
    <Group
      clipFunc={(ctx: any) => {
        ctx.beginPath()
        ctx.moveTo(x + cornerRadius[0], y)
        ctx.lineTo(x + width - cornerRadius[0], y)
        ctx.quadraticCurveTo(x + width, y, x + width, y + cornerRadius[0])
        ctx.lineTo(x + width, y + height - cornerRadius[1])
        ctx.quadraticCurveTo(
          x + width,
          y + height,
          x + width - cornerRadius[1],
          y + height
        )
        ctx.lineTo(x + cornerRadius[1], y + height)
        ctx.quadraticCurveTo(x, y + height, x, y + height - cornerRadius[2])
        ctx.lineTo(x, y + cornerRadius[2])
        ctx.quadraticCurveTo(x, y, x + cornerRadius[3], y)
        ctx.closePath()
      }}
    >
      <Image
        image={img}
        width={width / 4}
        height={height / 4}
        x={40}
        y={40}
        fill="blue"
      />
    </Group>
  </Layer>
</Stage>

clip in konva

Codesandbox 👉 https://codesandbox.io/s/clip-rounded-image-in-react-konva-09d2l?file=/src/App.tsx

How can I make the inner image the same shape as the outer one?


Solution

  • export default function App() {
      const [img] = useImage(
        "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAABlBMVEUAAADY2NjnFMi2AAAAAXRSTlMAQObYZgAAABVJREFUGNNjYIQDBgQY0oLDxBsIQQCltADJNa/7sQAAAABJRU5ErkJggg=="
      );
    
      const width = window.innerWidth;
      const height = window.innerHeight;
      const x = 20;
      const y = 20;
      const cornerRadius = [0, 0, 20, 20];
    
      const imageWidth = width / 4;
      const imageHeight = height / 4;
    
      return (
        <div className="App">
          <h1
            style={{
              paddingLeft: 20
            }}
          >
            Clip in Konva
          </h1>
          <Stage width={width} height={height}>
            <Layer>
              <Rect
                width={width / 2}
                height={height / 2}
                x={20}
                y={20}
                fill=""
                cornerRadius={10}
                shadowEnabled={true}
                shadowColor="#bada41"
                shadowBlur={50}
                shadowOffset={{ x: 10, y: 10 }}
                shadowOpacity={1}
                shadow={10}
              />
              <Rect
                width={width / 2}
                height={height / 2}
                x={20}
                y={20}
                cornerRadius={cornerRadius}
                fill="palevioletred"
              />
              <Group
                x={40}
                y={40}
                clipFunc={(ctx: any) => {
                  ctx.beginPath();
                  let topLeft = 0;
                  let topRight = 0;
                  let bottomLeft = 0;
                  let bottomRight = 0;
                  if (typeof cornerRadius === "number") {
                    topLeft = topRight = bottomLeft = bottomRight = Math.min(
                      cornerRadius,
                      width / 2,
                      height / 2
                    );
                  } else {
                    topLeft = Math.min(
                      cornerRadius[0] || 0,
                      imageWidth / 2,
                      imageHeight / 2
                    );
                    topRight = Math.min(
                      cornerRadius[1] || 0,
                      imageWidth / 2,
                      imageHeight / 2
                    );
                    bottomRight = Math.min(
                      cornerRadius[2] || 0,
                      imageWidth / 2,
                      imageHeight / 2
                    );
                    bottomLeft = Math.min(
                      cornerRadius[3] || 0,
                      imageWidth / 2,
                      imageHeight / 2
                    );
                  }
                  ctx.moveTo(topLeft, 0);
                  ctx.lineTo(imageWidth - topRight, 0);
                  ctx.arc(
                    imageWidth - topRight,
                    topRight,
                    topRight,
                    (Math.PI * 3) / 2,
                    0,
                    false
                  );
                  ctx.lineTo(imageWidth, imageHeight - bottomRight);
                  ctx.arc(
                    imageWidth - bottomRight,
                    imageHeight - bottomRight,
                    bottomRight,
                    0,
                    Math.PI / 2,
                    false
                  );
                  ctx.lineTo(bottomLeft, imageHeight);
                  ctx.arc(
                    bottomLeft,
                    imageHeight - bottomLeft,
                    bottomLeft,
                    Math.PI / 2,
                    Math.PI,
                    false
                  );
                  ctx.lineTo(0, topLeft);
                  ctx.arc(
                    topLeft,
                    topLeft,
                    topLeft,
                    Math.PI,
                    (Math.PI * 3) / 2,
                    false
                  );
                  ctx.closePath();
                }}
              >
                <Image
                  image={img}
                  width={imageWidth}
                  height={imageHeight}
                  fill="blue"
                />
              </Group>
            </Layer>
          </Stage>
        </div>
      );
    }
    

    Demo: https://codesandbox.io/s/react-konva-image-clip-demo-x4i2d?file=/src/App.tsx:99-3886

    Gotcha: The x & y attribute on Image should be removed & placed on Group component.