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
Related
I have a Game class with canvas and I add an event listener for mouse clicks in the constructor.
I have a method that should log the position of the mouse click, but I get error that this method is not defined:
class Game {
constructor(canvas, event) {
this.canvas = canvas;
this.canvas.addEventListener('mousedown', function (e) {
getClickPosition(canvas, e)
})
}
getClickPosition(canvas, e) {
const rect = canvas.getBoundingClientRect()
const x = event.clientX - rect.left
const y = event.clientY - rect.top
console.log("x: " + x + " y: " + y)
}
}
const canvas = document.querySelector('canvas')
const game = new Game(canvas)
Use an arrow function expression (to preserve this) and call this.getClickPosition():
this.canvas.addEventListener('mousedown', (e) => {
this.getClickPosition(canvas, e);
});
Hello I am tring to make a svg editor where I can create and drag the rectangles I create. Each time the mouseDown event gets fired I add 2 eventListeners to the svg element (mouseMove and mouseUp) and each time mouseUp gets called I try to remove these events, but because the remove doesn't work the element has multiple mouseMove and mouseUp listeners attached to it. I can't figure out what's wrong. MouseDown does 2 things, first it can draw the rectangles and then if createMode is false it let me drag them
this.domElement.addEventListener("mousedown", (event) => {
if (event.button === 0 && this.createMode === true) {
document.body.style.cursor = 'crosshair'
const rect = document.createElementNS(this.svgns, 'rect');
//rect.setAttribute('tabindex', "1");
let first_mouseX = event.clientX;
let first_mouseY = event.clientY;
const drawRect = (event) => {
let mouseX = event.clientX;
let mouseY = event.clientY;
const width = Math.abs(mouseX - first_mouseX);
const height = Math.abs(mouseY - first_mouseY);
if (mouseX > first_mouseX) {
mouseX = first_mouseX;
}
if (mouseY > first_mouseY) {
mouseY = first_mouseY;
}
rect.setAttribute('x', mouseX - this.domElement.getBoundingClientRect().left)
rect.setAttribute('y', mouseY - this.domElement.getBoundingClientRect().top);
rect.setAttribute('width', width);
rect.setAttribute('height', height);
rect.setAttribute('stroke', "black");
rect.setAttribute('fill', 'white');
rect.setAttribute('stroke-width', 2);
this.rect = rect;
this.domElement.appendChild(rect);
}
const endDraw = () => {
this.domElement.removeEventListener("mousemove", drawRect);
this.domElement.removeEventListener("mouseup", endDraw);
if (this.rect !== null) {
this.rect.addEventListener('click', () => {
this.createMode = false;
document.body.style.cursor = "auto"
event.target.focus()
})
this.rect.addEventListener('blur', () => {
this.stergeClick();
})
this.rect.addEventListener("focus", (event) => {
this.stergeClick();
this.adaugaClick(event.target)
})
this.rect.focus();
this.rect = null;
}
}
this.domElement.addEventListener("mouseup", endDraw)
this.domElement.addEventListener("mousemove", drawRect)
} else if (this.createMode === false) {
const startDrag = (event) => {
this.selectedItem = {}
this.selectedItem.item = event.target
this.selectedItem.offsetX = event.clientX - this.selectedItem.item.getAttribute('x');
this.selectedItem.offsetY = event.clientY - this.selectedItem.item.getAttribute('y');
}
startDrag(event)
const drag = (event) => {
if (Object.keys(this.selectedItem).length > 0 && this.createMode === false && this.selectedItem.item.nodeName !== "svg") {
this.stergeClick();
this.adaugaClick(this.selectedItem.item);
this.selectedItem.item.setAttribute('x', event.clientX - this.selectedItem.offsetX);
this.selectedItem.item.setAttribute('y', event.clientY - this.selectedItem.offsetY);
} else {
return false
}
}
const endDrag = (ev) => {
if (Object.keys(this.selectedItem).length > 0 && this.selectedItem.item.nodeName !== "svg") {
this.stergeClick();
this.adaugaClick(this.selectedItem.item);
ev.target.removeEventListener('mouseup', drag);
ev.target.removeEventListener('mousemove', endDrag);
this.domElement.removeEventListener('mouseup', drag);
this.domElement.removeEventListener('mousemove', endDrag);
this.selectedItem = {};
}
}
this.domElement.addEventListener("mousemove", drag);
this.domElement.addEventListener("mouseup", endDrag);
}
})
}
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?
There is a class with a handler that calculates the direction - InputManager.
How do I change the values of variables in the main Game class each time process an event in the InputManager?
On the codepen or:
class Game {
constructor() {
this.size = 4;
this.score = 0;
this.inputManager = new InputManager;
}
}
class InputManager {
constructor() {
this.mouseDown_position = {};
this.events = {};
this.listen();
}
listen() {
document.addEventListener("mousedown", () => {
this.mouseDown_position = {
x : event.clientX,
y : event.clientY
};
});
document.addEventListener("mouseup", () => {
let mouseUp_position = {
x : event.clientX,
y : event.clientY
};
let deltaX = this.mouseDown_position.x - mouseUp_position.x,
deltaY = this.mouseDown_position.y - mouseUp_position.y;
// Move directions:
if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX > 0) {
console.log('left');
}
if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX < 0) {
console.log('right');
}
});
}
}
My advice is that you pass the Game context to the InputManager, and from there you can manipulate the variable.
This is not encapsulation done right.
In theory Game should have a function to update the score, and InputManager should call that function to update the score (or use a similar approach for other variables).
class Game {
constructor() {
this.size = 4;
this.score = 10;
this.inputManager = new InputManager(this);
this.direction = this.inputManager.direction;
console.log(this.direction);
}
}
class InputManager {
constructor(gC) {
console.log(gC.score);
this.gC = gC;
this.mouseDown_position = {};
this.events = {};
this.direction = null;
this.listen();
}
listen() {
document.addEventListener("mousedown", () => {
this.mouseDown_position = {
x : event.clientX,
y : event.clientY
};
});
document.addEventListener("mouseup", () => {
this.gC.score++;
console.log(this.gC.score);
let mouseUp_position = {
x : event.clientX,
y : event.clientY
};
let deltaX = this.mouseDown_position.x - mouseUp_position.x,
deltaY = this.mouseDown_position.y - mouseUp_position.y;
// MOVE DIRECTION:
// LEFT
if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX > 0) {
console.log('left');
this.direction = 'left';
}
// RIGHT
if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX < 0) {
console.log('right');
this.direction = 'right';
}
// UP
if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY > 0) {
console.log('up');
this.direction = 'up';
}
// DOWN
if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY < 0) {
console.log('down');
this.direction = 'down';
}
});
}
}
var game = new Game();
class Game {
constructor() {
this.size = 4;
this.score = 0;
this.changesize = (size) => {
this.size = size;
}
this.inputManager = new InputManager(changesize);
}
class InputManager(){
constructor(changesize){
[...]
this.changeSize = changesize;
this.changeSize(300);
[...]
}
}
One option could be to supply a reference to InputManager which refers back to the Game instance. For example, suppose InputManager required this reference on its constructor:
class InputManager {
constructor(game) {
this.game = game; // <-- store it locally here
this.mouseDown_position = {};
this.events = {};
this.listen();
}
//...
Then when creating an instance of InputManager, your Game class could pass a reference to itself:
this.inputManager = new InputManager(this);
With that reference, anywhere in InputManager you can modify the values on that instance. Something as simple as:
this.game.score++;
From here you can further refactor to invoke operations in the objects instead of modifying values directly, you can pass closures to wrap access to the values, etc. But the main point is that you'd simply need some reference back to what you want to modify.
I have a canvas that I can draw things what I want to do is generate new canvases dynamically when clicking a button.I've defined a generate function but it did not work
here is script
//<![CDATA[
window.addEventListener('load', function () {
// get the canvas element and its context
var canvas = document.getElementById('sketchpad');
var context = canvas.getContext('2d');
// create a drawer which tracks touch movements
var drawer = {
isDrawing: false,
touchstart: function (coors) {
context.beginPath();
context.moveTo(coors.x, coors.y);
this.isDrawing = true;
},
touchmove: function (coors) {
if (this.isDrawing) {
context.lineTo(coors.x, coors.y);
context.stroke();
}
},
touchend: function (coors) {
if (this.isDrawing) {
this.touchmove(coors);
this.isDrawing = false;
}
}
};
// create a function to pass touch events and coordinates to drawer
function draw(event) {
var type = null;
// map mouse events to touch events
switch(event.type){
case "mousedown":
event.touches = [];
event.touches[0] = {
pageX: event.pageX,
pageY: event.pageY
};
type = "touchstart";
break;
case "mousemove":
event.touches = [];
event.touches[0] = {
pageX: event.pageX,
pageY: event.pageY
};
type = "touchmove";
break;
case "mouseup":
event.touches = [];
event.touches[0] = {
pageX: event.pageX,
pageY: event.pageY
};
type = "touchend";
break;
}
// touchend clear the touches[0], so we need to use changedTouches[0]
var coors;
if(event.type === "touchend") {
coors = {
x: event.changedTouches[0].pageX,
y: event.changedTouches[0].pageY
};
}
else {
// get the touch coordinates
coors = {
x: event.touches[0].pageX,
y: event.touches[0].pageY
};
}
type = type || event.type
// pass the coordinates to the appropriate handler
drawer[type](coors);
}
// detect touch capabilities
var touchAvailable = ('createTouch' in document) || ('ontouchstart' in window);
// attach the touchstart, touchmove, touchend event listeners.
if(touchAvailable){
canvas.addEventListener('touchstart', draw, false);
canvas.addEventListener('touchmove', draw, false);
canvas.addEventListener('touchend', draw, false);
}
// attach the mousedown, mousemove, mouseup event listeners.
else {
canvas.addEventListener('mousedown', draw, false);
canvas.addEventListener('mousemove', draw, false);
canvas.addEventListener('mouseup', draw, false);
}
// prevent elastic scrolling
document.body.addEventListener('touchmove', function (event) {
event.preventDefault();
}, false); // end body.onTouchMove
}, false); // end window.onLoad
function generate(){
var newCanvas = document.createElement('canvas');
newCanvas.width = 400;
newCanvas.height = 400;
document.getElementById('container').appendChild(newCanvas);
ctx = newCanvas.getContext('2d');
}
//]]>
here is jsfiddle http://jsfiddle.net/regeme/WVUwn/
ps:drawing not displayed on jsfiddle however it works on my localhost I have totally no idea about it , anyway what I need is generate function , I did but I think I am missing something..
Any ideas? thanks..
Below is a function I wrote to dynamically create canvas.
If the canvas already exists (same ID) then that canvas is returned.
The pixelRatio parameter can be defaulted to 1. It's used for setting the correct size on retina displays (so for iPhone with Retina the value would be 2)
function createLayer(sizeW, sizeH, pixelRatio, id, zIndex) {
// *** An id must be given.
if (typeof id === undefined) {
return false;
}
// *** If z-index is less than zero we'll make it a buffer image.
isBuffer = (zIndex < 0) ? true : false;
// *** If the canvas exist, clean it and just return that.
var element = document.getElementById(id);
if (element !== null) {
return element;
}
// *** If no zIndex is passed in then default to 0.
if (typeof zIndex === undefined || zIndex < 0) {
zIndex = 0;
}
var canvas = document.createElement('canvas');
canvas.width = sizeW;
canvas.height = sizeH;
canvas.id = id;
canvas.style.width = sizeW*pixelRatio + "px";
canvas.style.height = sizeH*pixelRatio + "px";
canvas.style.position = "absolute";
canvas.style.zIndex = zIndex;
if (!isBuffer) {
var body = document.getElementsByTagName("body")[0];
body.appendChild(canvas);
}
return canvas;
}
Change the jsfiddle option
"onLoad"
to
"No Wrap - in <body>"
EDIT: See also similar question over here: Uncaught ReferenceError for a function defined in an onload function
JSFiddle options: http://doc.jsfiddle.net/basic/introduction.html#frameworks-and-extensions
This is the working update of your JSFIDDLE
javascript:
document.getElementById('generate').addEventListener('mousedown', generate, false);
I guess this is what you want.
I've just added an eventListener to your button in javascript code itself.
P.S.: I've also added black background color to canvas to show it on white background.