Search code examples
canvasresponsive-designkonvajsdimensionreact-konva

Responsive position of a draggable element in React-Konva


I am using react-konva to try and make a tool where users can visualize and plan their frame wall. The user can choose a background, then chooses a frame size and different posters. It kind of looks like this: Example of tool

So far I have made a background image that scales correctly whenever the window size changes. I have also created an image that represents a poster that scales down/up the same way as the background image. The image that represents a poster is draggable. It can currently only de dragged inside of the background image. However, I want the position of the poster to be the same relative to the background image whenever the window is resized.

This is a demo - https://blue-print.vercel.app/ Select a background and a poster and resize the window. You will see that both the poster and the background shrinks/grows but the position of the poster will not be same relative to the background.

This is the relevant code of the Poster component:

//rest of component

  const handleDragEnd = (e: Konva.KonvaEventObject<DragEvent>) => {
    setElementPos({
      x: e.target.x(),
      y: e.target.y(),
    });
  };

  const handleDrag = (pos: Konva.Vector2d) => {

//this makes the group only be draggable within the background image

    const { width, height, x, y } = props.bg;
    const newX = Math.max(x!, Math.min(pos.x, x! + width - scaledSizes.width!));
    const newY = Math.max(
      y!,
      Math.min(pos.y, y! + height - scaledSizes.height!)
    );
    return {
      x: newX,
      y: newY,
    };
  };

  return (
<Group
      x={elementPos.x}
      y={elementPos.y}
      dragBoundFunc={handleDrag}
      draggable
      onDragEnd={handleDragEnd}
      ref={groupRef}
      onClick={() => handleSelectItem(props.item)}
      onTap={() => handleSelectItem(props.item)}
    > 

// content of the group

Please let me know if you need any additional code / info. Thanks in advance! :)


Solution

  • For any future people reading this post:

    So it turns out that all I had to do was to add a scaleX and scaleY attribute to a group containing all the elements that I wanted to scale. Them set a "virtual height/width" for the elements inside of the group.This would scale all of the elements instead of trying to do it myself with dynamic variables.

    I did this simple calculation to determine the scale variable: Here the canvas.Background is the height and width of my background-image.

    aspectRatio = canvasBackground!.width / canvasBackground!.height;
    scaleX = dimensions.width / canvasBackground!.width;
    scaleY = dimensions.height / canvasBackground!.height;
    scale = aspectRatio < 1 ? Math.max(scaleX, scaleY) : Math.min(scaleX, scaleY);
    

    Which resulted in the following code in the component:

     <Container
          sx={{ height: '100%', position: 'relative' }}
          ref={stageCanvasRef}//ref is used to get the height and width of this container
        >
          <Stage
            height={dimensions.height}//This is the same height as the above container
            width={dimensions.width}//This is the same width as the above container
    
          >
            <Layer>
                <Group scaleX={scale} scaleY={scale} x={x} y={y}>
                  <Image
                    height={canvasBackground.height}
                    width={canvasBackground.width}
                    alt="background"
                    image={canvasBackground}
                  /> 
    /**Rest of the Groups content*/