I am writing an application that draws rectangles on a HTML canvas using the fillRect function. I currently track the movement of the mouse and detect when the mouse pointer hovers over a rectangle to highlight it:
This is how I am currently detecting collision which works great.
//boxes2 is my array of rectangles
var l = boxes2.length;
for (var i = l - 1; i >= 0; i--) {
if (mouseX >= boxes2[i].x && mouseX <= (boxes2[i].x + boxes2[i].w ) &&
mouseY >= boxes2[i].y && mouseY <= (boxes2[i].y + boxes2[i].h )) {
selectedBoxNum = i;
}
}
My problem is that this hover detection no longer works well after zooming in/out as the actual bounds of the rectangles desync from their values in my rectangle array.
var currentZoomValue = 1;
function myOnMouseWheel(event) {
event.preventDefault();
// Normalize wheel to +1 or -1.
var wheel = event.wheelDelta / 120;
if (wheel == 1) {
zoom = 1.1;
}
else {
zoom = .9;
}
currentZoomValue = currentZoomValue * zoom
canvas.style.transform = "scale(" + currentZoomValue + ")";
}
What I have tried:
Scaling the values in the array as I zoom in/out so that the rectangle bounds will stay in sync
This will not work for me because the scale function is stretching my canvas to make the rectangles look bigger. If I also actually make them bigger, they will be doubly enlarged and outpace the zoom of my canvas background.
Compensating my hover detection based upon my current zoom level
I have tried something like:
if (mouseX >= boxes2[i].x && mouseX <= (boxes2[i].x + (boxes2[i].w * currentZoomValue) ) &&
mouseY >= boxes2[i].y && mouseY <= (boxes2[i].y + (boxes2[i].h * currentZoomValue) )) {
selectedBoxNum = i;
}
My attempts at this do not work because while the rectangle height and width do scale in an easily predictable way, the x,y coordinates do not. When zooming in, the rectangles will radiate out from the center so some rectangles will gain x value and other lose based upon their position. I also considered maintaining a second rectangle array that I could use just for hover detection but decided against it for this reason.
A good solution would be to actually scale the rectangle's sizes to give the illusion of zooming, but the rectangles positions on the background image is important, and this technique will not affect the background.
Since there is no standard way of knowing page zoom level, I would suggest catching the click event with an absolutely-positioned div.
You can get the offset of your canvas element with the getBoundingClientRect() method.
Then, the code would look something like this:
boxes2.forEach(function(box, i) {
cx.fillRect(box.x, box.y, box.w, box.h);
/* We create an empty div */
var div = document.createElement("div");
/* We get the position of the canvas */
var rect = canvas.getBoundingClientRect();
div.style.position = "absolute";
div.style.left = (rect.left + box.x) + "px"; //Don't forget the pixels!!!
div.style.top = (rect.top + box.y) + "px";
div.style.width = box.w + "px";
div.style.height = box.h + "px";
/* For demonstration purposes we display a border */
div.style.border = "1px dashed black"
div.onclick = function() {/* Your event handler */}
document.body.appendChild(div);
});
Here's a live demonstration. At least in my browser, regions stay consistent even if I zoom in and out the page.
Related
I want to achieve a draggable element in vanilla javascript.
I want to make a small circular div draggable within a square div.
To make myself a bit more clear, I do NOT want to:
Create drag and drop,
Use jQuery UI or any other library or plugin to achieve this, just vanilla javascript
I already have a few events for handling dragging:
parent.addEventListener("mousedown", ..),
document.addEventListener("mouseup", ..)
and
document.addEventListener("mousemove", ..)
My question is how can I keep the draggable inside the given bounds of its parent?
Reference: https://codepen.io/ChickenFlavored/pen/rNxRXGo
You may use getBoundingClientRect that gives DOMRect object(an invincible rectangle that encompass the object) with eight properties: left, top, right, bottom, x, y, width, height.
var parent = document.querySelector('.parent');
var parentRect = parent.getBoundingClientRect();
var draggable = document.querySelector('.draggable');
var draggableRect = draggable.getBoundingClientRect();
You can get the mouse (X,Y) coordinates anytime by using e.clientX and e.clientY, so just check if they are outside the bounding rectable of .parent div if so then only update the draggable div's left and top properties
if( (e.clientX >= parentRect.left && (e.clientX+draggableRect.width <= parentRect.right)) &&
(e.clientY >= parentRect.top && (e.clientY+draggableRect.height <= parentRect.bottom))
//+draggableRect.height and +draggableRect.height accounts for the size of ball itself
){
draggable.style.left = `${e.clientX}px`;
draggable.style.top = `${e.clientY}px`;
}
Note that numbers increase down and towards right in graphics world
https://codepen.io/vsk/pen/PozVryr
UPDATE:
To fix the issue mentioned in comment use this
if(/* mouse was moved withing red container's bounds*/)
else{
//if mouse went out of bounds in Horizontal direction
if(e.clientX+draggableRect.width >= parentRect.right){
draggable.style.left = `${parentRect.right-draggableRect.width}px`;
}
//if mouse went out of bounds in Vertical direction
if(e.clientY+draggableRect.height >= parentRect.bottom){
draggable.style.top = `${parentRect.bottom-draggableRect.height}px`;
}
}
And assign mousemove to document insted of the div container
I hope you could provide a code snippet to try to simulate what you want, a codepen url, codesandbox or similar.
I made this sample using what you provided : )
let x = event.clientX - element.offsetLeft
const elementWidth = element.clientWidth
const fullX = x + elementWidth
const containerW = yourContainer.clientWidth
if(fullX > containerW){
//If element will be out of container
x = containerW - elementWidth // So it will be never out of container
}
element.style.transform = "translate("+x+"px, "+y+"px)"
I am looking to try and do something like this where the content is off the screen and when you move the mouse the browser follows it around. I was thinking it would be similar to this where the edge of the screen animates when the mouse moves.
It looks like in the original example they use JS to change the transform: matrix. On the second link the screen is animated using greensock and the following code to change the CSS:
// Mouse move tilt effect
$(document).mousemove(function(event){
// Detect mouse position
var xPos = (event.clientX/$(window).width())-0.5;
var yPos = (event.clientY/$(window).height())-0.5;
// Tilt the hero container
TweenLite.to($hero, 0.6, {rotationY:5*xPos, rotationX:5*yPos, ease:Power1.easeOut, transformPerspective:900, transformOrigin:"center"});
// Update text on the page with the current mouse position
$(".bottom strong").text(event.pageX + ", " + event.pageY);
});
Is it possible to do something similar to do what I need?
Based on how I understood your intentions basically what you need to do is.
Create a div container which has width and height greater than window size and fill it up with content
Create div container which has width and height equal to window and overflow: hidden and contains the container in 1.
Center container in 1 in 2 with transform: translateX(-25%) translateX(-25%); and transition: transform 1s;
After that
Detected mouse position
Calculate distance from center of window
And based on that add or remove up to 25% to the translateX and translateY value
EDIT:
document.querySelector('body').style.transition = 'transform 1s';
window.addEventListener('mousemove', event => {
const absMaxX = window.innerWidth / 2;
const absMaxY = window.innerHeight / 2;
const maxDistance = Math.sqrt(Math.pow(absMaxX, 2) + Math.pow(absMaxY, 2));
const mouseX = event.clientX;
const mouseY = event.clientY;
const directionX = mouseX - absMaxX >= 0 ? 1 : -1;
const directionY = mouseY - absMaxY >= 0 ? 1 : -1;
const distance = Math.sqrt(Math.pow(mouseX - absMaxX, 2) + Math.pow(mouseY - absMaxY, 2))
const translation = distance / maxDistance * 100;
document.querySelector('body').style.transform =
`translateX(${directionX * translation}px) translateY(${directionY * translation}px)`
});
I am having difficulty of getting the mouse coordinates and the hero's coordinates.
When I click on the bottom right corner of the canvas I get a result of x = 641, y = 386 for the mouse click. The enemy coordinate system is 100% accurate.
It seems that the enemy coordinate system is different than the mouse coordinate system. I want them to be on a single coordinate system. Thanks for your help!
This is the initialization of canvas:
<canvas id="canvas" width = "1664" height = "1000" style = "border:1px solid gray; width: 640px; height 480px;"> </canvas>
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
var mousePos = getMousePos(Context.canvas,e);
//Use mousePos.x or mousePos.y to get the coordinates of the mouse click
var mx, my;
if(e.offsetX) {
mx = e.offsetX;
my = e.offsetY;
}
else if(e.layerX) {
mx = e.layerX;
my = e.layerY;
}
Gun.shoot()
for (var i = EnemyManager.enemies.length - 1; i >= 0; i--) {
var enemy = EnemyManager.enemies[i]
console.log("Enemy: " + enemy.x + " " + enemy.y)
console.log("Mouse: " + mousePos.x + " " + mousePos.y)
if ((enemy.x < mx) && (enemy.y < my) && ( (enemy.x + enemy.width) > mx ) && (enemy.y + enemy.height > my)) {
alert("Target HIT")
}
};
})*
I've had the same problem using canvas (especially it needs to work when scrolled down on the page, offset from the top of the page by arbitrary divs, and at different zoom levels). I've found the approach in this answer to be the most robust as a replacement to that getMousePos function.
You are receiving different coordinates probably because you are using two different methods. In the player's position, you are using offsetX instead of mousePos.X (enemy.X in comparison)
Also, since you are defining the return of getMousePos as X and Y, you need to access them as X and Y. So:
var mx, my;
mx = mousePos.X;
my = mousePos.Y;
EDIT: You may also find this link helpful.
You are clicking outside of canvas see my example, http://jsfiddle.net/gn0pkkra/.
document.getElementById('wrapper').addEventListener('click', on_canvas_click, false);
You can compute the exact trajectory even if you are clicking outside of canvas. Then Cartesian coordinates will have the center (0,0) on you game cannon position in canvas. Compute cannon/gun offset over top left corner of canvas (real 0,0) and use those offsets to detect your trajectories.
Clicking on buttom right of the canvas, or even outside of it's impossible to get negative values.
I am using JavaFX WebView included in jdk-8u45 to open a web page which shows a map using OpenLayers 2.13.1. I'm trying to zoom in on the map using a ZoomBox with a BoxHandler. The zooming works like it should, but the problem is how the the rectangle is drawn.
The wanted result is that once I click on the map and start dragging, the rectangle should start drawing as I move the mouse. This works fine in all browsers, except inside my WebView. What happens is that only after I have moved my mouse a few cm in both x- and y-direction (e.g. diagonally), the rectangle starts drawing from this position (not the one where I started dragging). I have looked at the coordinates from the different mouse events, and they all seem to be correct, which is confirmed by the fact that it zooms in on the area I actually dragged over (e.g. not the area that is drawn).
JavaScript console.log stmts output coordinates from the moment I click on the map, but nothing is drawn in the beginning of the drag.
Has anyone had similar problems with WebView? As I said, the code works like a charm in all other browsers I have tried (Safari, Firefox, Chrome, IE). I have looked around on the internet, but I haven't been able to find an answer to my problem.
Code taken from BoxHandler.js:
startBox: function (xy) {
;;;console.log(xy);
;;;console.log("startbox xy=" + xy.x + "," + xy.y);
if(this.zoomBox){
this.removeBox();
}
this.zoomBox = OpenLayers.Util.createDiv('zoomBox',
this.dragHandler.start);
this.zoomBox.className = this.boxDivClassName;
this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
this.map.viewPortDiv.appendChild(this.zoomBox);
OpenLayers.Element.addClass(
this.map.viewPortDiv, "olDrawBox"
);
},
/**
* Method: moveBox
*/
moveBox: function (xy) {
var startX = this.dragHandler.start.x;
var startY = this.dragHandler.start.y;
;;;console.log("dragHandler.start.x=" + this.dragHandler.start.x);
;;;console.log("dragHandler.start.y=" + this.dragHandler.start.y);
var deltaX = Math.abs(startX - xy.x);
var deltaY = Math.abs(startY - xy.y);
this.zoomBox.style.width = Math.max(1, deltaX) + "px";
this.zoomBox.style.height = Math.max(1, deltaY) + "px";
this.zoomBox.style.left = xy.x < startX ? xy.x+"px" : startX+"px";
this.zoomBox.style.top = xy.y < startY ? xy.y+"px" : startY+"px";
console.log("zoombox width=" + this.zoomBox.style.width);
console.log("zoombox height=" + this.zoomBox.style.height);
console.log("zoombox left=" + this.zoomBox.style.left);
console.log("zoombox top=" + this.zoomBox.style.top);
// depending on the box model, modify width and height to take borders
// of the box into account
var box = this.getBoxCharacteristics();
;;;console.log("movebox xOffset=" + box.xOffset);
;;;console.log("movebox yOffset=" + box.yOffset);
if (box.newBoxModel) {
if (xy.x > startX) {
this.zoomBox.style.width =
Math.max(1, deltaX - box.xOffset) + "px";
}
if (xy.y > startY) {
this.zoomBox.style.height =
Math.max(1, deltaY - box.yOffset) + "px";
}
}
},
/**
* Method: endBox
*/
endBox: function(end) {
var result;
console.log(this.map.viewPortDiv.lastElementChild.style);
if (Math.abs(this.dragHandler.start.x - end.x) > 5 ||
Math.abs(this.dragHandler.start.y - end.y) > 5) {
var start = this.dragHandler.start;
var top = Math.min(start.y, end.y);
var bottom = Math.max(start.y, end.y);
var left = Math.min(start.x, end.x);
var right = Math.max(start.x, end.x);
result = new OpenLayers.Bounds(left, bottom, right, top);
} else {
result = this.dragHandler.start.clone(); // i.e. OL.Pixel
}
this.removeBox();
this.callback("done", [result]);
}
Also, I don't know if this is relevant, but when I inspect the HTML div element that holds the map (using Firebug Lite) it looks like the top border of the div is further down than it should be. The map (in correct position on the webpage) is extending beyond the top border. This is different behavior than in the other browsers I mentioned.
Any help would be appreciated.
I'm trying to implement a function as a part of a firefox add-on with canvas which gives the user the ability to draw.
function draw(event,context,drawit) {
var drawx = event.layerX;
var drawy = event.layerY;
if (!drawit) {
context.beginPath();
context.strokeStyle='rgb(0,255,0)';
context.lineWidth=1;
context.moveTo(drawx,drawy);
drawit = true;
} else {
context.lineTo(drawx,drawy);
context.stroke();
}
};
This works, but there seems to be a difference between the result of layerX/layerY and the line drawn. It's only possible to draw in the upper left part of the canvas element. When the mouse pointer reaches about the half of the element the line doesn't go further.
I already checked the position of the elements in Firebug and it seems ok: the canvas is inside a div-element and both have a defined width of 100%, while the drawing ends at about 50% of the element. It also works to set the values manually so that the line is also drawn in the right part of the canvas element.
Does anyone have an idea what is going wrong?
When the mouse pointer reaches about the half of the element the line doesn't go further.
This could happen for a lot of reasons. Mis-translated context (from rotation/translation/scaling) and mismatched canvas size (what you wrote it as in html and what you're considering it in code).
Are you certain that LayerX and LayerY are getting you the right mouse co-ordinates? What exactly do you mean by "difference" between layerx/y and whats drawn? Is there an offset? I ask because my mouse code is a bit more complex:
// Sets mx,my to the mouse position relative to the canvas
// unfortunately this can be tricky, we have to worry about padding and borders
function getMouse(e) {
var element = canvas, offsetX = 0, offsetY = 0;
if (element.offsetParent) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
// Add padding and border style widths to offset
offsetX += stylePaddingLeft;
offsetY += stylePaddingTop;
offsetX += styleBorderLeft;
offsetY += styleBorderTop;
mx = e.pageX - offsetX;
my = e.pageY - offsetY
}