Is there a way in javascript to check whether the mouse position currently lies within an element's bounds?
Is there a function you can suggest or a method thats quick?
if ( document.mouse.x > ele.offsetLeft && document.mouse.x < ele.offsetRight ...check y bounds)
{
return true;
}
else return false;
You could store the boundary coordinates and the mouse coordinates. This would let you check it any time you want.
var coords = [0,0];
$(document).mousemove(function(e){
var C = coords; // one global lookup
C[0] = e.pageX;
C[1] = e.pageY;
});
var box_area = {x1:0, y1:0, x2:0, y2:0};
var box = $('#box');
function store_boundary() {
var B = box, O = B.offset();
box_area = {
x1: O.left,
y1: O.top,
x2: O.left + B.width(),
y2: O.top + B.height()
};
}
store_boundary();
function is_mouse_in_area() {
var C = coords, B = box_area;
if (C[0] >= B.x1 && C[0] <= B.x2) {
if (C[1] >= B.y1 && C[1] <= B.y2) {
return true;
}
}
return false;
};
I would like to have given you an answer without jQuery, but I think .offset() (left/top coordinates relative to the document) is really well done and very well tested. You could write your own, however, that totals up offsetLeft and offsetTop up to document. For that matter, you could also replace $.mousemove() with:
document.addEventListener('mousemove',function(e){ ... },false);
One last thing: if you reflow the page, you need to call store_boundary() again.
var t = $("#element");
var mouseX = event.clientX + document.body.scrollLeft;
var mouseY = event.clientY + document.body.scrollTop;
if (mouseX >= t.offset().left && mouseX <= t.offset().left + t.width()
&& mouseY >= t.offset().top && mouseY <= t.offset().top + t.height()) {
return true;
}
Related
I'm making a small game with one player and blocks that builds up the environment. The problem I'm having is knowing the difference between when the player hits the ground (the top of a block), and hitting a wall (the side of the block).
So far the player can walk on the ground just fine, but when he meets a wall, he immediately jumps to the top of that block.
This is my collision detector:
function collisionDetector(){
if(myPlayer.y + myPlayer.h > c.height){ //Bottom of the canvas
myPlayer.vy = 0;
myPlayer.ay = 0;
myPlayer.y = c.height - myPlayer.h;
myPlayer.onGround = true;
console.log(myPlayer.y + myPlayer.h, c.height);
}
if(myPlayer.x + myPlayer.w >= c.width){ //right side of canvas
myPlayer.x = c.width - myPlayer.w;
myPlayer.vx = 0;
}
if(myPlayer.x <= 0){ //Left side of canvas
myPlayer.x = 0;
myPlayer.vx = 0;
}
function hitTest(a,b){ //hitTest between two objects
if(a.y + a.h > b.y && a.y < b.y + b.h && a.x + a.w > b.x && a.x < b.x + b.w){
return true;
}
}
for(var i = 0; i < blocks.length; i++){ //Loop through blocks
if(hitTest(myPlayer, blocks[i])){ //If it touches a block
myPlayer.y = blocks[i].y - myPlayer.h;
myPlayer.onGround = true; //onGround = ready to jump
}
}
}
I realized that I'm setting the players y pos to be on top of what ever block it hits, but I cannot figure out a solution to this problem. Can anyone help me or at least lead me in the right direction? Thanks!
(Let me know if you need more of the code)
PS: the player is just a head. No body hiding behind the blocks.
So basically, what you need to do is to check collision between many points in the player.
In the snippet you can show many points represented in the player.
Bottom almost-left and almost-right (in blue), check against below blocks. They are not fully left or right, in order to prevent a race condition which will allow the player to climb walls. In that case, if the player is pushing against a wall and jumping, the collider will detect both side collision and bottom collision as true, then the player will quickly move to the top until there are no more blocks.
Left and right points (in black), check against edges of the blocks. This is just a point instead of two like the bottom edge, because we don't need more for this particular case. One more for each side could be easily added to get a better detection.
Top point (in red) checks against the top blocks. This is in the middle in order to allow the player a more easy way to tranverse the map. If this is not needed, you would need to add one more point like in the bottom edge (but never reaching the far edge, because that will generate a race condition).
So in summary, to have a good collision detection based on points (instead of raycasts), you need to detect the player like if it where a rounded shape, in order to prevent strange behaviours.
You can player around with the map layout by altering the layout variable. 0's are empty space, 1's are brown blocks and 2's are green blocks.
The collisionDetector fuction has comments to understand what's going on.
Also I have added a jump feature since I understand you would need that as well.
const c = document.getElementById('canvas');
c.width = window.innerWidth;
c.height = window.innerHeight;
const ctx = c.getContext('2d');
// map layout
const layout =
`000000001
001000001
000000101
100110111
222222222`;
// convert layout to blocks
const blocks = [...layout].reduce((a, c, i) => {
if (i === 0 || c === "\n") a.push([]);
if (c === "\n") return a;
const y = a.length - 1;
const row = a[y];
const x = row.length;
row.push({x: x * 32, y: y * 32, t:c, w:32, h:32});
return a;
}, []).reduce((a, c) => a.concat(c), []);
// player starting position
const myPlayer = {x: 32*1.5, y: 0, h: 32, w: 16, onGround: true};
const gravity = -1;
let pkl = 0, pkr = 0;
let pvely = 0;
function render() {
// player logic
const pvelx = pkr + pkl;
const speed = 2;
myPlayer.x += pvelx * speed;
myPlayer.y -= pvely;
if (pvely > -2) pvely += gravity;
const debugColliders = collisionDetector();
ctx.clearRect(0, 0, c.width, c.height);
// player render
ctx.fillStyle = '#FFD9B3';
ctx.fillRect(myPlayer.x, myPlayer.y, myPlayer.w, myPlayer.h);
renderLayout();
debugColliders();
window.requestAnimationFrame(render);
}
function renderLayout() {
const colors = {'1': '#A3825F', '2': '#7FAC72'}
blocks.forEach(b => {
if (+b.t > 0) {
ctx.fillStyle = colors[b.t];
ctx.fillRect(b.x, b.y, b.w, b.h);
}
});
}
window.addEventListener('keydown', e => {
if (e.key == 'ArrowRight') {
pkr = 1;
e.preventDefault();
} else if (e.key == 'ArrowLeft') {
pkl = -1;
e.preventDefault();
} else if (e.key == 'ArrowUp') {
if (myPlayer.onGround)
pvely = 8;
myPlayer.onGround = false;
e.preventDefault();
}
});
window.addEventListener('keyup', e => {
if (e.key == 'ArrowRight') {
pkr = 0;
} else if (e.key == 'ArrowLeft') {
pkl = 0;
}
});
function collisionDetector(){
const p = myPlayer;
const playerTop = p.y;
const playerLeft = p.x;
const playerRight = playerLeft + p.w;
const playerBottom = playerTop + p.h;
const playerHalfLeft = playerLeft + p.w * .25;
const playerHalfRight = playerLeft + p.w * .75;
const playerHMiddle = playerLeft + p.w * .5;
const playerVMiddle = playerTop + p.h * .5;
if(playerBottom > c.height){ //Bottom of the canvas
p.vy = 0;
p.ay = 0;
p.y = c.height - p.h;
p.onGround = true;
}
if(playerRight >= c.width){ //right side of canvas
p.x = c.width - p.w;
p.vx = 0;
}
if(playerLeft <= 0){ //Left side of canvas
p.x = 0;
p.vx = 0;
}
blocks.forEach(b => { //Loop through blocks
if (b.t === "0") return; // If not collidable, do nothing
const blockTop = b.y;
const blockLeft = b.x;
const blockRight = blockLeft + b.w;
const blockBottom = b.y + b.h;
// Player bottom against block top
if ((playerBottom > blockTop && playerBottom < blockBottom) && // If player bottom is going through block top but is above block bottom.
((playerHalfLeft > blockLeft && playerHalfLeft < blockRight) || // If player left is inside block horizontal bounds
(playerHalfRight > blockLeft && playerHalfRight < blockRight))) { // Or if player right is inside block horizontal bounds
p.y = blockTop - p.h;
p.onGround = true;
}
// Player top against block bottom
if ((playerTop < blockBottom && playerTop > blockTop) && // If player top is going through block bottom but is below block top.
((playerHMiddle > blockLeft && playerHMiddle < blockRight))) { // If player hmiddle is inside block horizontal bounds
p.y = blockBottom;
p.onGround = false;
}
// Player right against block left, or player left against block right
if (playerVMiddle > blockTop && playerVMiddle < blockBottom) { // If player vertical-middle is inside block vertical bounds
if ((playerRight > blockLeft && playerRight < blockRight)) { // If player vmiddle-right goes through block-left
p.x = blockLeft - p.w;
} else if ((playerLeft < blockRight && playerRight > blockLeft)) { // If player vmiddle-left goes through block-right
p.x = blockRight;
}
}
});
return function debug() {
ctx.fillStyle = 'black';
ctx.fillRect(playerLeft, playerVMiddle, 1, 1);
ctx.fillRect(playerRight, playerVMiddle, 1, 1);
ctx.fillStyle = 'red';
ctx.fillRect(playerHMiddle, playerTop, 1, 1);
ctx.fillStyle = 'blue';
ctx.fillRect(playerHalfLeft, playerBottom, 1, 1);
ctx.fillRect(playerHalfRight, playerBottom, 1, 1);
}
}
window.requestAnimationFrame(render);
html, body{ width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; }
canvas { background: #7AC9F9; display: block; }
<canvas id="canvas"></canvas>
Introduce a block[i].type attribute. For instance if block[i].type=='floor' then make player stay on floor. If for another instance block[i].type=='wall' then make it stop moving through the wall. When block[i].type=='brick' or square or block or whatever, them it's a mixture of two.
Another part to be edited is when you check the collisions. What if you have only one-direction collision? What I am saying is maybe use or instead of and in this part if(a.y + a.h > b.y && a.y < b.y + b.h && a.x + a.w > b.x && a.x < b.x + b.w){
Also you could check each collision separately, like
function hitTest(a,b){ //hitTest between two objects
var collisions = {up: false, down: false, left: false, right: false};
collisions.up = (a.y + a.h > b.y ) || collisions.up
collisions.down = (a.y < b.y + b.h ) ||collisions.down
collisions.right = ( a.x + a.w > b.x) || collisions.right
collisions.left = (a.x < b.x + b.w) || collisions.left
return collisions
}
var escapeFrom = {
down: function(player, block){
player.y = block.y + block.h;
player.onGround = true; //onGround = ready to jump
},
up: function(player, block){
// you logic to escape from hitting the ceiling
},
// and for the next 2
left: function(player, block) {},
right: function(player, block){}
}
// Now here you check whether your player hits blocks
for(var i = 0; i < blocks.length; i++){ //Loop through blocks
cls = hitTest(myPlayer, blocks[i]) //If it touches a block
Object.keys(cls).map(function(direction, ind){
if (cls[direction]){
// call escape from function to escape collision
escapeFrom[direction](myPlayer, blocks[i]);
}
})
}
This is highly unoptimized, the whole your code is unoptimized, but at least it can help to move further.
I'm using a drag function (not jquery UI, I don't want Jquery UI) on a div with a class, but I have more div with the same class, How can I intercept the click on this div
Here my code:
$(function(){
var dragging = false;
var iX, iY;
// how can I intercept $(this, ".marker") ??
$(".marker").mousedown(function(e) {
dragging = true;
iX = e.clientX - this.offsetLeft;
iY = e.clientY - this.offsetTop;
this.setCapture && this.setCapture();
return false;
});
document.onmousemove = function(e) {
if (dragging) {
var e = e || window.event;
var oX = e.clientX - iX;
var oY = e.clientY - iY;
// how can I find the ID related to this div ?
$('.marker').css({"left":oX + "px", "top":oY + "px"});
return false;
}
};
$(document).mouseup(function(e) {
dragging = false;
// $(".marker")[0].releaseCapture();
e.cancelBubble = true;
})
})
And here the original codePen https://codepen.io/Jonh/pen/jgyLB
Here the JS that create the div, they are created by JS dynamically
for (i = 0; i < theFeat.length; i++) {
markerText = theFeat[i].value;
mk_totalHeight += 50;
$('<div>', { id: 'd' + i, style:"top:" + mk_totalHeight + "px;"}).addClass('marker').appendTo('#map');
$('<div>', { id: i, style:"background-image:url('../../../symbols/marker" + (i + 1) + ".png');" }).addClass('markerIcon').appendTo('#d'+ i);
$('<span>', { id: 's' + i, text: markerText}).appendTo('#d' + i);
} // end for loop
It's useful if you have just one div but how can I implement that on a class that is on a various div, I have to copy the function and add as a select the ID? (the max allowed div are 4, so it will be #d0, #d1, #d2, #d3)
So one of your listeners,
// how can I intercept $(this, ".marker") ??
$(".marker").mousedown(function(e) {
dragging = true;
iX = e.clientX - this.offsetLeft;
iY = e.clientY - this.offsetTop;
this.setCapture && this.setCapture();
return false;
});
...won't, in fact, work as you expect. Because the .marker elements are being created dynamically, event listeners on them will fail. Rather, listen at the nearest parent node:
// Try something like this:
$("#map").on("mousedown", ".marker", function(e){
// Either of the following should get to the element that
// triggered the mousedown
var target = $(this);
// OR YOU CAN USE
var target = $(e.currentTarget);
// ... whatever other processing you need...
});
After the comment of #Snowmonkey I notice that the problem was on document.onmousemove where do you change the CSS property, this is my solution.
I created a var to get the selected div
$(".marker").mousedown(function(e) {
dragging = true;
mdiv = $(this);
iX = e.clientX - this.offsetLeft;
iY = e.clientY - this.offsetTop;
this.setCapture && this.setCapture();
return false;
});
Then on the onmousemove function, I have the target stored on a var and I can get the ID
document.onmousemove = function(e) {
if (dragging) {
var e = e || window.event;
var oX = e.clientX - iX;
var oY = e.clientY - iY;
$("#" + mdiv.attr('id')).css({"left":oX + "px", "top":oY + "px"});
return false;
}
};
I have a remote control pen as cursor (hardware).
External hardware via SDK to Javascript i receive a cursor.
Where i get x, x, y, y position and click command.
But, How to submit click on that position?
(no matter whatever Element is there on that position, i need to submit the click on that position)
if (_label == rs.cursor.GestureType.CURSOR_CLICK) {
// I have: x, x, y, y
/*
Following method works:
but i have too many buttons, how to apply dynamic click without tracking which element?
var myButton = document.getElementById("mediaplayer");
var rect = myButton.getBoundingClientRect();
if (x >= rect.left &&
x <= rect.right &&
y >= rect.top &&
y <= rect.bottom){
myButton.click();
}
*/
}
EDIT:
//
// 1 - Find Active DIV/MAP/Container?
//
var layerID = $('#layoutmain').attr('usemap');
if (_label == rs.cursor.GestureType.CURSOR_CLICK) {
//
// 2 - Loop each buttons of that container
//
$('#' + layerID).find('area').each(function(){
var onclick_function = $(this).attr('onclick') ;
var coords = $(this).attr('coords');
coords = coords.split(' ').join('');
var coords_array = coords.split(',');
console.log('>>> we have: ',
onclick_function ,
coords,
coords_array);
var rectleft = coords_array[0];
var recttop = coords_array[1];
var rectright = coords_array[2];
var rectbottom = coords_array[3];
//
// 3 - Match the RANGE of buttons with the coordinates
//
if (x >= rectleft && y >= recttop &&
x <= rectright && y <= rectbottom){
$(this).trigger('click');
}
});
}
Method 1: When the CURSOR_CLICK event is triggered, iterate all the clickable buttons and check to which button the click is "related" to.
$buttons = $('.clickable-button');
var rect;
if (_label == rs.cursor.GestureType.CURSOR_CLICK) {
$buttons.each(function(){
rect = $(this).getBoundingClientRect();
if (x >= rect.left &&
x <= rect.right &&
y >= rect.top &&
y <= rect.bottom){
$(this).click();
}
});
}
Method 2: Create an array with all the buttons on the page and their cords.
When the click event is being triggered, just compare the cords.
Advantage - Less time is being spent in the loop.
I have this code for a math game. I have a function for a button which defines the location and then draws the button specified in the callback.
The problem is I can only create one of these, since event listeners are, for some baffling reason, referred to by the function they execute, rather than by some other string name.
Unfortunately this means writing a lot of repetitive code instead. Each function called by the eventListener would only differ by the name of the function for that particular button. How do people work around this problem? Is there no way to name the function (see: functAnim & functClick) using a string?
function factsButton(x, y, doFunction, contentCallback)
{
var getID = document.getElementById("canvas_1");
if (getID.getContext)
{
var ctx = getID.getContext("2d");
var btnW = 100;
var btnH = 100;
var cx = x - btnW/2;
var cy = y - btnH/2;
var left = cx;
var right = cx + btnW;
var top = cy;
var bottom = cy + btnH;
function functAnim(event)
{
var mousePos = getMousePos(getID, event);
var rect = getID.getBoundingClientRect();
var mouseX = mousePos.x;
var mouseY = mousePos.y;
if (mouseX >= left
&& mouseX <= right
&& mouseY >= top
&& mouseY <= bottom)
{
contentCallback(cx, cy, btnW, btnH, true);
}
else
{
contentCallback(cx, cy, btnW, btnH, false);
}
}
function functClick(event)
{
var mousePos = getMousePos(getID, event);
var rect = getID.getBoundingClientRect();
var clickX = mousePos.x;
var clickY = mousePos.y;
if (clickX >= left
&& clickX <= right
&& clickY >= top
&& clickY <= bottom)
{
doFunction();
getID.removeEventListener("mousemove", functAnim, false);
getID.removeEventListener("click", functClick, false);
}
}
contentCallback(cx, cy, btnW, btnH, false);
getID.addEventListener("click", functClick, false);
getID.addEventListener("mousemove", functAnim, false);
}
}
EDIT:
For some reason it wasn't as clear as to what I wanted. I'm not wanting to call the functions by string, but rather create them with dynamic names. For instance, since I need 4 buttons created, I would like to call my function like this: factsButton(50, 50, addClicked, "add"); With "add", instead of "functClick" they could be called "addClick" and "addAnim" or say it was "sub" instead of "add", it would create those events and functions as "subAnim" and "subClick". That way I get 8 separate event listeners each with different "names". Otherwise I end up with only 2 despite having 4 buttons.
The wording of your question confuses me for some reason :-\
Anyway,
If you want to call a function by string instead of by function pointer, add your functions as elements in a javascript object.
// a javascript object containing function definitions
var myFunctions={
functAnim: function(event)
{
var mousePos = getMousePos(getID, event);
var rect = getID.getBoundingClientRect();
var mouseX = mousePos.x;
var mouseY = mousePos.y;
if (mouseX >= left
&& mouseX <= right
&& mouseY >= top
&& mouseY <= bottom)
{
contentCallback(cx, cy, btnW, btnH, true);
}
else
{
contentCallback(cx, cy, btnW, btnH, false);
}
},
functClick: function(event)
{
var mousePos = getMousePos(getID, event);
var rect = getID.getBoundingClientRect();
var clickX = mousePos.x;
var clickY = mousePos.y;
if (clickX >= left
&& clickX <= right
&& clickY >= top
&& clickY <= bottom)
{
doFunction();
getID.removeEventListener("mousemove", functAnim, false);
getID.removeEventListener("click", functClick, false);
}
}
}
Then you can call your functions by string.
// useage
myFunctions["functAnim"](eventObject); // calls functAnim
myFunctions["functClick"](eventObject); // calls functClick
I am plotting an image of fixed width and height. I am allowing the user to click on the image - and storing the location (x-y coordinates) where the image was clicked. Here is a sample code:
<script language="JavaScript" type="text/JavaScript">
var posx; var posy;
function showP(e) {
// captures the mouse position
posx = 0; posy = 0;
if (!e) { var e = window.event; }
if (e.pageX || e.pageY) {
posx = e.pageX;
posy = e.pageY;
}
else if (e.clientX || e.clientY) {
posx = e.clientX;
posy = e.clientY;
}
alert('X mouse is: ' + posx + ' Y mouse is: ' + posy );
}
</script>
I am noticing that for a fixed point on the image, I am getting different X and Y coordinates on different browsers.
Can anyone tell why this is the case.
Thanks
you can check all data that you get in your event
var a = "";
for (var key in e){
if( typeof e[key]!='function' && typeof e[key] !='object' )
a += key+'='+ e[key]+'\n'
}
alert(a)
this constraction helps you compare margin and padding in browsers
var padding = parseInt($("#imgId").css("padding-top"));
I think you need investigate you code and styles, maybe you have some conflict with padding and margins that influences on you result.
Hope this way helps you.