Нарисуйте стрелку на холсте с помощью React-Konva

Я хотел бы создать холст, на котором пользователь может нарисовать стрелку с помощью мыши.

Я пытаюсь добиться именно этого: https://jsfiddle.net/w33e9fpa/

Но я не понимаю, как преобразовать это в код React, и моя реализация в настоящее время не работает. Когда я запускаю этот код, кажется, что в левом верхнем углу холста рисуется стрелка, но если я щелкну по ней, ничего не произойдет.

Вот мой код:

class DrawArrow extends Component {

  state = {
    isDrawing: false,
    mode: "brush"
  };

  componentDidMount() {
    const canvas = document.createElement("canvas");
    canvas.width = 300;
    canvas.height = 300;
    const context = canvas.getContext("2d");

    this.setState({ canvas, context });
  }


  handleMouseDown = () => {
    this.setState({ isDrawing: true });

    // TODO: improve
    const stage = this.arrow.parent.parent;
    this.lastPointerPosition = stage.getPointerPosition();

    this.setState({
      posX: this.lastPointerPosition.x,
      poxY: this.lastPointerPosition.y
    })

  }

  handleMouseUp = () => {
    this.setState({ isDrawing: false });
  };

  handleMouseMove = () => {
    if (this.state.drawing === true) {
      const stage = this.arrow.parent.parent;
      this.lastPointerPosition = stage.getPointerPosition();
      var pos = stage.getPointerPosition();
      var oldPoints = this.arrow.points();
      this.arrow.points([oldPoints[0], oldPoints[1], pos.x, pos.y])
      this.arrow.getLayer().draw();
    }
  }


  render() {
    return (
      <Arrow
        points= {[this.state.posX,this.state.posY, this.state.posX, this.state.posY]}
        pointerLength= {20}
        pointerWidth=  {20}
        fill= 'black'
        stroke= 'black'
        strokeWidth= {4}
        onMouseDown={this.handleMouseDown}
        onMouseUp={this.handleMouseUp}
        onMouseMove={this.handleMouseMove}
      />
    );
  }
}


class NewWhite extends Component {
  render() {
    return (
      <Stage width={900} height={700}>
        <Layer>
          <DrawArrow />
        </Layer>
      </Stage>
    );
  }
}

Спасибо за помощь!


person Herbot Sikaro    schedule 23.03.2019    source источник
comment
в качестве альтернативы вы можете использовать решение svg, такое как npmjs.com/package/react-simple-arrows   -  person Ulad Kasach    schedule 09.02.2020


Ответы (1)


Ну вот:

import React, { Component } from "react";
import { Stage, Layer, Arrow, Circle, Line } from "react-konva";
import ReactDOM from "react-dom";
import "./styles.css";

class Drawable {
  constructor(startx, starty) {
    this.startx = startx;
    this.starty = starty;
  }
}

class ArrowDrawable extends Drawable {
  constructor(startx, starty) {
    super(startx, starty);
    this.x = startx;
    this.y = starty;
  }
  registerMovement(x, y) {
    this.x = x;
    this.y = y;
  }
  render() {
    const points = [this.startx, this.starty, this.x, this.y];
    return <Arrow points={points} fill="black" stroke="black" />;
  }
}

class CircleDrawable extends ArrowDrawable {
  constructor(startx, starty) {
    super(startx, starty);
    this.x = startx;
    this.y = starty;
  }
  render() {
    const dx = this.startx - this.x;
    const dy = this.starty - this.y;
    const radius = Math.sqrt(dx * dx + dy * dy);
    return (
      <Circle radius={radius} x={this.startx} y={this.starty} stroke="black" />
    );
  }
}

class FreePathDrawable extends Drawable {
  constructor(startx, starty) {
    super(startx, starty);
    this.points = [startx, starty];
  }
  registerMovement(x, y) {
    this.points = [...this.points, x, y];
  }
  render() {
    return <Line points={this.points} fill="black" stroke="black" />;
  }
}

class SceneWithDrawables extends Component {
  constructor(props) {
    super(props);
    this.state = {
      drawables: [],
      newDrawable: [],
      newDrawableType: "FreePathDrawable"
    };
  }

  getNewDrawableBasedOnType = (x, y, type) => {
    const drawableClasses = {
      FreePathDrawable,
      ArrowDrawable,
      CircleDrawable
    };
    return new drawableClasses[type](x, y);
  };

  handleMouseDown = e => {
    const { newDrawable } = this.state;
    if (newDrawable.length === 0) {
      const { x, y } = e.target.getStage().getPointerPosition();
      const newDrawable = this.getNewDrawableBasedOnType(
        x,
        y,
        this.state.newDrawableType
      );
      this.setState({
        newDrawable: [newDrawable]
      });
    }
  };

  handleMouseUp = e => {
    const { newDrawable, drawables } = this.state;
    if (newDrawable.length === 1) {
      const { x, y } = e.target.getStage().getPointerPosition();
      const drawableToAdd = newDrawable[0];
      drawableToAdd.registerMovement(x, y);
      drawables.push(drawableToAdd);
      this.setState({
        newDrawable: [],
        drawables
      });
    }
  };

  handleMouseMove = e => {
    const { newDrawable } = this.state;
    if (newDrawable.length === 1) {
      const { x, y } = e.target.getStage().getPointerPosition();
      const updatedNewDrawable = newDrawable[0];
      updatedNewDrawable.registerMovement(x, y);
      this.setState({
        newDrawable: [updatedNewDrawable]
      });
    }
  };

  render() {
    const drawables = [...this.state.drawables, ...this.state.newDrawable];
    return (
      <div>
        <button
          onClick={e => {
            this.setState({ newDrawableType: "ArrowDrawable" });
          }}
        >
          Draw Arrows
        </button>
        <button
          onClick={e => {
            this.setState({ newDrawableType: "CircleDrawable" });
          }}
        >
          Draw Circles
        </button>
        <button
          onClick={e => {
            this.setState({ newDrawableType: "FreePathDrawable" });
          }}
        >
          Draw FreeHand!
        </button>
        <Stage
          onMouseDown={this.handleMouseDown}
          onMouseUp={this.handleMouseUp}
          onMouseMove={this.handleMouseMove}
          width={900}
          height={700}
        >
          <Layer>
            {drawables.map(drawable => {
              return drawable.render();
            })}
          </Layer>
        </Stage>
      </div>
    );
  }
}

function App() {
  return <SceneWithDrawables />;
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Рабочий пример для игры: https://codesandbox.io/s/w12qznzx5

person Pärt Johanson    schedule 23.03.2019
comment
большое спасибо ! Я не думал о том, чтобы поместить стрелку в массив, а затем просто отобразить ее в рендере. Обязательно поможет. Есть идеи, как добавить несколько кнопок, например «Перо» и «Круг», чтобы пользователь мог сначала выбрать, а затем соответствующим образом рисовать? - person Herbot Sikaro; 23.03.2019
comment
@HerbotSikaro Я добавил круг и возможность рисования от руки (я думаю, это то, что вы пишете пером). Это сделало его немного более волосатым, но удалось сохранить менее 200 строк и довольно просто. Хотя в вашем собственном проекте вам стоит задуматься о том, чтобы разбить эту штуку на отдельные файлы. Если у вас есть еще вопросы, задавайте их! Я отвечу в течение 24 часов - person Pärt Johanson; 24.03.2019
comment
@HerbotSikaro на этой неделе у меня много работы, но я постараюсь взглянуть на нее на выходных, если вы пока не нашли решение. - person Pärt Johanson; 26.03.2019
comment
Конечно, я пытался, но не могу понять, так что я вроде как жду вашей помощи ..: / Все эти последние 4 функции представляют собой проблемы, которые очень трудно понять, и несколько примеров, которые я могу найти онлайн слишком просты, чтобы их можно было применить .. - person Herbot Sikaro; 27.03.2019
comment
(За исключением отмены / повтора, которая кажется менее сложной для структуры данных типа Stack, я работаю над этим сейчас, я не могу сделать все остальное) - person Herbot Sikaro; 27.03.2019
comment
есть идеи? - - person Herbot Sikaro; 31.03.2019
comment
@ pärt-johanson, пример действительно хорош, у вас есть идея, как нарисовать линию или стрелку, но с двумя щелчками мыши? Я имею в виду щелчок 1, чтобы начать рисование, щелчок на двух остановках и завершение линии. - person Pablo Palacios; 30.09.2020