1

I'm developing a map (green rectangle ) and a path drawn over it ( black lines ) in Konva:

enter image description here

The map has a fixed dimensions while the path is get from an API call.

I want to be able to zoom the map either with the mousescroll or by clicking the buttons + o - that I created.

I follow this example how to react-konva zooming on scroll in order to implement the zoom, but when I scroll, also the buttons change their positions, see image below.

enter image description here

I would like to keep the buttons fixed at the bottom side of the map.

This is a piece of my code, I defined some custom components as Map, Ground, WholePath, GroundButtons and Button:

export const Map = ( props ) => {
    const [mouseScale, setMouseScale] = useState(1);
    const [x0, setX0] = useState(0);
    const [y0, setY0] = useState(0);

    const scaleBy = 1.02;
    const handleWheel = (e)  => {
      const group = e.target.getStage();
      e.evt.preventDefault();
      const mousePointTo = {
        x: group.getPointerPosition().x / mouseScale - group.x() / mouseScale,
        y: group.getPointerPosition().y / mouseScale - group.y() / mouseScale
      };
      const newScale = e.evt.deltaY < 0 ? mouseScale * scaleBy : mouseScale / scaleBy;
      setMouseScale(newScale);
      setX0(-(mousePointTo.x - group.getPointerPosition().x / newScale) * newScale );
      setY0(-(mousePointTo.y - group.getPointerPosition().y / newScale) * newScale );
    };

    const SimulateMouseWheel = (e, BtnType, mScale = scaleBy) => {
      const newScale = BtnType > 0 ? mouseScale * mScale : mouseScale / mScale;
      setMouseScale(newScale);
    };

    const onClickPlus = (e) => {
        SimulateMouseWheel(e, +1, 1.08);
    };

    const onClickMinus = (e) => {
        SimulateMouseWheel(e, -1, 1.08);
    };

    return (
        <Stage
            width={800}
            height={400}
            draggable={true}
            onWheel={handleWheel}
            scaleX={mouseScale}
            scaleY={mouseScale}
            x={x0}
            y={y0}
        >
            <Layer>
                <Ground pitch={props.pitch}/>
                <WholePath path={props.path}/>
            </Layer>
            <Layer>
                <GroundButtons reference={props.reference} onClickMinus={onClickMinus} onClickPlus={onClickPlus} />
            </Layer>
        </Stage>
    )
}


const GroundButtons = ( props ) => {
    return (
        <Group>
            <Button xText={10} yText={5} text={"+"} x={790} y={360}  side={30} onClick={props.onClickPlus}/>
            <Button xText={12} yText={5} text={"-"} x={790} y={360}  side={30} onClick={props.onClickMinus}/>
        </Group>
    )
}

const Button = props => {
    return (
            <Group x={props.x} y={props.y} onClick={props.onClick}>
                <Rect
                    width={props.side}
                    height={props.side}
                    fill='#f2f1f0'
                    stroke='#777'
                    strokeWidth={1}
                />
                <Text
                  x={props.xText}
                  y={props.yText}
                  fontSize={20}
                  text={props.text}
                  stroke='#777'
                  align="center"
                  strokeWidth={1}
                />
            </Group>
    )
}

This is the solution I implemented: Thanks to Ondolin's answer.

export const Map = ( props ) => {
    const [mouseScale, setMouseScale] = useState(1);
    const [x0, setX0] = useState(0);
    const [y0, setY0] = useState(0);

    const scaleBy = 1.02;
    const handleWheel = (e)  => {
      const group = e.target.getStage();
      e.evt.preventDefault();
      const mousePointTo = {
        x: group.getPointerPosition().x / mouseScale - group.x() / mouseScale,
        y: group.getPointerPosition().y / mouseScale - group.y() / mouseScale
      };
      const newScale = e.evt.deltaY < 0 ? mouseScale * scaleBy : mouseScale / scaleBy;
      setMouseScale(newScale);
      setX0(-(mousePointTo.x - group.getPointerPosition().x / newScale) * newScale );
      setY0(-(mousePointTo.y - group.getPointerPosition().y / newScale) * newScale );
    };

    const SimulateMouseWheel = (e, BtnType, mScale = scaleBy) => {
      const newScale = BtnType > 0 ? mouseScale * mScale : mouseScale / mScale;
      setMouseScale(newScale);
    };

    const onClickPlus = (e) => {
        SimulateMouseWheel(e, +1, 1.08);
    };

    const onClickMinus = (e) => {
        SimulateMouseWheel(e, -1, 1.08);
    };

    return (
        <div>
        <Stage
            width={800}
            height={400}
            draggable={true}
            onWheel={handleWheel}
            scaleX={mouseScale}
            scaleY={mouseScale}
            x={x0}
            y={y0}
        >
            <Layer>
                <Ground pitch={props.pitch}/>
                <WholePath path={props.path}/>
            </Layer>
            <Layer>
                <GroundButtons reference={props.reference} onClickMinus={onClickMinus} onClickPlus={onClickPlus} />
            </Layer>
        </Stage>
            <button className="ground_controls" onClick={onClickPlus}><i className="fa fa-plus" aria-hidden="true"></i></button>
            <button className="ground_controls" onClick={onClickMinus}><i className="fa fa-minus" aria-hidden="true"></i></button>
        </div>
    )
}
Andrea Barnabò
  • 534
  • 6
  • 19

1 Answers1

1

I would strongly recommend you not to use Buttons drawn on the canvas (stage). You should use standard HTML buttons. You can position them on top of the stage with css (Positon absolute).

If you do it this way, there are many benefits to you. E.g. you can design it easier and better. In addition, it is more efficient and also solves the problem from this question.

Ondolin
  • 324
  • 2
  • 13