Please see this fiddle: http://jsfiddle.net/ZWw3Z/5/
My code is:
p {
position: relative;
background-color: blue;
}
p:before {
content: '';
position: absolute;
left:100%;
width: 10px;
height: 100%;
background-color: red;
}
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate...</p>
I would like to trigger a click event only on the pseudo-element (the red bit). That is, I don't want the click event to be triggered on the blue bit.
This is not possible; pseudo-elements are not part of the DOM at all so you can't bind any events directly to them, you can only bind to their parent elements.
If you must have a click handler on the red region only, you have to make a child element, like a span, place it right after the opening <p> tag, apply styles to p span instead of p:before, and bind to it.
Actually, it is possible. You can check if the clicked position was outside of the element, since this will only happen if ::before or ::after was clicked.
This example only checks the element to the right but that should work in your case.
span = document.querySelector('span');
span.addEventListener('click', function (e) {
if (e.offsetX > span.offsetWidth) {
span.className = 'c2';
} else {
span.className = 'c1';
}
});
div { margin: 20px; }
span:after { content: 'AFTER'; position: absolute; }
span.c1 { background: yellow; }
span.c2:after { background: yellow; }
<div><span>ELEMENT</span></div>
JSFiddle
On modern browsers you can try with the pointer-events css property (but it leads to the impossibility to detect mouse events on the parent node):
p {
position: relative;
background-color: blue;
color:#ffffff;
padding:0px 10px;
pointer-events:none;
}
p::before {
content: attr(data-before);
margin-left:-10px;
margin-right:10px;
position: relative;
background-color: red;
padding:0px 10px;
pointer-events:auto;
}
When the event target is your "p" element, you know it is your "p:before".
If you still need to detect mouse events on the main p, you may consider the possibility to modify your HTML structure. You can add a span tag and the following style:
p span {
background:#393;
padding:0px 10px;
pointer-events:auto;
}
The event targets are now both the "span" and the "p:before" elements.
Example without jquery: http://jsfiddle.net/2nsptvcu/
Example with jquery: http://jsfiddle.net/0vygmnnb/
Here is the list of browsers supporting pointer-events: http://caniuse.com/#feat=pointer-events
Short Answer:
I did it.
I wrote a function for dynamic usage for all the little people out there...
Working example which displays on the page
Working example logging to the console
Long Answer:
...Still did it.
It took me awhile to do it, since a psuedo element is not really on the page. While some of the answers above work in SOME scenarios, they ALL fail to be both dynamic and work in a scenario in which an element is both unexpected in size and position(such as absolute positioned elements overlaying a portion of the parent element). Mine does not.
Usage:
//some element selector and a click event...plain js works here too
$("div").click(function() {
//returns an object {before: true/false, after: true/false}
psuedoClick(this);
//returns true/false
psuedoClick(this).before;
//returns true/false
psuedoClick(this).after;
});
How it works:
It grabs the height, width, top, and left positions(based on the position away from the edge of the window) of the parent element and grabs the height, width, top, and left positions(based on the edge of the parent container) and compares those values to determine where the psuedo element is on the screen.
It then compares where the mouse is. As long as the mouse is in the newly created variable range then it returns true.
Note:
It is wise to make the parent element RELATIVE positioned. If you have an absolute positioned psuedo element, this function will only work if it is positioned based on the parent's dimensions(so the parent has to be relative...maybe sticky or fixed would work too....I dont know).
Code:
function psuedoClick(parentElem) {
var beforeClicked,
afterClicked;
var parentLeft = parseInt(parentElem.getBoundingClientRect().left, 10),
parentTop = parseInt(parentElem.getBoundingClientRect().top, 10);
var parentWidth = parseInt(window.getComputedStyle(parentElem).width, 10),
parentHeight = parseInt(window.getComputedStyle(parentElem).height, 10);
var before = window.getComputedStyle(parentElem, ':before');
var beforeStart = parentLeft + (parseInt(before.getPropertyValue("left"), 10)),
beforeEnd = beforeStart + parseInt(before.width, 10);
var beforeYStart = parentTop + (parseInt(before.getPropertyValue("top"), 10)),
beforeYEnd = beforeYStart + parseInt(before.height, 10);
var after = window.getComputedStyle(parentElem, ':after');
var afterStart = parentLeft + (parseInt(after.getPropertyValue("left"), 10)),
afterEnd = afterStart + parseInt(after.width, 10);
var afterYStart = parentTop + (parseInt(after.getPropertyValue("top"), 10)),
afterYEnd = afterYStart + parseInt(after.height, 10);
var mouseX = event.clientX,
mouseY = event.clientY;
beforeClicked = (mouseX >= beforeStart && mouseX <= beforeEnd && mouseY >= beforeYStart && mouseY <= beforeYEnd ? true : false);
afterClicked = (mouseX >= afterStart && mouseX <= afterEnd && mouseY >= afterYStart && mouseY <= afterYEnd ? true : false);
return {
"before" : beforeClicked,
"after" : afterClicked
};
}
Support:
I dont know....it looks like ie is dumb and likes to return auto as a computed value sometimes. IT SEEMS TO WORK WELL IN ALL BROWSERS IF DIMENSIONS ARE SET IN CSS. So...set your height and width on your psuedo elements and only move them with top and left. I recommend using it on things that you are okay with it not working on. Like an animation or something. Chrome works...as usual.
My answer will work for anyone wanting to click a definitive area of the page. This worked for me on my absolutely-positioned :after
Thanks to this article, I realized (with jQuery) I can use e.pageY and e.pageX instead of worrying about e.offsetY/X and e.clientY/X issue between browsers.
Through my trial and error, I started to use the clientX and clientY mouse coordinates in the jQuery event object. These coordinates gave me the X and Y offset of the mouse relative to the top-left corner of the browser's view port. As I was reading the jQuery 1.4 Reference Guide by Karl Swedberg and Jonathan Chaffer, however, I saw that they often referred to the pageX and pageY coordinates. After checking the updated jQuery documentation, I saw that these were the coordinates standardized by jQuery; and, I saw that they gave me the X and Y offset of the mouse relative to the entire document (not just the view port).
I liked this event.pageY idea because it would always be the same, as it was relative to the document. I can compare it to my :after's parent element using offset(), which returns its X and Y also relative to the document.
Therefore, I can come up with a range of "clickable" region on the entire page that never changes.
Here's my demo on codepen.
or if too lazy for codepen, here's the JS:
* I only cared about the Y values for my example.
var box = $('.box');
// clickable range - never changes
var max = box.offset().top + box.outerHeight();
var min = max - 30; // 30 is the height of the :after
var checkRange = function(y) {
return (y >= min && y <= max);
}
box.click(function(e){
if ( checkRange(e.pageY) ) {
// do click action
box.toggleClass('toggle');
}
});
This works for me:
$('#element').click(function (e) {
if (e.offsetX > e.target.offsetLeft) {
// click on element
}
else{
// click on ::before element
}
});
This is edited answer by Fasoeu with latest CSS3 and JS ES6
Edited demo without using JQuery.
Shortest example of code:
<p><span>Some text</span></p>
p {
position: relative;
pointer-events: none;
}
p::before {
content: "";
position: absolute;
pointer-events: auto;
}
p span {
display: contents;
pointer-events: auto;
}
const all_p = Array.from(document.querySelectorAll('p'));
for (let p of all_p) {
p.addEventListener("click", listener, false);
};
Explanation:
pointer-events control detection of events, removing receiving events from target, but keep receiving from pseudo-elements make possible to click on ::before and ::after and you will always know what you are clicking on pseudo-element, however if you still need to click, you put all content in nested element (span in example), but because we don't want to apply any additional styles, display: contents; become very handy solution and it supported by most browsers. pointer-events: none; as already mentioned in original post also widely supported.
The JavaScript part also used widely supported Array.from and for...of, however they are not necessary to use in code.
Add condition in Click event to restrict the clickable area .
$('#thing').click(function(e) {
if (e.clientX > $(this).offset().left + 90 &&
e.clientY < $(this).offset().top + 10) {
// action when clicking on after-element
// your code here
}
});
DEMO
I solved this case with add pointer-events: none; at :after css
None of these answers are reliable, and mine wont be much more reliable.
Caveats aside, if you do get into the lucky scenario where the element you're trying to have clicked doesn't have padding (such that all of the "inner" space of the element is completely covered by sub-elements), then you can check the target of the click event against the container itself. If it matches, that means you've clicked a :before or :after element.
Obviously this would not be feasible with both types (before and after) however I have implemented it as a hack/speed fix and it is working very well, without a bunch of position checking, which may be inaccurate depending on about a million different factors.
I'll post the solution with:
offset() method to get the top-left corner info of the root element.
window.getComputedStyle() method to get the styles of the pseudo-element.
Please make sure the position property of the root element is specified as relative, and absolute for the pseudo-element.
The Fiddle demo is here.
Hope this will help.
Additional comment:
This question was posted 11 years ago, and already has an accepted answer.
The reason why I posted this solution despite the above fact is as follows.
I had struggled with the same desire to detect click event on pseudo-element.
The accepted answer ("adding a child element") does not satisfy my needs.
I've got the idea of the solution thanks to some posts on this thread.
I would be happy if someone with a similar problem could solve it with this post.
/**
* Click event listener.
*/
$(element).click((e) => {
// mouse cursor position
const mousePoint = { x: e.pageX, y: e.pageY };
if (isMouseOnPseudoElement(mousePoint, e.target, 'before')) {
console.log('[:before] pseudo-element was clicked');
} else if (isMouseOnPseudoElement(mousePoint, e.target, 'after')) {
console.log('[:after] pseudo-element was clicked');
}
});
/**
* Returns the info of whether the mouse cursor is over the pseudo-element.
*
* #param {JSON} point: coordinate of mouse cursor
* #param {DOMElement} element: root element
* #param {String} pseudoType: "before" or "after"
* #returns {JSON}: info of whether the mouse cursor is over the pseudo-element
*/
function isMouseOnPseudoElement(point, element, pseudoType = 'before') {
const pseudoRect = getPseudoElementRect(element, pseudoType);
return point.y >= pseudoRect.top
&& point.y <= pseudoRect.bottom
&& point.x >= pseudoRect.left
&& point.x <= pseudoRect.right;
}
/**
* Gets the rectangle info of the pseudo-element.
*
* #param {DOMElement} element: root element
* #param {String} pseudoType: "before" or "after"
* #returns {JSON}: rectangle info of the pseudo-element
*/
function getPseudoElementRect(element, pseudoType = 'before') {
// top-left info of the root element
const rootOffset = $(element).offset();
// style of the pseudo-element
const pseudoStyle = window.getComputedStyle(element, ':' + pseudoType);
const top = rootOffset.top + parseFloat(pseudoStyle.top);
const left = rootOffset.left + parseFloat(pseudoStyle.left);
const height = parseFloat(pseudoStyle.height);
const width = parseFloat(pseudoStyle.width);
const borderTop = parseFloat(pseudoStyle['border-top-width']);
const borderBottom = parseFloat(pseudoStyle['border-bottom-width']);
const borderLeft = parseFloat(pseudoStyle['border-left-width']);
const borderRight = parseFloat(pseudoStyle['border-right-width']);
// rounds the decimal numbers detected when zooming
return {
top: Math.round(top),
left: Math.round(left),
bottom: Math.round(top + height + borderTop + borderBottom),
right: Math.round(left + width + borderLeft + borderRight)
};
}
Without JQuery i used this for sidebar menu click detectionon pseudo plus icons:
HTML:
<ul>
<li>MENU ELEMENT</li>
<li>MENU ELEMENT</li>
<li>MENU ELEMENT</li>
</ul>
CSS:
ul { margin: 30px; }
li { display: flex; width: 300px; justify-content: space-between;}
li:after { content: ' +'}
li.c1 { background: red; }
li.c2:after { background: yellow; }
JS:
document.querySelectorAll("li").forEach(function (e) {
e.addEventListener('click', function(u) {
let linkWidth = this.offsetWidth;
let pseudoWidth = parseFloat(window.getComputedStyle(this, ':after').width);
const mouseX = u.offsetX;
if (mouseX > (linkWidth - pseudoWidth)) {
console.log ("click pseudo");
this.className = 'c2';
} else {
console.log ("click element");
this.className = 'c1';
}
})
});
No,but you can do like this
In html file add this section
<div class="arrow">
</div>
In css you can do like this
p div.arrow {
content: '';
position: absolute;
left:100%;
width: 10px;
height: 100%;
background-color: red;
}
Hope it will help you
Related
I am attempting to write some JavaScript code that will allow me to center a child element within it's parent using padding. Then using the same function to recalculate the spacing using the 'resize' event. Before you start asking me why i am not doing this with CSS, this code is only a small part of a larger project. I have simplified the code as the rest of the code works and would only serve to confuse the subject.
Calculating the space - This is the function that caculates the amount of space to be used on either side of the child element.
($outer.outerWidth() - $inner.outerWidth()) / 2;
($outer.outerHeight() - $inner.outerHeight()) / 2;
The problem
Although i have successfully managed to get the desired results with margin. Padding is causing me problems.
It appears to be increasing the width on the outer element when resized
It does not center the child element perfectly (there appears to be an offset)
The inner element collapses on resize and becomes invisible.
I realize that there may be some fundamentals regarding padding that are causing my problems however after numerous console logs and observing the data returned i still can't put my finger on the problem. Any suggestion would be very welcome. It may turn out that this is not feasible at all.
HTML
<div id="demo" class="outer">
<div class="inner">
</div>
</div>
CSS
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
.outer {
width:97%;
height:400px;
border:1px solid black;
margin:20px;
}
.inner {
width:40%;
height:100px;
background-color:grey;
}
JAVASCRIPT
var $outer = $(".outer");
var $inner = $(".inner");
var getSpace = function(axis) {
if (axis.toLowerCase() == "x") {
return ($outer.outerWidth() - $inner.outerWidth()) / 2;
} else if (axis.toLowerCase() == "y") {
return ($outer.outerHeight() - $inner.outerHeight()) / 2;
}
}
var renderStyle = function(spacingType) {
var lateralSpace = getSpace("x");
var verticalSpace = getSpace("y");
var $element;
if (spacingType == "padding") {
$element = $outer;
} else if (spacingType == "margin") {
$element = $inner;
}
$.each(["top", "right", "bottom", "left"], function(index, direction) {
if (direction == "top" || direction == "bottom") {
$element.css(spacingType + "-" + direction, verticalSpace);
}
else if (direction == "right" || direction == "left") {
$element.css(spacingType + "-" + direction, lateralSpace);
}
});
};
var renderInit = function() {
$(document).ready(function() {
renderStyle("padding");
});
$(window).on("resize", function() {
renderStyle("padding");
});
}
renderInit();
EXAMPLE - link
Although I completely disagree with this approach to horizontally centring an element, hopefully this will help you on your way.
Fiddle: https://jsfiddle.net/0uxx2ujg/
JavaScript:
var outer = $('.outer'), inner = $('.inner');
function centreThatDiv(){
var requiredPadding = outer.outerWidth() / 2 - (inner.outerWidth() / 2);
console.log(requiredPadding);
outer.css('padding', '0 ' + requiredPadding + 'px').css('width','auto');
}
$(document).ready(function(){
// fire on page load
centreThatDiv();
});
$(window).resize(function(){
// fire on window resize
centreThatDiv();
});
HTML:
<div class="outer">
<div class="inner">Centre me!</div>
</div>
CSS:
.outer{ width:80%; height:300px; margin:10%; background: tomato; }
.inner{ width:60px; height:60px; background:white; }
Furthered on from why I disagree with this approach - JavaScript shouldn't be used to lay things out. Sure - it can be, if it really needs to be used; but for something as simple as centring an element, it's not necessary at all. Browsers handle resizing CSS elements themselves, so by using JS you introduce more headaches for yourself further down the line.
Here's a couple of examples of how you can achieve this in CSS only:
text-align:center & display:inline-block https://jsfiddle.net/0uxx2ujg/1/
position:absolute & left:50% https://jsfiddle.net/0uxx2ujg/2/ (this can be used for vertically centring too which is trickier than horizontal)
You can create the new CSS class to adjust for elements size on $window.onresize = function () {
//add your code
};
I have a list of Elements that are floating left:
<style>
ul {
overflow: hidden;
width: 100%;
}
ul li {
float: left;
width: 100px;
}
</style>
<ul>
<li id="#one">One</li>
<li id="#two">Two</li>
<li id="#three">Three</li>
...
</ul>
...having a window width of 200px the first Element that is floating on the second line would be #three... having a window width of just 100px that would be #two.
Now on every <li> there is a click-event bound, that should tell me which Element is the first Element on the next Line, seen by itself.
Clicking on #one with a window width of 200px I want to get #three, and with a window width of 100px I want to get #two.
I tried to solve this by getting the position of the Element that is clicked on, query the Position inside the window using .getBoundingClientRect(), get the height of the Element (taking 20px here), and then get the Element by using document.elementFromPoint(y,x)...something like this:
y = clicked_element.getBoundingClientRect().left
x = clicked_element.getBoundingClientRect().top + 21
first_element_on_next_line = document.elementFromPoint(y,x)
so far, so good... but of course this is just working if the first element on the next line is really inside the window! :-\
and this is where I'm stuck...if I have a window width of 200px and a window height of just 20px (just for the example), I just can't select #three with document.elementFromPoint() because its position is outside the window.
I could also use jQuery, it's included anyways, but everything came across was using document.elementFromPoint()
Any Ideas??
You could use following logic using jQuery .position() method: {as suggested by maja}
$(function () {
var $lis = $('ul li').on('click', function(){
var $nextFirstElement = $(this).nextAll('.first').first();
console.log($nextFirstElement);
});
$(window).on('resize', function () {
var initialLeft;
$lis.each(function(i){
if (i === 0) { // this could be put out of the loop, but...
initialLeft = $(this).position().left;
}
$(this).toggleClass('first', i === 0 || $(this).position().left === initialLeft);
});
}).resize();
});
-jsFiddle-
The Solution I'm using now (which in fact is the Solution charlietfl brought up in the Comments) is this:
$(function(){
var list = $("ul"),
lis = list.children(),
first_li = lis.first(),
list_width, first_li_width, items_per_line
var set_vars = function(){
list_width = list.width()
first_li_width = first_li.width()
items_per_row = Math.floor(list_width / first_li_width)
}
set_vars()
lis.click(function(){
index = lis.index(this)
next_first_index = index + (items_per_row - (index % items_per_row))
console.log(next_first_index)
})
$(window).resize(function(){
set_vars()
})
})
I have two columns in my HTML page.
<div id="content">
<div id="left"></div>
<div id="right"></div>
</div>
Each of them occupies half of the page
#content {
height: 100%;
}
#left, #right {
float: left;
width: 50%;
height: 100%;
overflow: auto;
}
I'd like the boundary between left and right halves to be adjustable by the user. That is, the user can move the boundary to the left or to the right as he/she browses the page. Is it possible to do that somehow?
Yes, but it requires JavaScript. To apply it, you could of course just set the width of each of the sides:
var leftPercent = 50;
function updateDivision() {
document.getElementById('left').style.width = leftPercent + '%';
document.getElementById('right').style.width = (100 - leftPercent) + '%';
}
Now you can adjust the division with, say leftPercent = 50; updateDivision(), but the user isn't going to do that. There are multiple different ways you could present this to the user. Probably the best-suited way would be a little line in the middle they could drag. For this, you could use a little CSS for the positioning:
#content {
position: relative;
}
#divider {
position: absolute;
/* left to be set by JavaScript */
width: 1px;
top: 0;
bottom: 0;
background: black;
cursor: col-resize;
/* feel free to customize this, of course */
}
And then make sure you've got a div with an id of divider in content and update updateDivision to also update the left of divider:
document.getElementById('left').style.left = leftPercent + '%';
Then you just need a little logic to handle the dragging. (Here, I've put all of the elements into appropriately-named variables):
divider.addEventListener('mousedown', function(e) {
e.preventDefault();
var lastX = e.pageX;
document.documentElement.addEventListener('mousemove', moveHandler, true);
document.documentElement.addEventListener('mouseup', upHandler, true);
function moveHandler(e) {
e.preventDefault();
e.stopPropagation();
var deltaX = e.pageX - lastX;
lastX = e.pageX;
leftPercent += deltaX / parseFloat(document.defaultView.getComputedStyle(content).width) * 100;
updateDivision();
}
function upHandler(e) {
e.preventDefault();
e.stopPropagation();
document.documentElement.removeEventListener('mousemove', moveHandler, true);
document.documentElement.removeEventListener('mouseup', upHandler, true);
}
}, false);
You should be able to read it to see how it works, but in short: It listens for when someone presses on the divider. When they do, it'll attach listeners to the page for when they move their mouse. When they do, it updates the variable and calls updateDivision to update the styles. When eventually it gets a mouseup, it stops listening on the page.
As a further improvement, you could make every element have an appropriate cursor style while dragging so your cursor doesn't flash while dragging it.
Try it out.
There's nothing in the divisions so nothing will happen. It's like writing:
<h1></h1>
And changing the CSS for h1 and expecting something to be there
I'm trying to get my jQuery event callback to trigger correctly, but I can't seem to get around the fact the element I am interested in not receiving the event because of another element that covers it on the page. I can summarise it as follows (I've styled the elements so they show up in a jsfiddle):
<div id='mydiv'>
<div style="border: 1px solid; border-color: red; position: absolute; left: 0px; top: 0px; width: 200px; height: 200px; z-index: 100">Hello</div>
<canvas style="border: 1px solid; border-color: yellow; position: absolute; left: 50px; top: 50px; width: 150px; height: 150px"></canvas>
</div>
With the segment above, if I try to listen to mouse clicks on the <canvas>, the event never gets called:
$('#mydiv').on('mousedown', 'canvas', this, function(ev) {
console.log(ev.target);
});
However, if I modify my event handler to listen to the <div> element instead, the callback is triggered as expected:
$('#mydiv').on('mousedown', 'div', this, function(ev) {
console.log(ev.target);
});
How can I coerce my <canvas> to receive events, whilst leaving the offending <div> block in the fore-front?
This should be the simplest solution, as already proposed:
http://jsfiddle.net/CUJ68/4/
$('canvas').on('mousedown', function(ev) {
console.log(ev.target);
});
$('ul').on('mousedown', function(ev){
$('canvas').mousedown();
});
if you need the original eventdata:
$('canvas').bind('mousedown', function(ev, parentEV) {
if(parentEV){
console.log(parentEV);
alert("Canvas INdirectly clicked!");
}else{
console.log(ev);
alert("Canvas directly clicked!");
}
});
$('ul').on('mousedown', function(ev){
$('canvas').trigger('mousedown', ev);
});
You can't. Objects on top get the click events. That's how the DOM works.
If you want to handle that click event, you will need to handle in the object that is on top or use bubbling and handle it in a parent object. You can handle it in the top object and "forward" it to the other object if you want by triggering a click on that other object or by just calling a click handler directly.
Or, you can move the canvas element above the ul by setting it's z-index to a higher value and it will then get the click event.
Or, you can make a new transparent canvas object that is on top that gets the event, leaving the other two objects where they are for the desired visual effect.
You can bind the element to the closest common parent, and check whether the X and Y coordinates of the mouse are within the range of the canvas.
In the example below, I have cached the dimensions (height and width) of the canvas, because I assume these to be constant. Move this inside the function if the dimensions are not constant.
I use the .offset() method to calculate the real X and Y coordinates of the <canvas>s top-left corner. I calculate the coordinates of the bottom-right corner by adding the values of outerWidth() and .outerHeight().
Basic demo: http://jsfiddle.net/75qbX/2/
var $canvas = $('canvas'), /* jQuery reference to the <canvas> */
$canvasWidth = $canvas.outerWidth(), /* assuming height and width to be constant */
$canvasHeight = $canvas.outerHeight();
function isCanvasClicked(x, y, target) {
if (target.tagName === 'CANVAS') return true;
var offset = $canvas.offset(),
left = offset.left,
top = offset.top;
return x >= left && x <= left + $canvasWidth &&
y >= top && y <= top + $canvasHeight;
}
$('#mydiv').on('mousedown', '*', this, function(ev) {
if (isCanvasClicked(ev.pageX, ev.pageY, ev.target)) {
$canvas.fadeOut().fadeIn();
}
});
Here you have a solution that consists in capture click event on above element, and triggering the event on the other: registering clicks on an element that is under another element
How to detect if two <div> elements have collided?
The two divs are simple coloured boxes travelling perpendicular to each other, so no complicated shapes or angles.
var overlaps = (function () {
function getPositions( elem ) {
var pos, width, height;
pos = $( elem ).position();
width = $( elem ).width();
height = $( elem ).height();
return [ [ pos.left, pos.left + width ], [ pos.top, pos.top + height ] ];
}
function comparePositions( p1, p2 ) {
var r1, r2;
r1 = p1[0] < p2[0] ? p1 : p2;
r2 = p1[0] < p2[0] ? p2 : p1;
return r1[1] > r2[0] || r1[0] === r2[0];
}
return function ( a, b ) {
var pos1 = getPositions( a ),
pos2 = getPositions( b );
return comparePositions( pos1[0], pos2[0] ) && comparePositions( pos1[1], pos2[1] );
};
})();
$(function () {
var area = $( '#area' )[0],
box = $( '#box0' )[0],
html;
html = $( area ).children().not( box ).map( function ( i ) {
return '<p>Red box + Box ' + ( i + 1 ) + ' = ' + overlaps( box, this ) + '</p>';
}).get().join( '' );
$( 'body' ).append( html );
});
body {
padding: 30px;
color: #444;
font-family: Arial, sans-serif;
}
h1 {
font-size: 24px;
margin-bottom: 20px;
}
#area {
border: 2px solid gray;
width: 500px;
height: 400px;
position: relative;
}
#area > div {
background-color: rgba(122, 122, 122, 0.3);
position: absolute;
text-align: center;
font-size: 50px;
width: 60px;
height: 60px;
}
#box0 {
background-color: rgba(255, 0, 0, 0.5) !important;
top: 150px;
left: 150px;
}
#box1 {
top: 260px;
left: 50px;
}
#box2 {
top: 110px;
left: 160px;
}
#box3 {
top: 200px;
left: 200px;
}
#box4 {
top: 50px;
left: 400px;
}
p {
margin: 5px 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<h1>Detect overlapping with JavaScript</h1>
<div id="area">
<div id="box0"></div>
<div id="box1">1</div>
<div id="box2">2</div>
<div id="box3">3</div>
<div id="box4">4</div>
</div>
General idea - you get the offset and dimension of the boxes and check whether they overlap.
If you want it to update, you can use setInterval:
function detectOverlapping() {
// code that detects if the box overlaps with a moving box
setInterval(detectOverlapping, 25);
}
detectOverlapping();
Also, note that you can optimize the function for your specific example.
you don't have to read the box dimensions repeatedly (like I do in my code) since they are fixed. You can read them on page load (into a variable) and then just read the variable
the horizontal position of the little box does not change (unless the user resizes the window). The vertical positions of the car boxes does not change. Therefore, those values also do not have to be read repeatedly, but can also be stored into variables.
you don't have to test whether the little box overlaps with all car boxes at all times. You can - based on its vertical position - figure out in which lane the box is currently, and test only the specific car box from that lane.
I believe this is the easiest way:
https://plugins.jquery.com/overlaps/
Here is another one, in German:
http://www.48design.de/news/2009/11/20/kollisionsabfrage-per-jquery-plugin-update-v11-8/
I'd give those a try.
--UPDATE--
I can't really spend anytime on it right now, but i can when i get home if no one answers but you;d do something like:
setInterval(function(){
//First step would be to get the offset of item 1 and item 2
//Second would be to get the width of each
//Third would be to check if the offset+width ever overlaps
//the offset+width of the 2nd
//Fourth would be, if so, do X or set a class...
},10);
Its a little late on this but I guess you could use this approach that I tried when I was faced with the similar situation. The advantage here is that there are no additional plugin, or scripts involved and neither do you have to introduce performance hungry polling into it.
This technique uses the the built-in methods and events that Jquery's droppable has to offer.
Ok, enough said, here's the solution technique:
Say if you have two elements (images in my case) and you don't want them to overlap or detect when they do, make the two elements a droppable and make them to 'accept' each other:
$([div1, div2]).droppable(CONFIG_COLLISSION_PREVENTION_DROPPABLE);
The 'CONFIG_COLLISSION_PREVENTION_DROPPABLE' looks like this:
var originatingOffset = null;
CONFIG_COLLISSION_PREVENTION_DROPPABLE = {
tolerance: "touch",
activate : function (event, ui) {
// note the initial position/offset when drag starts
// will be usedful in drop handler to check if the move
// occurred and in cae overlap occurred, restore the original positions.
originatingOffset = ui.offset;
},
drop : function (event, ui) {
// If this callback gets invoked, the overlap has occurred.
// Use this method to either generate a custom event etc.
// Here, i used it to nullify the move and resetting the dragged element's
// position back to it's original position/offset
// (which was captured in the 'activate' handler)
$(ui.draggable).animate({
top: originatingOffset.top + "px",
left: originatingOffset.left + "px"
}, 300);
}
}
The 'activate' and 'drop' handlers refer to the 'dropactivate' and 'drop' events of "droppable" plugin
Here, the key is the 'drop' callback. Whenever any of the two elements overlap and they are dropped over each other, the 'drop' will be called. This is the place to detect and take actions, may be sending out custom events or calling other actions (I here chose to revert the overlapping element's positions to the initial position when the drag started, which was captured in 'activate' callback).
That's it. No polling, no plugins, just the built-in events.
Well, there can be other optimizations/extensions done to it, this was simply the first shot out of my head that worked :)
You can also use the 'dropover' and 'dropout' events to signal and create a visual feedback to the user that two elements are overlapping, while they may be still on the move.
var CLASS_INVALID = "invalid";
// .invalid { border: 1px solid red; }
...
$.extend(CONFIG_COLLISSION_PREVENTION_DROPPABLE, {
over : function (event, ui) {
// When an element is over another, it gets detected here;
// while it may still be moved.
// the draggable element becomes 'invalid' and so apply the class here
$(ui.draggable).addClass(CLASS_INVALID);
},
out : function(event, ui) {
// the element has exited the overlapped droppable now
// So element is valid now and so remove the invalid class from it
$(ui.draggable).removeClass(CLASS_INVALID);
}
});
Hope this helps!
You can do this using getBoundingClientRect()
function isOverlapping(div1, div2){
const div1 = div1.getBoundingClientRect();
const div2 = div2.getBoundingClientRect();
return (div1.right > div2.left &&
div1.left < div2.right &&
div1.bottom > div2.top &&
div1.top < div2.bottom)
}
EDIT: I have written a blog post on my website. Here a link to it.
http://area36.nl/2014/12/creating-your-own-collision-detection-function-in-javascript/
Well I had the same problem but thanks to the answer of Oscar Godson I got a function that works. I used Jquery for easy coding and because i'm lazy ;p. I put the function in a other function that is fired every second so keep that in mind.
function collidesWith (element1, element2) {
var Element1 = {};
var Element2 = {};
Element1.top = $(element1).offset().top;
Element1.left = $(element1).offset().left;
Element1.right = Number($(element1).offset().left) + Number($(element1).width());
Element1.bottom = Number($(element1).offset().top) + Number($(element1).height());
Element2.top = $(element2).offset().top;
Element2.left = $(element2).offset().left;
Element2.right = Number($(element2).offset().left) + Number($(element2).width());
Element2.bottom = Number($(element2).offset().top) + Number($(element2).height());
if (Element1.right > Element2.left && Element1.left < Element2.right && Element1.top < Element2.bottom && Element1.bottom > Element2.top) {
// Do your stuff here
}
}
What it does is basically it gets all the values of element1 and then get all the values of element2. Then with the help of some calculations it figures out all the values. Then in the if statement it compares the square of element1 to the square of element2. If the values of element1 are between the left, right, top and bottom values of element2. If that is true the code in the bottom is executed.
I ran into this generalized issue myself, so (full disclosure) I made a plugin for it. For simple collision queries about static objects, try this:
http://sourceforge.net/projects/jquerycollision/
Which allows you to get a list of overlapping collision boxes (or none if there's no collision):
hits = $("#collider").collision(".obstacles");
Or to get a collision event during "dragging", use this:
http://sourceforge.net/apps/mediawiki/jquidragcollide/?source=navbar#collision
Which gives you a "collision" event to connect to. (Or a "protrusion" event, to see if a div escapes another div that currently contains it.)
$(draggable).bind(
"collision",
function(event,ui) {
...
}
);
If you are checking collisions during motion other than dragging, just call the original repeatedly, it's pretty quick. Note: the dragging one doesn't play nicely with resizing.
Post is old, May be it help someone...
function CheckDiv()
{
var ediv1 = document.getElementById('DIV1');
var ediv2 = document.getElementById('DIV2');
ediv1.top = $(ediv1).offset().top;
ediv1.left = $(ediv1).offset().left;
ediv1.right = Number($(ediv1).offset().left) + Number($(ediv1).width());
ediv1.bottom = Number($(ediv1).offset().top) + Number($(ediv1).height());
ediv2.top = $(ediv2).offset().top;
ediv2.left = $(ediv2).offset().left;
ediv2.right = Number($(ediv2).offset().left) + Number($(ediv2).width());
ediv2.bottom = Number($(ediv2).offset().top) + Number($(ediv2).height());
if (ediv1.right > ediv2.left && ediv1.left < ediv2.right && ediv1.top < ediv2.bottom && ediv1.bottom > ediv2.top)
{
alert("hi");
}
if (ediv1.left > ediv2.left && ediv1.top > ediv2.top && ediv1.right < ediv2.right && ediv1.bottom < ediv2.bottom)
{
alert("hello");
}
}