#javascript #reactjs #canvas #konvajs
#javascript #reactjs #холст #конвайс
Вопрос:
Я хотел бы создать холст, на котором пользователь может нарисовать стрелку с помощью мыши.
Чего я пытаюсь достичь, так это именно этого: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>
);
}
}
Спасибо за вашу помощь!
Комментарии:
1. в качестве альтернативы вы можете использовать svg-решение, такое как npmjs.com/package/react-simple-arrows
Ответ №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
Комментарии:
1. большое вам спасибо! Я не думал о том, чтобы поместить стрелку в массив, а затем просто отобразить ее при рендеринге. Определенно поможет. Есть идеи о том, как добавить несколько кнопок, например «Перо» и «Круг», чтобы пользователь мог сначала выбрать, а затем нарисовать соответственно?
2. @HerbotSikaro Я добавил круг и опцию рисования от руки (я думаю, это то, что вы улучшаете с помощью пера). Это сделало его немного более сложным, но ему удалось сохранить его размером менее 200 строк и довольно простым. Хотя в вашем собственном проекте вам следует начать думать о разбиении этой вещи на отдельные файлы. Если у вас есть еще какие-либо вопросы, задавайте сразу! Я отвечу в течение 24 часов
3. @HerbotSikaro на этой неделе у меня много работы, но я постараюсь взглянуть на нее в выходные, если вы за это время не нашли решение.
4. Конечно, я пытался, но я не могу понять это, поэтому я вроде как жду вашей помощи .. :/ Все эти последние 4 функции представляют проблемы, которые очень трудно понять, а несколько примеров, которые я могу найти в Интернете, слишком просты, чтобы быть применимыми..
5. (За исключением отмены / повтора, который кажется менее сложным при структуре данных стекового типа, я сейчас работаю над этим, я не могу сделать остальное)