How to detect objects on the canvas with click event - javascript

Hi I am using a hashmap that allows me to efficiently detect objects in the given coordinates. However it is working perfectly , the problem lies with using the mouse to gather the position of the mouse within the canvas down to the pixel. I have been using the offsetX and offsetY methods for the event to gather some offset but it seems there is an offset I am unaware of and may have something to do with either:
1.using scaling on the canvas , Note: ive tried to fix this by division of the renderscale, this works with everything else so should be fine here.
mouseoffset is not accounting for parts of the page or is missing pixels at a low level (maybe 20) but divided by the render scale thats massive.
3.I am using a cartesian coordinate system to simplify things for the future , so the game map is in cartesian and may have to do with the problem.
I will not be supplying all the code because it is allot of work to go through it all so i will supply the following :
the html/css canvas code
<html lang="en">
<head>
<meta charset="UTF-8">
<title> Game</title>
</head>
<body onload="jsEngine = new JsEngine(24, 24, .1); " >
<div class ="wrapper">
<canvas id="canvas" width="1920" height="1080"></canvas>
</div>
<style>
.wrapper {
position: relative;
width: auto;
height: 900px;
}
.wrapper canvas {
position: absolute;
left: 90px;
top: 50px;
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
width: 90%;
height: 90%;}
.GUI{
top: -315px;
left: -302px;
position: absolute;
width: 300px;
height: 300px;
background-color: cadetblue;
opacity: .5;
word-wrap: break-word;}
img{
image-rendering: optimize-contrast;
}
</style>
<div id = GUI class = "GUI"></div>
<!-- Libraries -->
<script src="../myapi/JSONE.js"></script>
<script src="../myapi/engine/SpacialHash.js"></script>
</body>
</html>
2.the javascript click function
//Click on objects
let onClick = function(event){
let canvas_ctx = document.getElementById("canvas").getContext("2d");
let canvasOffsetX = canvas_ctx.canvas.width/2;
let canvasOffsetY = canvas_ctx.canvas.height/2;
let mousePosX = event.clientX;
let mousePosY = event.clientY;
let mouseX =jsEngine.cameraFocus.x-canvasOffsetX/jsEngine.renderScale+(mousePosX)/jsEngine.renderScale;
let mouseY = jsEngine.cameraFocus.y+(canvasOffsetY)/jsEngine.renderScale+((-mousePosY)/jsEngine.renderScale);
console.log("sum to",mouseX,mouseY);
//My hashMap to place the mouse coordinates on the game map
let clickPosition = hm.find({x:mouseX,y:mouseY,width:1,height:1});
if(clickPosition.length===1){
let gameObject = jsEngine.gameObjects[clickPosition[0].range.id];
//console.log(gameObject.transform.x,gameObject.transform.y,mouseX,mouseY);
let clickBox = {};
let picture = gameObject.texture;
guiCreateClickBox(clickBox,gameObject.id,1200,500,picture);
}else if(clickPosition.length>1) {
for (let i = 0; i < clickPosition.length; i++) {
let gameObject = jsEngine.gameObjects[clickPosition[i].range.id];
if (gameObject instanceof PlayerShip|| gameObject instanceof Bullet)
continue;
let clickBox = {};
let picture = gameObject.texture;
guiCreateClickBox(clickBox,gameObject.id,1200,500,picture);
//console.log(gameObject.transform.x,gameObject.transform.y,mouseX,mouseY)
}
}
};
// Listeners
//Click on objects
document.getElementById("canvas").addEventListener("click", onClick);
the making of the map and scale :Note: this is done via onPreRender
function drawBackground(canvas_ctx, renderScale, imageResource) {
let img = imageResource.mapBackground;
let mapWidth = 1000000;
let mapHeight= 1000000;
let zoom = 1;
mapWidth *= renderScale / zoom;
mapHeight *= renderScale / zoom;
// Render the Background
canvas_ctx.fillStyle = canvas_ctx.createPattern(img, 'repeat');
canvas_ctx.scale(zoom, zoom);
canvas_ctx.fillRect(-mapWidth / 2, - mapHeight / 2, mapWidth, mapHeight);
//if (jsEngine.cameraFocus.x > 1000000) {}
canvas_ctx.scale(1/zoom, 1/zoom);
}
The rendering method used for playership
renderGameObject(gameObject) {
let x = gameObject.transform.x * this.renderScale;
let y = -(gameObject.transform.y * this.renderScale);
let rotation = Math.radians(gameObject.transform.rotation);
let width = gameObject.transform.width;
width *= this.renderScale;
let height = gameObject.texture.height;
height *= this.renderScale;
// Render the gameObject
this.canvas_ctx.translate(x, y);
this.canvas_ctx.rotate(rotation);
this.canvas_ctx.drawImage(gameObject.texture, 0, 0, width / this.renderScale, height / this.renderScale, // Make sure the image is not cropped
-width/2 , // X
-height/2 , // Y
width, height); // width and height
this.canvas_ctx.rotate(-rotation);
this.canvas_ctx.translate(-x, -y);
}
the issue to solve is to make it so that when you click on any given quadrant of the canvas it will return -+ for top left, -- bottom left , -+ topright, +- bottomright, as well as being applied to the render scale which at the moment is .1 so just divide your mouse and canvas coords like shown above and you should be able to get the same results.
Things to keep in mind :
the jsEngine.cameraFocus is set to the playerships x and y coordinates(which are set to the 0,0 posiiton on the map) (which are also in the middle of the ship)
the top left of the canvas is still 0,0 and ++ is still toward the bottom right so theoretically minusing half the canvas width/height then adding the offsets X and Y. this should be working but at my map coordinate -4000,-4000 i get ~-3620,-3295 and at +4000,+4000 I get 3500,3500. (The reason why the canvas 0,0 is not where the ship is , is to make the ship in the middle of the screen)
If you have questions about anything based on code that needs to be supplied please ask via comment . Please note if you have problems with the format of the code supplied I have nothing to say about it . all I need is the click function working on the canvas model i set up in cartesian format.
ps: jQuery is not a solution its a problem please use vanilla js.

I found out why it was off , my canvas has a offset of 90 px and 50 px as well as the main problem that the canvas is only 90% of its origonal size (also in css). If anyone can give me help for how to adjust to these issues please reply in comment . until then I beleieve I have solved my own issue .

Related

How to find out if an object is touching another object in JS

(excuse my bad english, I am 13 years old)
alright, even though i just joined stackoverflow today, I have been coding for a while. I'm trying to make a simple game (and may make it something bigger later) and I want the hammer (the player sprite) not be able to go through the box toward the middle of the screen, but I don't know how. here is what I have:
var sq = document.getElementById("box");
var posX = 0;
var posY = 0;
var rot = "rotate(0deg)";
var id = null;
function move(object, pixels, xa){
if(xa == true) {
posX+=pixels;
object.style.left = posX + 'px';
}else{
posY+=pixels;
object.style.top = posY + 'px';
}
}
OBJect.style.left = "200px";
OBJect.style.top = "200px";
document.addEventListener('keydown', logKey);
function logKey(e) {
if (`${e.code}` == "ArrowRight") {
rot = "rotate(90deg)";
sq.style.transform = rot;
move(sq, 5, true);
if(posX > 470){
posX = 5;
}
}
if (`${e.code}` == "ArrowLeft") {
rot = "rotate(270deg)";
sq.style.transform = rot;
move(sq, -5, true);
if(posX < 0){
posX = 465;
}
}
if (`${e.code}` == "ArrowDown") {
rot = "rotate(180deg)";
sq.style.transform = rot;
move(sq, 5, false);
if(posY > 465){
posY = 5;
}
}
if (`${e.code}` == "ArrowUp") {
rot = "rotate(0deg)";
sq.style.transform = rot;
move(sq, -5, false);
if(posY < 0){
posY = 470;
}
}
}
setInterval(function(){
xaxis.innerHTML = "x: " + posX;
yaxis.innerHTML = "y: " + posY;
rotate.innerHTML = rot;
},1);
#myContainer {
width: 500px;
height: 500px;
position: relative;
background: black;
outline: red solid 10px;
}
#box {
width: 30px;
height: 30px;
position: absolute;
background-color: gray;
}
#OBJect {
width: 30px;
height: 30px;
position: absolute;
background-color: gray;
}
<!DOCTYPE html>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<html>
<style>
/*style would go here/*
</style>
<body>
<div id="myContainer">
<img src="https://cdn.glitch.com/7f9c2ae2-9b45-42a1-a387-380de7f5d3bd%2Fhammer.png?v=1615308683807" alt="hammer" id="box">
<div id="OBJect"></div>
</div>
<br><br>
<div id="xaxis"></div>
<div id="yaxis"></div>
<div id="rotate"></div>
<script>
//script would go here
</script>
</body>
</html>
yeah, yeah, I know. I could definetly improve, but I only know basic things,and some of these from either Stackoverflow or w3schools, and yes, I know there is a lot of solved answers with this problem, but I get confused by them because they are too complicated for me. I just request easy, simple code to understand (if it isn't, please label things so I know).
sorry if this is too much, I just need help :/
so to do this you need to know the 4 corners of each element then you need to check if the other object is within the players bounds
they're both rectangles and to be touching 1 of these statements have to be true
the top left corner of the square is to the right of the top left corner of the player and the top left corner of the square is to the left of the top right corner of the player while the top left corner of the square is less than the y of the top side and higher than the y of the bottom side
the top right corner of the square is to the left of the top right corner of the player and the top right corner of the square is to the right of the top left corner of the player while the top right corner of the square is less than the y of the top side and higher than the y of the bottom side
same as 1 & 2 but with the bottom left and right corners
if one of the corners has the same x and y as a corner on the other object or if the position of one object is the same as the other
a second way to do this is using Pythagoras theorem to get the distance between two points
a^2 + b^2 = c^2
how that would look in code
let c = Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(x2, y2), 2))
this is a little tricky but the distance between two points is a triangle in a sense so the distance on x and y form the two legs then to get the actual distance we need the hypotenuse which is what Pythagoras theorem gets us. So to get a and b we need to get the absolute value of the first minus second x and y to get the distance on x and y then we square them with Math.pow() and add them but that gives us c^2 and we just want c so we get the square root with Math.sqrt() which gives us the distance then the one other thing we have to do is get the center of the objects to use as a and b. Since you're working with javascript on a webpage the X and Y values are for the top left corner so you need to add half the width and height to x and y appropriately to get the center of the object, then you can use the distance formula. the only problem with this approach is that it works best for circles because every point around the edge is equally distant from the center which isn't true for a square.
So personally I would use the first method, you can get the other corners by using width and height to add to the position of the top left corner to get the other positions. it's more code but more precise than distance because you're checking the bounds of each object rather than checking distance from center
I also joined today and I am 12. since the hammer is an img, you can use css:
I think your question was that you want to move the hammer, if yes:
var left = 0; /*left and top can be edited in your code but*/
var top = 0;/*you have to reset them using the code below*/
var hammer = document.querySelector('#hammer');
hammer.style.left = left + 'px';
hammer.style.top = top + 'px';
#hammer {
position: absolute;
left: 0px;
right: 0px;
}/*set left and right via js*/
<!--just add an id like #hammer to the hammer-->

arranging <div>s around a polygon (HTML, css, .ejs)

I want to arrange some rectangular div components around a regular polygon. Basically one of the long sides of the divs will be coincident with a line segment around the polygon.
In the final code, I'll use .ejs (since the number of sides of the polygon is dynamic, 3-10 sides). In my "quick and dirty" testing I'm doing a triangle in just HTML and CSS to get the math right.
I have a "very close" solution already and am wondering how to get it "exact" and am also wondering why my geometry intuition is so far off.
HTML and CSS:
div {
position: absolute;
left: 200px;
top: 200px;
width: 80px;
height: 40px;
background-color: skyblue;
}
.rotatedA {
transform: translateY(-60px) translateX(-35px) rotate(300deg);
background-color: blue;
}
.rotatedB {
transform: translateY(-60px) translateX(35px) rotate(60deg);
background-color: red;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>title</title>
<link rel="stylesheet" href="basic.css">
</head>
<body>
<div>Normal</div>
<div class="rotatedA">Rotated</div>
<div class="rotatedB">Rotated</div>
</body>
</html>
The first attempt I rotated "A" by 60 and "B" by -60 and did a translateY equal to the div height. When that did not work I played around with it.
On this last attempt (close but not perfect since the rotations won't give an integer) it seems like the Y adjustment is 1.5x (item height + cos(60)) but the X adjustment is 1/2 of sin(60) (I don't understand why).
Since my results aren't going to be an integer number of pixels what is the correct way to do this? Also, I don't understand why my geometry is so off (I could understand sin(60) but 1/2(sin(60)) doesn't make sense to me
Here's a mathematical way; the number and dimensions are read by the script, then the divs are arranged accordingly. I also made sure that the wrapper container has the correct dimensions so it can be used with other elements:
function arrange(wrapper) {
wrapper.style.position = "relative";
const rects = Array.from(wrapper.children);
const n = rects.length;
/* dimensions of a rectangle */
const bb = rects[0].getBoundingClientRect();
const a = bb.width;
const h = bb.height;
/* incircle radius of regular polygon */
const r = a * 0.5 / Math.tan(Math.PI / n);
/* radius of outer circle */
const bigR = Math.sqrt((r + h) * (r + h) + a * a / 4);
rects.forEach((rect, i) => {
const angle = i * (360 / n);
if (angle) rect.style.transform = `rotate(${angle}deg)`;
rect.style.position = angle ? "absolute" : "relative";
rect.style.marginBottom = bigR + r + "px";
rect.style.transformOrigin = `${a/2}px ${-r}px`;
rect.style.left = bigR - a / 2 + "px";
rect.style.top = bigR + r + "px";
});
if (window.getComputedStyle(wrapper).display == "inline-block")
wrapper.style.width = 2 * bigR + "px";
}
arrange(document.querySelector('#polygon'));
#polygon {
border: 1px solid black;
display: inline-block;
}
#polygon div {
width: 80px;
height: 20px;
background-color: skyblue;
text-align: center;
padding: 5px;
}
<div id="polygon">
<div>Normal</div>
<div>Rotated</div>
<div>Rotated</div>
<div>Rotated</div>
<div>Rotated</div>
<div>Rotated</div>
<div>Rotated</div>
</div>
The basic idea is to
calculate the in-circle's radius of the polygon based on the width of a rectangle
set transform-origin accordingly centered and above the first rectangle
arrange the others by rotating them
(do more calculations so the wrapper element encompasses everything exactly)

How get real positions of element in javascript

I want to get real X and Y position of my styled elements in javascript ( or jquery short code).
var offset = obj.offset();
ox=offset['left'];
oy=offset['top'];
px=parseInt(obj.css('padding-left')); // padding left
py=parseInt(obj.css('padding-top')); // padding top
bx=parseInt(obj.css('border-width') ); // stroke value
ox=ox+px+bx;
oy=oy+py+bx;
But this codes sometimes not work..
when scrool top or scroll left change im not get real position :(
please help me..
You don't have to use offsets. Use the modern getBoundingClientRect function:
function getPosition( element ) {
var rect = element.getBoundingClientRect();
return {
x: rect.left,
y: rect.top
};
}
You could then use the above function like this:
var element = document.getElementById( 'myElement' );
var pos = getPosition( el );
// Alert position in X axis
alert( pos.x );
// Alert position in Y axis
alert( pos.y );
Works in all browsers 🙂
EDIT: If you want the position of the element with respect to page scroll, just add document.body.scrollTop to Y position and document.body.scrollLeft to X position.
You'll want the offset from the element relative to the document. You have to keep in mind that styles like padding, margin and border can greatly affect the result of the offset. You might want to calculate those on or off the offset.
Finally you need to check if you are using box-sizing, which pushing padding and borders to the inside (with the most common version box-sizing: border-box;);
document.getElementById('cloud').offsetLeft; //offsetTop
Just debug with the other styles (adding/subtracting) until you get the real offset. I mostly test if the offset is correct by making a screenshot (or using the OS X selective-screenshot function) to see if the offset is correctly calculated (counting pixels) with the other styles.
Here's a little example:
CSS
#cloud {
height: 500px;
width: 500px;
margin: 20px auto;
border: 1px dotted #CCC;
}
HTML
<body>
<div id="cloud">
<div id="centerBox"></div>
</div>
</body>
JavaScript
'use strict';
console.log(document.getElementById('cloud').offsetLeft);
console.log(document.getElementById('cloud').offsetTop);
Ouput
main.js: 1 >>> 372
main.js: 2 >>> 20
Funny thing here, you can easily test if the offsetLeft works because of the margin: 20px auto;. If you resize the window it will also have a different offsetLeft.
This function gives you the exact position of an element without padding or margin or border:
function getElementRec(element) {
let rec = { x: 0, y: 0, width: 0, height: 0 };
let computedStyle = getComputedStyle(element);
rec.width = element.clientWidth;
rec.width -= parseFloat(computedStyle.paddingLeft);
rec.width -= parseFloat(computedStyle.paddingRight);
rec.height = element.clientHeight;
rec.height -= parseFloat(computedStyle.paddingTop);
rec.height -= parseFloat(computedStyle.paddingBottom);
let boundingRect = element.getBoundingClientRect();
rec.x = boundingRect.left;
rec.x += parseFloat(computedStyle.paddingLeft);
rec.x += parseFloat(computedStyle.borderLeft);
rec.y = boundingRect.top;
rec.y += parseFloat(computedStyle.paddingTop);
rec.y += parseFloat(computedStyle.borderTop);
return rec;
}
var offset = obj.offset();
ox=offset['left']; = document.getElementById('Mypicture').offsetLeft;
oy=offset['top']; = document.getElementById('Mypicture').offsetTop;
px=parseInt(obj.css('padding-left')); // padding left
py=parseInt(obj.css('padding-top')); // padding top
bx=parseInt(obj.css('border-width') ); // stroke value
ox=ox+px+bx;
oy=oy+py+bx;
//Two codes get equal value; But Not Real clientX ClientY positions..
// My html:
<div id="container">
<img id="Mypicture" src="myimage.jpg" alt="The source image for paint"/>
</div>
//My css :
#Mypicture{
margin:100px;
padding:20px;
border: 20px solid #cacaca;
}
#container {
display:block;
background:#ffaaff;
width: 550px;
padding:50px;
margin-left:300px;
}
// I want to get realx and realY value of Mypicture,on Document.
// All codes work Realx ( ox ) return true value But RealY (oy) nearly 10px false;

Javascript Image Pan / Zoom

By combining some CSS and Jquery UI / draggable I have created the ability to pan an image and with a little extra JS you can now zoom the image.
The problem I am having is that, if you zoom in the image's top left corner is fixed, as you would expect. What I would like is for the image to stay central (based on the current pan) so that the middle of the image stays in the middle of the container whilst getting larger.
I have written some code for this but doesn't work, I expect my maths is wrong. Could anyone help?
I want it to work like this does. When you scroll into an image it keeps the image centered based on the current pan rather than zooming out from the corner.
HTML:
<div id="creator_container" style="position: relative; width: 300px; height: 400px; overflow: hidden;">
<img src="/images/test.gif" class="user_image" width="300" style="cursor: move; position: absolute; left: 0; top: 0;">
</div>
Javascript:
$("#_popup_creator .user_image").bind('mousewheel', function(event, delta) {
zoomPercentage += delta;
$(this).css('width',zoomPercentage+'%');
$(this).css('height',zoomPercentage+'%');
var widthOffset = (($(this).width() - $(this).parent().width()) / 2);
$(this).css('left', $(this).position().left - widthOffset);
});
Long story short, you need to make a transform matrix to scale by the same amount as the image and then transform the image's position using that matrix. If that explanation is complete greek to you, look up "image transforms" and "matrix math".
The beginning of this page is a pretty good resource to start with even though it's a different programming language:
http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/geom/Matrix.html
Anyway, I've implemented those methods in some projects of my own. Here's the zoom in function from something I wrote that functions the way you want:
function zoomIn(event) {
var prevS = scale;
scale += .1;
$(map).css({width: (baseSizeHor * scale) + "px", height: (baseSizeVer * scale) + "px"});
//scale from middle of screen
var point = new Vector.create([posX - $(viewer).width() / 2, posY - $(viewer).height() / 2, 1]);
var mat = Matrix.I(3);
mat = scaleMatrix(mat, scale / prevS, scale / prevS);
point = transformPoint(mat, point);
//http://stackoverflow.com/questions/1248081/get-the-browser-viewport-dimensions-with-javascript
posX = point.e(1) + $(viewer).width() / 2;
posY = point.e(2) + $(viewer).height() / 2;
$(map).css({left: posX, top: posY});
return false;//prevent drag image out of browser
}
Note the commands "new Vector.create()" and "Matrix.I(3)". Those come from the JavaScript vector/matrix math library http://sylvester.jcoglan.com/
Then note "transformPoint()". That's one of the functions from that ActionScript link (plus hints on http://wxs.ca/js3d/) that I implemented using sylvester.js
For the full set of functions I wrote:
function translateMatrix(mat, dx, dy) {
var m = Matrix.create([
[1,0,dx],
[0,1,dy],
[0,0,1]
]);
return m.multiply(mat);
}
function rotateMatrix(mat, rad) {
var c = Math.cos(rad);
var s = Math.sin(rad);
var m = Matrix.create([
[c,-s,0],
[s,c,0],
[0,0,1]
]);
return m.multiply(mat);
}
function scaleMatrix(mat, sx, sy) {
var m = Matrix.create([
[sx,0,0],
[0,sy,0],
[0,0,1]
]);
return m.multiply(mat);
}
function transformPoint(mat, vec) {
return mat.multiply(vec);
}

Finding the absolute position of the paper/canvas in the Raphael JavaScript library

How can I find the absolute position of the paper/canvas when using the Raphael JavaScript library?
For instance, suppose the minimal working example is as follows:
<html>
<head>
<script type="text/javascript" src="raphael.js"></script>
<script>
window.onload = function() {
var size = 600;
var paper = new Raphael(document.getElementById("canvas_container"),
size, size);
var c = paper.circle(100, 100, 50).attr({fill: "#00f"});
var x = 0; // get the paper's absolute x coordinate
var y = 0; // get the paper's absolute y coordinate
document.getElementById("coordinates").innerHTML = x + " " + y;
};
</script>
<style type="text/css">
#canvas_container {
width: 600px;
margin-left: auto;
margin-right: auto;
border: 1px solid #aaa;
}
</style>
</head>
<body>
<p id="coordinates"></p>
<div id="canvas_container"></div>
</body>
</html>
How do I find the absolute x and y coordinates of the paper from the top of the page without knowing the ID of the div that it lives in? (In the example, I can know that, but my actual situation makes knowing the div ID much more complicated.)
OK. I see what it should be now:
var x = this.paper.canvas.offsetLeft;
var y = this.paper.canvas.offsetTop;
That seems to work correctly on both IE 8.0 and Chrome 9.0.
To get the abs position of your Raphael paper, you can use jQuery:
$(paper.canvas).offset()
The accepted solution doesn't worked for me, it gives me x = -1 and y = -1, so if someone has the same problem I solved it this way:
var x = paper.canvas.parentNode.offsetLeft;
var y = paper.canvas.parentNode.offsetTop;

Categories