Search code examples
konvajsreact-konvakonvakonvajs-reactjs

How to set the initial dimensions of a transformer when applying it on a group having clip properties in Konva?


I have a Konva.Group with a few nodes to which I have set clip properties to limit what is seen. I'm applying Konva.Transformer to the group and the problem I'm facing is that the Transformer encloses the entire group, even the unclipped portion. This is not looking good even if it is fully functional and does the job. Is there any way to set the initial width and height of the transformer so that it only encloses the clipped portion?

This is the group before applying clip and transform

enter image description here

This is what it looks like after applying clip and transform

enter image description here

import React, {useRef, useEffect} from 'react';
import { render } from 'react-dom';
import { Stage, Layer, Rect, Circle, Line, Group, Transformer } from 'react-konva';

const App = () => {
  const trRef = useRef(null)
  const grpRef = useRef(null)


  useEffect(()=>{
    const transformNode = trRef.current;
    transformNode.enabledAnchors(["top-left",
    "top-right",
    "bottom-left",
    "bottom-right"])
    transformNode.nodes([grpRef.current])
  },[trRef])

  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        <Group ref={grpRef} clipX={0} clipY={0} clipWidth={200} clipHeight={200}>
        <Rect
          x={20}
          y={50}
          width={100}
          height={100}
          fill="red"
          shadowBlur={10}
        />
        <Circle x={200} y={100} radius={50} fill="green" />
        <Line
          x={20}
          y={200}
          points={[0, 0, 100, 0, 100, 100]}
          tension={0.5}
          closed
          stroke="black"
          fillLinearGradientStartPoint={{ x: -50, y: -50 }}
          fillLinearGradientEndPoint={{ x: 50, y: 50 }}
          fillLinearGradientColorStops={[0, 'red', 1, 'yellow']}
        />
        </Group>
        <Transformer rotateEnabled={false} ref={trRef} />
      </Layer>
    </Stage>
  );
};

render(<App />, document.getElementById('root'));

Solution

  • This is the default behavior of Konva.Transform and it can't be altered. One workaround would be to create a transparent rectangle around the clipped portion, apply the transform to it and then copy the changes to the group.

    import React, {useRef, useEffect} from 'react';
    import { render } from 'react-dom';
    import { Stage, Layer, Rect, Circle, Line, Group, Transformer } from 'react-konva';
    
    const App = () => {
      const trRef = useRef(null)
      const grpRef = useRef(null)
      const rectRef = useRef(null)
    
      // To copy the transform matrix from the rectangle to the group
      function handleTransform(e){
        const shape1 = e.target;
        const transform = shape1.getTransform().copy();
        const attrs = transform.decompose();
        grpRef.current.setAttrs(attrs);
      }
    
      useEffect(()=>{
        const transformNode = trRef.current;
        transformNode.enabledAnchors(["top-left",
        "top-right",
        "bottom-left",
        "bottom-right"])
        transformNode.nodes([rectRef.current])
      },[trRef])
    
      return (
        <Stage width={window.innerWidth} height={window.innerHeight}>
          <Layer>
            <Group draggable>
              {/* Transparent rectangle to which the transform is now applied to */}
              <Rect
                ref={rectRef}
                x={0}
                y={0}
                width={200}
                height={200}
                id="invisible-rect"
              />
            <Group ref={grpRef} clipX={0} clipY={0} clipWidth={200} clipHeight={200}>
            <Rect
              x={20}
              y={50}
              width={100}
              height={100}
              fill="red"
              shadowBlur={10}
            />
            <Circle x={200} y={100} radius={50} fill="green" />
            <Line
              x={20}
              y={200}
              points={[0, 0, 100, 0, 100, 100]}
              tension={0.5}
              closed
              stroke="black"
              fillLinearGradientStartPoint={{ x: -50, y: -50 }}
              fillLinearGradientEndPoint={{ x: 50, y: 50 }}
              fillLinearGradientColorStops={[0, 'red', 1, 'yellow']}
            />
            </Group>
            </Group>
            <Transformer onTransform={handleTransform} rotateEnabled={false} ref={trRef} />
          </Layer>
        </Stage>
      );
    };
    
    render(<App />, document.getElementById('root'));
    

    Here's the demo of the above code. Thanks to Anton, the creator of this wonderful library for suggesting this solution.

    Reference - Konva shape transform sharing is simple