I am working on a react application at the minute that has some functionality that allows the user to drag and image around the canvas and reposition it, I have move functionality working but it's not particularly nice, it just seems to redraw a new image over the top of the old giving an image within an image within an image feedback. Secondly the it seems to drag to the top left corner of the of image not the click point I am not sure what I am doing wrong.
Here is an example, https://j3mzp8yxwy.codesandbox.io/, and my react code below,
import React, { Component } from 'react';
import { connect } from 'react-redux';
import styled from "styled-components";
import { toggleCommentModal, setCommentPos } from '../../actions/index';
import CommentHandle from "../../CommentHandle";
class ConnectedCanvasStage extends Component {
constructor(props) {
super(props);
this.handleMouseDown = this.handleMouseDown.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.handleMouseUp = this.handleMouseUp.bind(this);
this.state = {
dragging : false,
dragoffx : 0,
dragoffy : 0
}
}
render() {
const CanvasStage = styled.div`
width: 100%;
height: ${document.body.clientHeight}px;
position:relative;
`;
return(
<CanvasStage>
<canvas id="canvas"
ref="canvas"
width={document.body.clientWidth - (document.body.clientWidth * 0.10)}
height={document.body.clientHeight}
onMouseDown={this.handleMouseDown}
onMouseMove={this.handleMouseMove}
onMouseUp={this.handleMouseUp}
/>
</CanvasStage>
);
}
componentDidMount() {
this.updateCanvas();
}
updateCanvas(x=0, y=0) {
this.ctx = this.refs.canvas.getContext("2d");
let base_image = new Image();
base_image.src = this.props.image;
base_image.onload = () => {
this.ctx.drawImage(base_image, x, y, (document.body.clientWidth - (document.body.clientWidth * 0.10)) * 2, document.body.clientHeight * 2);
}
}
handleMouseDown(event) {
switch(this.props.activeTool) {
case "move":
let mx = event.clientX;
let my = event.clientY;
let pos = this.getRelativeClickPosition(document.getElementById('canvas'), event);
this.setState({
dragging: !this.state.dragging,
dragoffx : parseInt(mx - pos.x),
dragoffy : parseInt(my - pos.y)
});
break;
case "comment":
let comment_position = this.getRelativeClickPosition(document.getElementById('canvas'), event);
this.addComment(comment_position.x, comment_position.y);
break;
}
}
handleMouseMove(event) {
if(this.props.activeTool == 'move' && this.state.dragging) {
var dx = event.clientX - this.state.dragoffx;
var dy = event.clientY - this.state.dragoffy;
this.updateCanvas(dx, dy);
}
}
handleMouseUp() {
this.setState({
dragging :false
});
}
shouldComponentUpdate() {
return false;
}
addComment(x, y) {
x = x - 24;
y = y - 20;
this.props.toggleCommentModal(true);
this.props.setCommentPos({x, y});
}
getRelativeClickPosition(canvas, event) {
var rect = canvas.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
}
}
const mapDispatchToProps = dispatch => {
return {
toggleCommentModal : visible => dispatch(toggleCommentModal(visible)),
setCommentPos : position => dispatch(setCommentPos(position))
};
};
const mapStateToProps = state => {
return {
activeTool : state.activeTool,
comments : state.comments
}
};
const CanvasStage = connect(mapStateToProps, mapDispatchToProps)(ConnectedCanvasStage);
export default CanvasStage;
How would I go about smoothing this drag out so it doesn't just draw a new image on top and it also drags from the click point and not the top left of the image?
Related
so I was playing around with the fabricjs canvas library and I found this fiddle written in vanillajs which lets you draw polygons on the canvas. I wanted to implement this exact thing in my react project so I tried to convert the entire code into react (https://codesandbox.io/s/jolly-kowalevski-tjt58). The code works somewhat but there are some new bugs which are not in the original fiddle and I'm having trouble fixing them.
for eg: try to create a polygon by clicking the draw button, when you do this first time, the polygon is drawn without any bugs, but when you click the draw button again for the second time, the canvas starts acting weird and a weird polygon is created.
So basically I need help in converting the vanilla code to react with 0 bugs.
extra information:
fabric version used in the fiddle: 4.0.0
fabric version in sandbox: 4.0.0
Vanilla Js Code:
const getPathBtn = document.getElementById("get-path");
const drawPolygonBtn = document.getElementById("draw-polygon");
const showPolygonBtn = document.getElementById("show-polygon");
const editPolygonBtn = document.getElementById("edit-polygon");
const canvas = new fabric.Canvas("canvas", {
selection: false
});
let line, isDown;
let prevCords;
let vertices = [];
let polygon;
const resetCanvas = () => {
canvas.off();
canvas.clear();
};
const resetVariables = () => {
line = undefined;
isDown = undefined;
prevCords = undefined;
polygon = undefined;
vertices = [];
};
const addVertice = (newPoint) => {
if (vertices.length > 0) {
const lastPoint = vertices[vertices.length - 1];
if (lastPoint.x !== newPoint.x && lastPoint.y !== newPoint.y) {
vertices.push(newPoint);
}
} else {
vertices.push(newPoint);
}
};
const drawPolygon = () => {
resetVariables();
resetCanvas();
canvas.on("mouse:down", function(o) {
isDown = true;
const pointer = canvas.getPointer(o.e);
let points = [pointer.x, pointer.y, pointer.x, pointer.y];
if (prevCords && prevCords.x2 && prevCords.y2) {
const prevX = prevCords.x2;
const prevY = prevCords.y2;
points = [prevX, prevY, prevX, prevY];
}
const newPoint = {
x: points[0],
y: points[1]
};
addVertice(newPoint);
line = new fabric.Line(points, {
strokeWidth: 2,
fill: "black",
stroke: "black",
originX: "center",
originY: "center",
});
canvas.add(line);
});
canvas.on("mouse:move", function(o) {
if (!isDown) return;
const pointer = canvas.getPointer(o.e);
const coords = {
x2: pointer.x,
y2: pointer.y
};
line.set(coords);
prevCords = coords;
canvas.renderAll();
});
canvas.on("mouse:up", function(o) {
isDown = false;
const pointer = canvas.getPointer(o.e);
const newPoint = {
x: pointer.x,
y: pointer.y
};
addVertice(newPoint);
});
canvas.on("object:moving", function(option) {
const object = option.target;
canvas.forEachObject(function(obj) {
if (obj.name == "Polygon") {
if (obj.PolygonNumber == object.polygonNo) {
const points = window["polygon" + object.polygonNo].get(
"points"
);
points[object.circleNo - 1].x = object.left;
points[object.circleNo - 1].y = object.top;
window["polygon" + object.polygonNo].set({
points: points,
});
}
}
});
canvas.renderAll();
});
};
const showPolygon = () => {
resetCanvas();
if (!polygon) {
polygon = new fabric.Polygon(vertices, {
fill: "transparent",
strokeWidth: 2,
stroke: "black",
objectCaching: false,
transparentCorners: false,
cornerColor: "blue",
});
}
polygon.edit = false;
polygon.hasBorders = true;
polygon.cornerColor = "blue";
polygon.cornerStyle = "rect";
polygon.controls = fabric.Object.prototype.controls;
canvas.add(polygon);
};
// polygon stuff
// define a function that can locate the controls.
// this function will be used both for drawing and for interaction.
function polygonPositionHandler(dim, finalMatrix, fabricObject) {
let x = fabricObject.points[this.pointIndex].x - fabricObject.pathOffset.x,
y = fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y;
return fabric.util.transformPoint({
x: x,
y: y
},
fabric.util.multiplyTransformMatrices(
fabricObject.canvas.viewportTransform,
fabricObject.calcTransformMatrix()
)
);
}
// define a function that will define what the control does
// this function will be called on every mouse move after a control has been
// clicked and is being dragged.
// The function receive as argument the mouse event, the current trasnform object
// and the current position in canvas coordinate
// transform.target is a reference to the current object being transformed,
function actionHandler(eventData, transform, x, y) {
let polygon = transform.target,
currentControl = polygon.controls[polygon.__corner],
mouseLocalPosition = polygon.toLocalPoint(
new fabric.Point(x, y),
"center",
"center"
),
polygonBaseSize = polygon._getNonTransformedDimensions(),
size = polygon._getTransformedDimensions(0, 0),
finalPointPosition = {
x: (mouseLocalPosition.x * polygonBaseSize.x) / size.x +
polygon.pathOffset.x,
y: (mouseLocalPosition.y * polygonBaseSize.y) / size.y +
polygon.pathOffset.y,
};
polygon.points[currentControl.pointIndex] = finalPointPosition;
return true;
}
// define a function that can keep the polygon in the same position when we change its
// width/height/top/left.
function anchorWrapper(anchorIndex, fn) {
return function(eventData, transform, x, y) {
let fabricObject = transform.target,
absolutePoint = fabric.util.transformPoint({
x: fabricObject.points[anchorIndex].x -
fabricObject.pathOffset.x,
y: fabricObject.points[anchorIndex].y -
fabricObject.pathOffset.y,
},
fabricObject.calcTransformMatrix()
),
actionPerformed = fn(eventData, transform, x, y),
newDim = fabricObject._setPositionDimensions({}),
polygonBaseSize = fabricObject._getNonTransformedDimensions(),
newX =
(fabricObject.points[anchorIndex].x -
fabricObject.pathOffset.x) /
polygonBaseSize.x,
newY =
(fabricObject.points[anchorIndex].y -
fabricObject.pathOffset.y) /
polygonBaseSize.y;
fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5);
return actionPerformed;
};
}
function editPolygon() {
canvas.setActiveObject(polygon);
polygon.edit = true;
polygon.hasBorders = false;
let lastControl = polygon.points.length - 1;
polygon.cornerStyle = "circle";
polygon.cornerColor = "rgba(0,0,255,0.5)";
polygon.controls = polygon.points.reduce(function(acc, point, index) {
acc["p" + index] = new fabric.Control({
positionHandler: polygonPositionHandler,
actionHandler: anchorWrapper(
index > 0 ? index - 1 : lastControl,
actionHandler
),
actionName: "modifyPolygon",
pointIndex: index,
});
return acc;
}, {});
canvas.requestRenderAll();
}
// Button events
drawPolygonBtn.onclick = () => {
drawPolygon();
};
showPolygonBtn.onclick = () => {
showPolygon();
};
editPolygonBtn.onclick = () => {
editPolygon();
};
getPathBtn.onclick = () => {
console.log("vertices", polygon.points);
};
On 2nd draw (click the draw button again for the second time), the line is always connected to same point. So there is a problem with prevCords.
By adding a console.log to handler function of "mouse:mouse" confirmed above statement:
fabricCanvas.on("mouse:move", function (o) {
console.log("mousemove fired", prevCords); // always the same value
if (isDown.current || !line.current) return;
const pointer = fabricCanvas.getPointer(o.e);
const coords = {
x2: pointer.x,
y2: pointer.y
};
line.current.set(coords);
setPrevCords(coords); // the line should connect to this new point
fabricCanvas.renderAll();
});
It's because of closure, the function handler of mouse:move will always remember the value of prevCords when it was created (i.e when you click on Draw button) not the value that was updated by setPrevCords
To solve above problem, simply use useRef to store prevCords (or use reference)
Line 6:
const [fabricCanvas, setFabricCanvas] = useState();
const prevCordsRef = useRef();
const line = useRef();
Line 35:
const resetVariables = () => {
line.current = undefined;
isDown.current = undefined;
prevCordsRef.current = undefined;
polygon.current = undefined;
vertices.current = [];
};
Line 65:
if (prevCordsRef.current && prevCordsRef.current.x2 && prevCordsRef.current.y2) {
const prevX = prevCordsRef.current.x2;
const prevY = prevCordsRef.current.y2;
points = [prevX, prevY, prevX, prevY];
}
Line 96:
prevCordsRef.current = coords;
One last suggestion is to change Line 89 (so the feature match the demo):
if (!isDown.current) return;
On summary:
Don't use useState for variable that must have latest value in another function handler. Use useRef instead
Use useState for prevCords is a wasted since React will re-render on every setState
Hi I am clicking on particular point on image and displaying a icon on selected area. When i change resolution that point is moving to other part of image. below is my code.How to fix the coords on resolution or browser width change. Added eventlistner and need to know on window resize any calculation have to be done?
My component is:
import React, { Component } from "react";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import { showCardDetails } from "../../store/Action";
let temp = [];
class cardDetails extends Component {
constructor(props) {
super(props);
}
state = {
left: "",
right: "",
coords: []
};
componentDidMount() {
const { dispatch } = this.props;
console.log(this.props.backOffice + "props");
console.log(this.props.match.params.userId);
let coords = JSON.parse(localStorage.getItem("coords"));
this.setState({ coords: coords });
window.addEventListener("resize", this.handlePress);
}
handlePress = () => {
const { coords } = this.state;
console.log(this.state);
//anycalculation here?
};
handleclick = e => {
var canvas = document.getElementById("imgCoord");
var w = document.documentElement.clientWidth;
var h = document.documentElement.clientHeight;
console.log(e.offsetLeft);
var x =
e.clientX +
document.body.scrollLeft +
document.documentElement.scrollLeft;
var y =
e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
let left = x + "px";
let top = y + "px";
let obj = {
left: left,
top: top,
width: w,
height: h
};
temp.push(obj);
this.setState({ left: left, top: top, coords: temp });
localStorage.setItem("coords", JSON.stringify(temp));
};
render() {
const { backOffice, match } = this.props;
const { left, top, coords } = this.state;
console.log(coords);
return (
<React.Fragment>
<div>
<img
id="imgCoord"
src={require("../../assets/images/imageTagging.PNG")}
onClick={$event => this.handleclick($event)}
/>
{coords &&
coords.map(item => (
<img
style={{
width: "20px",
position: "absolute",
left: item.left,
top: item.top,
backgroundColor: "white"
}}
src={require("../../assets/images/tag.png")}
id="marker"
/>
))}
</div>
</React.Fragment>
);
}
}
/**
* Redux map state
#param {} state
#param {} ownParams
*/
function mapStateToProps(state, ownParams) {
console.log(state);
return {
backOffice: state.selectedCardData
};
}
export default withRouter(connect(mapStateToProps)(cardDetails));
[EDITED]
you need to add a event listener (here is docs https://www.w3schools.com/jsref/event_onresize.asp ) in componentDidMount method, and remove this listener in componentWillUnmount method.
inside listener put a function to set new coordinates.
and also need to calculate a percentage of icon's coordinates relative to width and height of the window
handleclick = e => {
...your previous code
const yPercent = h / top;
const xPercent = w / left;
this.setState({ yPercent, xPercent });
};
handleResize = () => {
var w = document.documentElement.clientWidth;
var h = document.documentElement.clientHeight;
const newTop = (this.state.yPercent * h) + "px";
const newLeft = (this.state.xPercent * w) + "px";
this.setState({ left: newLeft; top: newTop });
}
I'm using Konva 4.0.18 within a React 16.8. Application, which works well so far in the Standalone (CRA). When integrating this working solution into an existing Application Frame I'm facing an error which appears on transforming the rectangle (and constantly appears on moving the mouse even if the rectangle transformation stopped when clicked again):
Transformer.js:316 Uncaught TypeError: Cannot read property 'getStage' of undefined
at e._handleMouseMove (Transformer.js:316)
at default-passive-events.js:6
Screenshot of the debugger at the function 'findOne' which returns an 'undefined'
There is an Maskingcomponent which imports Konva's Stage, Layout and Image and as well as the MaskingRectangle which can be transformed.
import React, { useState, useEffect, useRef } from "react";
import MaskingRectangle from "./maskingRectangle";
const Masking = ({ canvas }) => {
const stageWrapperRef = useRef(null);
const [stageWidth, setStageWidth] = useState(600);
const [stageHeight, setStageHeight] = useState(400);
const [maskingRectangles, setMaskingRectangles] = useState([]);
const [shapes, setShapes] = useState([]);
const [selectedId, selectShape] = useState(null);
const [Stage, setStage] = useState(null);
const [Layer, setLayer] = useState(null);
const [Image, setImage] = useState(null);
const addRectangle = e => {
const getRandomInt = max => {
return Math.floor(Math.random() * Math.floor(max));
};
let rectX = getRandomInt(100);
let rectY = getRandomInt(100);
if (e) {
const { x, y } = e.currentTarget.getPointerPosition();
rectX = x;
rectY = y;
}
const rect = {
x: rectX,
y: rectY,
width: 100,
height: 100,
fill: "rgba(255,0,0,0.4)",
id: `rect${maskingRectangles.length + 1}`
};
const rects = maskingRectangles.concat([rect]);
setMaskingRectangles(rects);
const shs = shapes.concat([`rect${maskingRectangles.length + 1}`]);
setShapes(shs);
};
const deleteActiveRectangle = () => {
setMaskingRectangles(
maskingRectangles.filter(rect => rect.id !== selectedId)
);
setShapes(shapes.filter(shape => shape !== selectedId));
selectShape(null);
};
const onStageClick = e => {
if (e.target.attrs.className !== "mask-rect") {
selectShape(null);
}
};
const onStageDblClick = e => {
const stage = e.target.parent.parent;
const elem = e.currentTarget;
if (elem.clickStartShape.attrs.className !== "mask-rect") {
addRectangle(e);
}
if (elem.clickStartShape.attrs.id === selectedId) {
deleteActiveRectangle();
stage.container().style.cursor = "crosshair";
}
};
useEffect(() => {
async function loadKonvaModules() {
await import("react-konva").then(module => {
setStage(() => module.Stage);
setLayer(() => module.Layer);
setImage(() => module.Image);
});
}
loadKonvaModules();
const scaleX = stageWrapperRef.current.clientWidth / canvas.width;
const scaleY = stageWrapperRef.current.clientHeight / canvas.height;
const scalingFactor = Math.min(scaleX, scaleY);
setStageWidth(canvas.width * scalingFactor);
setStageHeight(canvas.height * scalingFactor);
}, []);
return (
<div>
<div className="stage-wrapper" ref={stageWrapperRef}>
<Stage
className="stage"
width={stageWidth}
height={stageHeight}
onClick={onStageClick}
onDblClick={onStageDblClick}
>
<Layer className="layer1">
<Image
image={canvas}
width={stageWidth}
height={stageHeight}
/>
{maskingRectangles.map((rect, i) => {
return (
<MaskingRectangle
key={i}
shapeProps={rect}
isSelected={rect.id === selectedId}
onSelect={() => {
selectShape(rect.id);
}}
onChange={newAttrs => {
const rects = maskingRectangles.slice();
rects[i] = newAttrs;
setMaskingRectangles(rects);
}}
onMouseEnter={e => {
const stage = e.target.parent.parent;
stage.container().style.cursor = "move";
}}
onMouseOut={e => {
const stage = e.target.parent.parent;
stage.container().style.cursor =
"crosshair";
}}
stageProps={{
width: stageWidth,
height: stageHeight
}}
/>
);
})}
</Layer>
</Stage>
</div>
</div>
);
};
export default Masking;
My MaskinRectangle componeht looks like this:
import React from "react";
import { Rect, Transformer } from "react-konva";
const MaskingRectangle = ({
shapeProps,
isSelected,
onSelect,
onChange,
stageProps,
onMouseEnter,
onMouseOver,
onMouseOut,
onMouseMove
}) => {
const shapeRef = React.useRef();
const trRef = React.useRef();
React.useEffect(() => {
if (isSelected) {
trRef.current.setNode(shapeRef.current);
trRef.current.getLayer().batchDraw();
}
}, [isSelected]);
const dragBoundFunc = pos => {
//Limit Min/Max x,y position
const stageWidth = (stageProps && stageProps.width) || 800;
const stageHeight = (stageProps && stageProps.height) || 600;
const minX = 0;
const minY = 0;
const maxX = stageWidth - shapeRef.current.attrs.width;
const maxY = stageHeight - shapeRef.current.attrs.height;
const newX = pos.x < minX ? minX : pos.x > maxX ? maxX : pos.x;
const newY = pos.y < minY ? minY : pos.y > maxY ? maxY : pos.y;
return {
x: newX,
y: newY
};
};
return (
<>
<Rect
className="mask-rect"
onClick={onSelect}
onTap={onSelect}
ref={shapeRef}
{...shapeProps}
draggable
onTouchMove={null}
onDragEnd={e => {
onChange({
...shapeProps,
x: e.target.x(),
y: e.target.y()
});
}}
onTransformEnd={e => {
const node = shapeRef.current;
const scaleX = node.scaleX();
const scaleY = node.scaleY();
node.scaleX(1);
node.scaleY(1);
onChange({
...shapeProps,
x: node.x(),
y: node.y(),
width: node.width() * scaleX,
height: node.height() * scaleY
});
e.evt.preventDefault();
}}
dragBoundFunc={dragBoundFunc}
onMouseOver={onMouseOver}
onFocus={onMouseOver}
onMouseEnter={onMouseEnter}
onMouseOut={onMouseOut}
onBlur={onMouseOut}
onMouseMove={onMouseMove}
/>
{isSelected && <Transformer ref={trRef} rotateEnabled={false} />}
</>
);
};
export default MaskingRectangle;
Some thoughts/guesses:
I wonder why this work perfectly well as a standalone App, but fails when included into an existing HTML Page (which, by the way, includes also libraries like jquery and Angular 1.x).
Maybe you can give me some hints how to search or answer one of these questions:
There are a couple of event handlers (click, mousemove) registered
on the body and other elements outside the React App itself, but
they shouldn't affect the react app, or am I wrong here?
I see the mention of default-passive-events in the error. How this
might have to do with the issue (and if so, how could that be
avoided)?
Is ist more likely that the Konva.js library has an
issue (see screenshot attached)
Thanks in advance for any help, comments, suggestions where/how to look
I can select objects on PC.
But i run it on phone, it's not work!
PC screenshots:
Phone screenshots:
NOTE: I rotated the canvas 90 degrees on the phone by the CSS(I guess it's because of this operation, but I don't know why):
transform-origin: left top;
transform: rotate(90deg);
import * as THREE from 'three';
export class PickupManager {
raycaster: THREE.Raycaster;
camera: THREE.Camera;
scene: THREE.Scene;
canvas: HTMLCanvasElement;
constructor(camera: THREE.Camera, scene: THREE.Scene, canvas: HTMLCanvasElement) {
this.raycaster = new THREE.Raycaster();
this.camera = camera;
this.scene = scene;
this.canvas = canvas;
this.addEventListener();
}
pickedObject: THREE.Object3D | any;
ljj1: THREE.Object3D = null;
ljj2: THREE.Object3D = null;
ljj3: THREE.Object3D = null;
ljj4: THREE.Object3D = null;
ljj5: THREE.Object3D = null;
pick = (normalizedPosition: THREE.Vector2) => {
this.ljj1 = this.ljj1 || this.scene.getObjectByName("LJJ1");
this.ljj2 = this.ljj2 || this.scene.getObjectByName("LJJ2");
this.ljj3 = this.ljj3 || this.scene.getObjectByName("LJJ3");
this.ljj4 = this.ljj4 || this.scene.getObjectByName("LJJ4");
this.ljj5 = this.ljj5 || this.scene.getObjectByName("64mmB016");
// 通过摄像机和鼠标位置更新射线
this.raycaster.setFromCamera(normalizedPosition, this.camera);
// 计算物体和射线的焦点
const objects = [this.ljj1, this.ljj2, this.ljj3,this.ljj4, this.ljj5];
const intersectedObjects = this.raycaster.intersectObjects(objects,true);
if (intersectedObjects.length > 0) {
this.pickedObject = intersectedObjects[0];
const obj:THREE.Object3D = this.pickedObject.object;
//output name of selected object
console.log(obj.name );
} else {
console.log("Not found!")
}
}
getCanvasRelativePosition = (event: MouseEvent | TouchEvent) => {
const rect: DOMRect = this.canvas.getBoundingClientRect();
const position: THREE.Vector2 = new THREE.Vector2();
if (event instanceof MouseEvent) {
position.x = event.clientX ;
position.y = event.clientY;
}
else if (event instanceof TouchEvent) {
const touch: Touch = event.changedTouches[0];
position.x = touch.clientX - rect.left;
position.y = touch.clientY - rect.top;
}
else {
throw "Incorrect event, needs MouseEvent or TouchEvent";
}
return position;
}
getPickPoint = (event: MouseEvent | TouchEvent) => {
const canvasPosition = this.getCanvasRelativePosition(event);
const pickPoint:THREE.Vector2 = new THREE.Vector2();
// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
pickPoint.x = (canvasPosition.x / window.innerWidth ) * 2 - 1;
pickPoint.y = (canvasPosition.y / window.innerHeight) * -2 + 1;
return pickPoint;
}
addEventListener = () => {
this.canvas.addEventListener('mousedown', this.onMouseDown, false);
this.canvas.addEventListener('touchstart', this.onTouchStart, false);
}
onMouseDown = (event: MouseEvent) => {
event.preventDefault();
this.pick(this.getPickPoint(event));
}
onTouchStart = (event: TouchEvent) => {
event.preventDefault();
this.pick(this.getPickPoint(event));
}
}
I want get the ljj1,ljj2,ljj3,ljj4,ljj5 object, i can get it on PC, but i can't get it on phone.
If you rotated the canvas using CSS then the way you compute mouse/touch coordinates won't work.
For handing CSS transformed elements you need some relatively complicated code
For mouse coordinates you can use event.offsetX and event.offsetY
For touch coordinates you need to generate synthetic events that pass the event position back into the browser so that it will generate event.offsetX and event.offsetY for you.
See How to get a canvas relative mouse position of a CSS 3D transformed canvas?
Also see this example and this gist
I'm using react and HTML5 canvas to draw rectangles over faces on an image, but react life-cycle is limiting me from doing this.
As I should use refs to reference to the canvas and this have to be done in componentDidMount() and I could not get props or states inside this function.
Here is the code:
import React, { Component } from 'react'
import { render } from 'react-dom'
import {Row} from 'react-bootstrap';
import '../App.css';
class TagImg extends Component {
constructor(props){
super(props);
this.state={
rects:[[110, 30, 70, 70], [40, 30, 70, 70]],
ctx : ''
};
this.tagPerson = this.tagPerson.bind(this);
}
tagPerson(e){
var x = e.offsetX,
y = e.offsetY;
for(var i=0;i<this.props.rects.length;i++) { // check whether:
if(x > this.props.rects[i][0] // mouse x between x and x + width
&& x < this.props.rects[i][0] + this.props.rects[i][2]
&& y > this.props.rects[i][1] // mouse y between y and y + height
&& y < this.props.rects[i][1] + this.props.rects[i][3]) {
alert('Rectangle ' + i + ' clicked');
}
}
}
componentDidMount() {
console.log("componentDidMount");
const ctx = this.refs.canvas.getContext('2d');
var myImage = new Image();
myImage.onload = function() {
var width = myImage.naturalWidth; // this will be 300
var height = myImage.naturalHeight; // this will be 400
ctx.drawImage(myImage, 0, 0, 300, 200);
ctx.beginPath();
ctx.strokeStyle="white";
// for(var i=0;i<this.state.rects.length;i++) {
// // ctx.rect(this.state.rects[i][0], // fill at (x, y) with (width, height)
// // this.state.rects[i][1],
// // this.state.rects[i][2],
// // this.state.rects[i][3]);
// console.log("helloo");
// }
console.log(this.state.rects);
ctx.stroke();
}
myImage.src = this.props.path;
}
render() {
return (
<div>
<form id="afterUpload" action="" method="post" encType="multipart/form-data">
<Row id="image_preview" className="row">
<canvas ref="canvas" onClick={this.tagPerson}/>
</Row>
</form>
</div>
);
}
}
export default TagImg;
After searching I figured out that I could use componentWillRecieveProps() but did not understand how to use it in my case.
componentDidMount will be called only once from React, after the component is mounted.
Since you want to draw something every time a user clicks on the canvas, you have to do that in a place that it's called at every rendering, like inside the render function itself.
Something like this:
import React, { Component } from 'react'
import { render } from 'react-dom'
import {Row} from 'react-bootstrap';
class TagImg extends Component {
constructor(props){
super(props);
this.state={
rects:[[110, 30, 70, 70], [40, 30, 70, 70]]
};
this.tagPerson = this.tagPerson.bind(this);
}
tagPerson(e){
var x = e.offsetX,
y = e.offsetY;
for(var i=0;i<this.props.rects.length;i++) { // check whether:
if(x > this.props.rects[i][0] // mouse x between x and x + width
&& x < this.props.rects[i][0] + this.props.rects[i][2]
&& y > this.props.rects[i][1] // mouse y between y and y + height
&& y < this.props.rects[i][1] + this.props.rects[i][3]) {
alert('Rectangle ' + i + ' clicked');
}
}
}
drawRects() {
const ctx = this.refs.canvas.getContext('2d');
ctx.drawImage(myImage, 0, 0, 300, 200);
ctx.beginPath();
ctx.strokeStyle="white";
// for(var i=0;i<this.state.rects.length;i++) {
// // ctx.rect(this.state.rects[i][0], // fill at (x, y) with (width, height)
// // this.state.rects[i][1],
// // this.state.rects[i][2],
// // this.state.rects[i][3]);
// console.log("helloo");
// }
console.log(this.state.rects);
ctx.stroke();
}
componentDidMount() {
console.log("componentDidMount");
var myImage = new Image();
myImage.onload = this.drawRects.bind(this);
myImage.src = this.props.path;
}
render() {
drawRects();
return (
<div>
<form id="afterUpload" action="" method="post" encType="multipart/form-data">
<Row id="image_preview" className="row">
<canvas ref="canvas" onClick={this.tagPerson}/>
</Row>
</form>
</div>
);
}
}
export default TagImg;
In the code I'm downloading the image once, in the componentDidMount method. And I'm running the drawRects() method at every rendering, so every time you call a setState you'll have your new rects