Usually I prefer to write my own solutions for trivial problems because generally plugins add a lot of unneeded functionality and increase your project in size. Size makes a page slower and a 30k difference (compared to jquery draggable) in a 100k pageviews / day website makes a big difference in the bill. I already use jquery and I think that's all I need for now, so please, don't tell me to use another plugin or framework to drag things around.
Whit that in mind I wrote the following code, to allow a box to be draggable around. The code works just fine (any tip about the code itself will be great appreciate), but I got a small little glitch.
When I drag the box to the browser right edge limit, a horizontal scroll bar appears, the window width gets bigger because of the box. The desirable behavior is to see no horizontal scroll bar, but allow to put part of the box outside the window area, like a windows window do.
Any tips?
CSS:
.draggable {
position: absolute;
cursor: move;
border: 1px solid black;
}
Javascript:
$(document).ready(function() {
var x = 0;
var y = 0;
$("#d").live("mousedown", function() {
var element = $(this);
$(document).mousemove(function(e) {
var x_movement = 0;
var y_movement = 0;
if (x == e.pageX || x == 0) {
x = e.pageX;
} else {
x_movement = e.pageX - x;
x = e.pageX;
}
if (y == e.pageY || y == 0) {
y = e.pageY;
} else {
y_movement = e.pageY - y;
y = e.pageY;
}
var left = parseFloat(element.css("left")) + x_movement;
element.css("left", left);
var top = parseFloat(element.css("top")) + y_movement;
element.css("top", top);
return false;
});
});
$(document).mouseup(function() {
x = 0;
y = 0;
$(document).unbind("mousemove");
});
});
HTML:
<div id="d" style="width: 100px; left: 0px; height: 100px; top: 0px;" class="draggable">a</div>
For a simple solution, you could just add some CSS to the draggable object's container to prevent the scrollbars.
body { overflow: hidden; }
jsFiddle: http://jsfiddle.net/F894P/
Instead of this :
$("#d").live("mousedown", function () {
// your code here
}); // live
try this :
$("body").on("mousedown","#d", function(){
// your code here
$("#parent_container").css({"overflow-x":"hidden"});
// or $("body").css({"overflow-x":"hidden"});
}); // on
Where #parent_container is where your draggable object is.
You should be using jQuery 1.7+
As of jQuery 1.7, the .live() method is deprecated. Use .on() to attach event handlers. Users of older versions of jQuery should use .delegate() in preference to .live().
Related
I am using JQuery Draggable function and Touch Punch to produce a list of horizontal sliders that can be scrolled by clicking and dragging. It works great in touch and click devices. The problem I am facing is that if I try to scroll up or down in touch devices, it doesn't work.
I have looked through SO and found that removing "event.preventDefault" from TouchPunch allows vertical scrolling, the problem with this fix is, that it only works on some devices, and not all.
I am wondering if anyone has any other solutions, or alternative way of producing the same horizontal sliders that work on both touch and click events.
Here is Example code (JQuery Draggable):
$(function() {
var slides = $('#list1 ul').children().length;
var slideWidth = $('#list1').width();
var min = 0;
var max = -((slides - 1) * slideWidth);
$("#list1 ul").width(slides * slideWidth).draggable({
axis: 'x',
drag: function(event, ui) {
if (ui.position.left > min) ui.position.left = min;
if (ui.position.left < max) ui.position.left = max;
}
});
$("#list2 ul").width(slides * slideWidth).draggable({
axis: 'x',
drag: function(event, ui) {
if (ui.position.left > min) ui.position.left = min;
if (ui.position.left < max) ui.position.left = max;
}
});
});
#list1 {
position: relative;
height: 16em;
width: 100%;
text-align: middle;
white-space: nowrap;
overflow-x: hidden;
overflow-y: hidden;
}
#list1 .floating-box {
margin: auto;
display: inline-block;
width: 15em;
height: 13.5em;
margin: 0.1em;
border: 0.2em solid black;
background-color: white;
}
#list2 {
position: relative;
height: 16em;
width: 100%;
text-align: middle;
white-space: nowrap;
overflow-x: hidden;
overflow-y: hidden;
}
#lis2 .floating-box {
margin: auto;
display: inline-block;
width: 15em;
height: 13.5em;
margin: 0.1em;
border: 0.2em solid black;
background-color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="list1">
<ul>
<p>One</p>
<div class="floating-box">Floating box</div>
<div class="floating-box">Floating box</div>
<div class="floating-box">Floating box</div>
</ul>
</div>
<div id="list2">
<ul>
<p>Two</p>
<div class="floating-box">Floating box</div>
<div class="floating-box">Floating box</div>
<div class="floating-box">Floating box</div>
</ul>
</div>
If I touch list1 or list2 div and try to scroll up or down, it doesn't recognize the movement. Any help or direction would be appreciated.
EDIT
Based on the idea to determine theswipe direction based on the pageY propertie of touchemove.
I think it's a good idea to avoid the annoying double tap (see first answer below) on the long run.
There is still a compromise on it, but it is much more reasonable.
I tried many things... The best result I got is when I gave up on simulating a scroll on touchmove.
So here is the way to I now use each of the three touch events:
touchstart gets the initial variables like: window scrollTop and pageY.
touchmove determines the swipe direction and gets the last pageY.
touchend does the math as to were the page should scrollTo.
For the cuteness, I've put that result value in an .animate().
I was pleasantly surprised to see that it compensates quite really smoothly the fact that the page scrolls only on touchend.
I think that very few users will notice it ;).
Since "Touch-Punch" is working by default for horizontal swipes, the "compromise" only affects the vertical scroll.
Here is the code:
And a live link to try it on a touche-enabled device.
$(function() {
var slides = $('#list1 ul').children().length;
var slideWidth = $('#list1').width();
var min = 0;
var max = -((slides - 1) * slideWidth);
$(".draggable").width(slides * slideWidth).draggable({
axis: 'x',
drag: function(event, ui) {
if (ui.position.left > min) ui.position.left = min;
if (ui.position.left < max) ui.position.left = max;
}
});
var startTouchX=0;
var startTouchY=0;
var actualPosX;
var actualPosY;
var eventCounter=0;
var directionDetermined=false;
var direction;
var thisTouchX;
var thisTouchY;
var lastTouchY;
$(document).on("touchstart", function(e) {
// Actual document position
actualPosX = $(document).scrollLeft();
actualPosY = $(document).scrollTop();
// StartTouches
startTouchX = parseInt(e.originalEvent.touches[0].pageX);
startTouchY = parseInt(e.originalEvent.touches[0].pageY);
});
$(document).on("touchmove", function(e) {
// Arbitrary considering ONLY the fourth event...
// Touchmove fires way too many times!
// We only need to determine the main direction ONCE.
// This prevents an "s" swipe from messing this code.
eventCounter++;
if(eventCounter==4 && !directionDetermined){
thisTouchX = parseInt(e.originalEvent.touches[0].pageX);
thisTouchY = parseInt(e.originalEvent.touches[0].pageY);
if ( (Math.abs(thisTouchX - startTouchX)) / Math.abs(thisTouchY - startTouchY) > 1){ //check swipe direction
// HORIZONTAL
$("#debug").html("HORIZONTAL");
directionDetermined=true;
// NO NEED here. This is re-enabled on touchend, if it has been disabled.
//$(".draggable").draggable('enable');
}
else{
// VERTICAL
$("#debug").html("VERTICAL");
directionDetermined=true;
direction="vertical";
$(".draggable").draggable('disable'); // Disable draggable.
}
}
// Getting all the Y touches...
// The "last" value will be used on touchend.
lastTouchY = parseInt(e.originalEvent.touches[0].pageY);
//$("#debug").html(lastTouchY);
});
$(document).on("touchend", function(e) {
if(direction=="vertical"){
//$("#debug").html(lastTouchY);
var thisMoveY = -parseInt(lastTouchY - startTouchY);
//$("#debug").html(thisMoveY);
var newPosY = (actualPosY + thisMoveY);
//$("#debug").html(actualPosX+ " " +newPosY);
//window.scrollTo(actualPosX, newPosY);
$("html,body").animate({ scrollTop: newPosY },400);
}
// Reset everything for a future swipe.
startTouchX=0;
startTouchY=0;
eventCounter=0;
directionDetermined=false;
direction="";
$("#debug").html("");
// Re-enable draggable.
$(".draggable").draggable('enable');
});
});
First answer, using a "double-tap" to switch direction.
First, the Touch Punch website states that it's basically a jQuery-UI hack to handle some cases actually unhandled by jQuery-UI...
And that it is possible to find cases where Touch Punch fails.
Your issue was reported to the Touch Punch developpers here.
As an answer to that (in my words here), they said that it isn't really bug or an issue...
But a usage "conflict" on two different "wished" actions that are using the same touch events.
Sometimes to scroll the page, and sometimes to drag an element.
As a solution hint, they posted this Fiddle.
It suggests to find a way to disable draggable when needed.
But in this solution, the scrollable section is within the draggable element.
Which is not your case.
And you use almost all the mobile screen space for your draggable elements, so there is not enougth space left to trigger a draggable("disable") around them.
So... I had this idea, which I hope will help.
What if you'd find an elegant way to inform your users that a "double-tap" changes the movement orientation.
Here, I suggest a quite simple "double arrow" showing the movement direction.
Maybe you'll find something better.
This sure is a little compromise user experience, to ask them to double tap...
But if your layout really needs it, maybe it's ok.
So here, I reproduced your initial issue.
And here is the fix that I suggest.
I only tryed it on a Samsung Galaxy S3, but should work on every touch device.
$(function() {
var slides = $('#list1 ul').children().length;
var slideWidth = $('#list1').width();
var min = 0;
var max = -((slides - 1) * slideWidth);
$(".draggable").width(slides * slideWidth).draggable({
axis: 'x',
drag: function(event, ui) {
if (ui.position.left > min) ui.position.left = min;
if (ui.position.left < max) ui.position.left = max;
}
});
// Flag
var draggableEnabled=true;
// Find the doubletap position (to show a nice double arrow)
var tapPosition=[];
$(document).on("touchstart",function(e){
tapPosition[0] = parseInt(e.touches[0].pageX) - $(document).scrollLeft() - ($("#arrows img").width()/2);
tapPosition[1] = parseInt(e.touches[0].pageY) - $(document).scrollTop() - ($("#arrows img").width()/2);
});
Hammer(document).on("doubletap", function() {
//alert("Double tap");
draggableEnabled = !draggableEnabled; // Toggle
if(!draggableEnabled){
$(".draggable").draggable('disable'); // Disables draggable (and touch Punch)
$("#arrows img").css({
"transform":"rotate(90deg)", // Nice vertical double arrow
"top":tapPosition[1],
left:tapPosition[0]
}).fadeIn(600, function(){
$(this).fadeOut(600);
});
}else{
$(".draggable").draggable('enable'); // Enables draggable (and touch Punch)
$("#arrows img").css({
"transform":"rotate(0deg)", // Nice horizontal double arrow
"top":tapPosition[1],
left:tapPosition[0]
}).fadeIn(600, function(){
$(this).fadeOut(600);
});
}
});
});
Notice that it uses the Hammer.js (CDN) to detect the double tap.
And some extra CSS for the double arrow.
#arrows img{
width: 60vw;
height: 60vw;
position: fixed;
top:calc( 50vh - 60vw );
left:calc( 50vh - 60vw );
z-index:1000;
display:none;
}
This is the closest I have come to it, I wonder if someone could refine this code:
$(watchlist).on("touchstart", function(e) {
touchY = e.originalEvent.touches[0].pageY;
touchX = e.originalEvent.touches[0].pageX;
});
$(watchlist).on("touchmove", function(e) {
var fTouchY = e.originalEvent.touches[0].pageY;
var fTouchX = e.originalEvent.touches[0].pageX;
if ((Math.abs(fTouchX - touchX)) / Math.abs(fTouchY - touchY) > 1){ //check swipe direction
$("#watchlist ul").draggable( 'enable'); //if swipe is horizontal
}
else{
$("#watchlist ul").draggable( 'disable'); //if swipe is horizontal
}
});
The problem with this code is, that it deactivates the draggable function only after the touch move has been finished rather than during. If anyone could modify this code so that the draggable function is deactivated during as soon as the condition is met, during touchmove, rather than after, I would give them the bounty.
I have this event:
$(window).scroll(function(e){
console.log(e);
})
I want to know, how much I have scroll value in pixels, because I think, scroll value depends from window size and screen resolution.
Function parameter e does not contains this information.
I can store $(window).scrollTop() after every scroll and calculate difference, but can I do it differently?
The "scroll value" does not depend on the window size or screen resolution. The "scroll value" is simply the number of pixels scrolled.
However, whether you are able to scroll at all, and the amount you can scroll is based on available real estate for the container and the dimensions of the content within the container (in this case the container is document.documentElement, or document.body for older browsers).
You are correct that the scroll event does not contain this information. It does not provide a delta property to indicate the number of pixels scrolled. This is true for the native scroll event and the jQuery scroll event. This seems like it would be a useful feature to have, similar to how mousewheel events provide properties for X and Y delta.
I do not know, and will not speculate upon, why the powers-that-be did not provide a delta property for scroll, but that is out of scope for this question (feel free to post a separate question about this).
The method you are using of storing scrollTop in a variable and comparing it to the current scrollTop is the best (and only) method I have found. However, you can simplify this a bit by extending jQuery to provide a new custom event, per this article: http://learn.jquery.com/events/event-extensions/
Here is an example extension I created that works with window / document scrolling. It is a custom event called scrolldelta that automatically tracks the X and Y delta (as scrollLeftDelta and scrollTopDelta, respectively). I have not tried it with other elements; leaving this as exercise for the reader. This works in currrent versions of Chrome and Firefox. It uses the trick for getting the sum of document.documentElement.scrollTop and document.body.scrollTop to handle the bug where Chrome updates body.scrollTop instead of documentElement.scrollTop (IE and FF update documentElement.scrollTop; see https://code.google.com/p/chromium/issues/detail?id=2891).
JSFiddle demo: http://jsfiddle.net/tew9zxc1/
Runnable Snippet (scroll down and click Run code snippet):
// custom 'scrolldelta' event extends 'scroll' event
jQuery.event.special.scrolldelta = {
delegateType: "scroll",
bindType: "scroll",
handle: function (event) {
var handleObj = event.handleObj;
var targetData = jQuery.data(event.target);
var ret = null;
var elem = event.target;
var isDoc = elem === document;
var oldTop = targetData.top || 0;
var oldLeft = targetData.left || 0;
targetData.top = isDoc ? elem.documentElement.scrollTop + elem.body.scrollTop : elem.scrollTop;
targetData.left = isDoc ? elem.documentElement.scrollLeft + elem.body.scrollLeft : elem.scrollLeft;
event.scrollTopDelta = targetData.top - oldTop;
event.scrollTop = targetData.top;
event.scrollLeftDelta = targetData.left - oldLeft;
event.scrollLeft = targetData.left;
event.type = handleObj.origType;
ret = handleObj.handler.apply(this, arguments);
event.type = handleObj.type;
return ret;
}
};
// bind to custom 'scrolldelta' event
$(window).on('scrolldelta', function (e) {
var top = e.scrollTop;
var topDelta = e.scrollTopDelta;
var left = e.scrollLeft;
var leftDelta = e.scrollLeftDelta;
// do stuff with the above info; for now just display it to user
var feedbackText = 'scrollTop: ' + top.toString() + 'px (' + (topDelta >= 0 ? '+' : '') + topDelta.toString() + 'px), scrollLeft: ' + left.toString() + 'px (' + (leftDelta >= 0 ? '+' : '') + leftDelta.toString() + 'px)';
document.getElementById('feedback').innerHTML = feedbackText;
});
#content {
/* make window tall enough for vertical scroll */
height: 2000px;
/* make window wide enough for horizontal scroll */
width: 2000px;
/* visualization of scrollable content */
background-color: blue;
}
#feedback {
border:2px solid red;
padding: 4px;
color: black;
position: fixed;
top: 0;
height: 20px;
background-color: #fff;
font-family:'Segoe UI', 'Arial';
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='feedback'>scrollTop: 0px, scrollLeft: 0px</div>
<div id='content'></div>
Note that you may want debounce the event depending on what you are doing. You didn't provide very much context in your question, but if you give a better example of what you are actually using this info for we can provide a better answer. (Please show more of your code, and how you are using the "scroll value").
To detemine how many pixels were scrolled you have to keep in mind that the scroll event gets fired almost every pixel that you move. The way to accomplish it is to save the previous scrolled value and compare that in a timeout. Like this:
var scrollValue = 0;
var scrollTimeout = false
$(window).scroll(function(event){
/* Clear it so the function only triggers when scroll events have stopped firing*/
clearTimeout(scrollTimeout);
/* Set it so it fires after a second, but gets cleared after a new triggered event*/
scrollTimeout = setTimeout(function(){
var scrolled = $(document).scrollTop() - scrollValue;
scrollValue = $(document).scrollTop();
alert("The value scrolled was " + scrolled);
}, 1000);
});
This way you will get the amount of scrolled a second after scrolling (this is adjustable but you have to keep in mind that the smooth scrolling that is so prevalent today has some run-out time and you dont want to trigger before a full stop).
The other way to do this? Yes, possible, with jQuery Mobile
I do not appreciate this solution, because it is necessary to include heavy jQuery mobile. Solution:
var diff, top = 0;
$(document).on("scrollstart",function () {
// event fired when scrolling is started
top = $(window).scrollTop();
});
$(document).on("scrollstop",function () {
// event fired when scrolling is stopped
diff = Math.abs($(window).scrollTop() - top);
});
To reduce the used processing power by adding a timer to a Jquery scroll method is probably not a great idea. The visual effect is indeed quite bad.
The whole web browsing experience could be made much better by hiding the scrolling element just when the scroll begins and making it slide in (at the right position) some time after. The scrolling even can be checked with a delay too.
This solution works great.
$(document).ready(function() {
var element = $('.movable_div'),
originalY = element.offset().top;
element.css('position', 'relative');
$(window).on('scroll', function(event) {
var scrollTop = $(window).scrollTop();
element.hide();
element.stop(false, false).animate({
top: scrollTop < originalY
? 0
: scrollTop - originalY + 35
}, 2000,function(){element.slideDown(500,"swing");});
});
});
Live demo here
I am currently working on an online presentation software. For the sake of this question imagine it as powerpoint or keynote.
I want to be able to add elements to the slide and then drag them around (live), getting the new position, updating the database.
However I want to do this without any use of external libraries or frameworks, including jQuery.
Can anyone point me in a direction for my research? My current ideas to implement this are pretty messy. Especially the live-dragging is what's giving me headaches.
Thanks!
UPDATE!
the elements look something like this:
<div class="textelement"
data-id="528fc9026803fa9d4b03e506"
data-role="Textelement"
style=" left: 50px;
top: 50px;
z-index: 0;
width: 72px;
height: 72px;">
<div class="textnode">slide: 0 textelement: 0</div>
</div>
While HTML5 does provide native drag and drop, this isn't what you asked for. Check out this simple tutorial to accomplish dragging in vanilla JS: http://luke.breuer.com/tutorial/javascript-drag-and-drop-tutorial.aspx
There is great vanilla JS snippet available, but with one problem - when element start dragged on clickable element, it "clicks" on mouseup: see it on http://codepen.io/ekurtovic/pen/LVpvmX
<div class="draggable">
Dont click me, just drag
</div>
<script>
// external js: draggabilly.pkgd.js
var draggie = new Draggabilly('.draggable');
</script>
here is the "plugin": draggabilly
And, here is my independent solution, working by :class: of the element:
(function (document) {
// Enable ECMAScript 5 strict mode within this function:
'use strict';
// Obtain a node list of all elements that have class="draggable":
var draggable = document.getElementsByClassName('draggable'),
draggableCount = draggable.length, // cache the length
i; // iterator placeholder
// This function initializes the drag of an element where an
// event ("mousedown") has occurred:
function startDrag(evt) {
that.preventDefault();
// The element's position is based on its top left corner,
// but the mouse coordinates are inside of it, so we need
// to calculate the positioning difference:
var diffX = evt.clientX - this.offsetLeft,
diffY = evt.clientY - this.offsetTop,
that = this; // "this" refers to the current element,
// let's keep it in cache for later use.
// moveAlong places the current element (referenced by "that")
// according to the current cursor position:
function moveAlong(evt) {
evt.preventDefault();
var left = parseInt(evt.clientX - diffX);
var top = parseInt(evt.clientY - diffY);
// check for screen boundaries
if (top < 0) { top = 0; }
if (left < 0) { left = 0; }
if (top > window.innerHeight-1)
{ top = window.innerHeight-1; }
if (left > window.innerWidth-1)
{ left = window.innerWidth-1; }
// set new position
that.style.left = left + 'px';
that.style.top = top + 'px';
}
// stopDrag removes event listeners from the element,
// thus stopping the drag:
function stopDrag() {
document.removeEventListener('mousemove', moveAlong);
document.removeEventListener('mouseup', stopDrag);
}
document.addEventListener('mouseup', stopDrag);
document.addEventListener('mousemove', moveAlong);
return false;
}
// Now that all the variables and functions are created,
// we can go on and make the elements draggable by assigning
// a "startDrag" function to a "mousedown" event that occurs
// on those elements:
if (draggableCount > 0) for (i = 0; i < draggableCount; i += 1) {
draggable[i].addEventListener('mousedown', startDrag);
}
}(document));
I have a bunch of divs positioned absolutely on top of each other. When I bind a click event to all of them, only the top div responds. How can I send the event to all divs under the cursor?
Taking FelixKling's suggestion to use document.elementFromPoint() and Amberlamps's fiddle, and employing jQuery for the DOM interactions, I ended up with the following :
$divs = $("div").on('click.passThrough', function (e, ee) {
var $el = $(this).hide();
try {
console.log($el.text());//or console.log(...) or whatever
ee = ee || {
pageX: e.pageX,
pageY: e.pageY
};
var next = document.elementFromPoint(ee.pageX, ee.pageY);
next = (next.nodeType == 3) ? next.parentNode : next //Opera
$(next).trigger('click.passThrough', ee);
} catch (err) {
console.log("click.passThrough failed: " + err.message);
} finally {
$el.show();
}
});
DEMO
try/catch/finally is used to ensure elements are shown again, even if an error occurs.
Two mechanisms allow the click event to be passed through or not :
attaching the handler to only selected elements (standard jQuery).
namespacing the click event, click.passThrough analogous to event.stopPropagation().
Separately or in combination, these mechanisms offer some flexibility in controlling the attachment and propagation of "passThrough" behaviour. For example, in the DEMO, try removing class p from the "b" element and see how the propagation behaviour has changed.
As it stands, the code needs to be edited to get different application-level behaviour. A more generalized solution would :
allow for programmatic attachment of app-specific behaviour
allow for programmatic inhibition of "passThrough" propagation, analogous to event.stopPropagation().
Both of these ambitions might be achieved by establishing a clickPassthrough event in jQuery, with underlying "passThrough" behaviour, but more work would be involved to achieve that. Maybe someone would like to have a go.
This is not as easy as you might think. This is a solution that I came up with. I only tested it in Chrome and I did not use any framework.
The following snippet is just for add a click event to every div in the document, that outputs its class name when triggered.
var divs = document.getElementsByTagName("div");
for(var i = 0; i < divs.length; i++) {
divs[i].onclick = function() {
console.log("class clicked: " + this.className);
};
}
Attaching a click event to the body element so that every single click event is noticed by our script.
if(document.addEventListener) {
document.body.addEventListener("click", countDivs);
} else if(document.attachEvent) {
document.attachEvent("onclick", countDivs);
}
Iterate through all divs that you want to check (you might want to adjust here to your preferred range of divs). Generate their computed style and check whether the mouse coordinates are within the range of the div´s position plus its width and height. Do not trigger click event when the div is our source element because the click event has already been fired by then.
function countDivs(e) {
e = e || window.event;
for(var i = 0; i < divs.length; i++) {
var cStyle = window.getComputedStyle(divs[i]);
if(divs[i] !== e.target && e.pageX >= parseInt(cStyle.left) && e.pageX <= (parseInt(cStyle.left) + parseInt(cStyle.width)) && e.pageY >= parseInt(cStyle.top) && e.pageY <= (parseInt(cStyle.top) + parseInt(cStyle.height))) {
divs[i].click();
}
}
}
CSS:
.a, .b, .c {
position: absolute;
height: 100px;
width: 100px;
border: 1px #000 solid
}
.a {
top: 100px;
left: 100px;
}
.b {
top: 120px;
left: 120px;
}
.c {
top: 140px;
left: 140px;
}
HTML:
<div class="a"></div>
<div class="b"></div>
<div class="c"></div>
I also added a jsFiddle
A simple way could be to use elementFromPoint():
http://jsfiddle.net/SpUeN/1/
var clicks = 0,cursorPosition={};
$('div').click(function (e) {
if(typeof cursorPosition.X === 'undefined') {
cursorPosition.X = e.pageX;
cursorPosition.Y = e.pageY;
}
clicks++;
e.stopPropagation();
$(this).addClass('hided');
var underELEM = document.elementFromPoint(cursorPosition.X, cursorPosition.Y);
if (underELEM.nodeName.toUpperCase() === "DIV") $(underELEM).click();
else {
$('#clicks').html("Clicks: " + clicks);
$('.hided').removeClass('hided');
clicks=0;
cursorPosition = {};
}
});
If you are stacking elements absolutely it may be simpler to stack them all in a positioned container, and handle the events from this parent. You can then manipulate its children without having to measure anything.
Is there a simple way to locate all DOM elements that "cover" (that is, have within its boundaries) a pixel with X/Y coordinate pair?
You can have a look at document.elementFromPoint though I don't know which browsers support it.
Firefox and Chrome do. It is also in the MSDN, but I am not so familiar with this documentation so I don't know in which IE version it is included.
Update:
To find all elements that are somehow at this position, you could make the assumption that also all elements of the parent are at this position. Of course this does not work with absolute positioned elements.
elementFromPoint will only give you the most front element. To really find the others you would have to set the display of the front most element to none and then run the function again. But the user would probably notice this. You'd have to try.
I couldn't stop myself to jump on Felix Kling's answer:
var $info = $('<div>', {
css: {
position: 'fixed',
top: '0px',
left: '0px',
opacity: 0.77,
width: '200px',
height: '200px',
backgroundColor: '#B4DA55',
border: '2px solid black'
}
}).prependTo(document.body);
$(window).bind('mousemove', function(e) {
var ele = document.elementFromPoint(e.pageX, e.pageY);
ele && $info.html('NodeType: ' + ele.nodeType + '<br>nodeName: ' + ele.nodeName + '<br>Content: ' + ele.textContent.slice(0,20));
});
updated: background-color !
This does the job (fiddle):
$(document).click(function(e) {
var hitElements = getHitElements(e);
});
var getHitElements = function(e) {
var x = e.pageX;
var y = e.pageY;
var hitElements = [];
$(':visible').each(function() {
var offset = $(this).offset();
if (offset.left < x && (offset.left + $(this).outerWidth() > x) && (offset.top < y && (offset.top + $(this).outerHeight() > y))) {
hitElements.push($(this));
}
});
return hitElements;
}
When using :visible, you should be aware of this:
Elements with visibility: hidden or opacity: 0 are considered visible,
since they still consume space in the layout. During animations that
hide an element, the element is considered to be visible until the end
of the animation. During animations to show an element, the element is
considered to be visible at the start at the animation.
So, based on your need, you would want to exclude the visibility:hidden and opacity:0 elements.