The problem I'm trying to solve is "What's at this position?"
It's fairly trivial to get the x/y position (offset) of a DIV, but what about the reverse? How do I get the id of a DIV (or any element) given an x/y position?
Unfortunately, triggering a manufactured/simulated mouse event won't work, since when you dispatch it, you have to provide a target element. Since that element is the one you're trying to figure out, all you could do is dispatch it on the body, as if it had already bubbled.
You really are left to do it on your own, that is manually walk through the elements you're interested in, and compare their position/size/zIndex to your x/y point and see if they overlap. Except in IE and more recently FF3, where you can use
var el = document.elementFromPoint(x, y);
See
http://developer.mozilla.org/En/DOM:document.elementFromPoint
http://msdn.microsoft.com/en-us/library/ms536417(VS.85).aspx
function getDivByXY(x,y) {
var alldivs = document.getElementsByTagName('div');
for(var d = 0; d < alldivs.length; d++) {
if((alldivs[d].offsetLeft == x) && (alldivs[d].offsetTop == y)) {
return alldivs[d];
}
}
return false;
}
Use a JQuery selector to filter the list of all DIVs for one that matches your position criteria?
Create a mouse event listener, then trigger a mouse event at that location. This should give you the entire stack of elements at that location.
Or, look at the source of Firebug.
If all you have is the X and Y position, (and you can't track mouse movement like you mentioned) then you will have to traverse the DOM, looping through every DIV. For each DIV you will need to compare its X and Y coordinates against those you have. This is an expensive operation, but it is the only way. I suggest you might be better off rethinking your problem instead of coming up with a solution for it.
One option is to build an array of "div-dimension" objects. (Not to be confused with the divs themselves... IE7 perf is frustrating when you read dimensions off of object.)
These objects consist of a pointer to the div, their dimensions (four points... say top, left, bottom, and right), and possibly a dirty bit. (Dirty bit is only really needed if the sizes change.
You could then iterate through the array and check dimensions. It requires O(n) to do that on each mouse move. You might be able to do slightly better with a binary search style approach... maybe.
If you do a binary search style approach, one way is to store 4 arrays. Each with a single point of the dimension, and then binary search on all four. O(4logn) = O(logn).
I'm not saying I recommend any of these, but they MIGHT work.
I think what John is saying is that you can use document.createEvent() to simulate a mousemove at the location you want. If you capture that event, by adding an eventlistener to the body, you can look at the event.target and see what element was at that position. I'm unsure as to what degree IE supports this method, maybe someone else knows?
http://developer.mozilla.org/en/DOM/document.createEvent
Update:
Here's a jquery plugin that simulates events:
http://jquery-ui.googlecode.com/svn/trunk/tests/simulate/jquery.simulate.js
this might be a little too processor intensive but going over the whole list of div elements on a page, finding their positions and sizes then testing if they're under the mouse. i don't think i'd want to do that to a browser though.
You might find it's more efficient to traverse the DOM tree once when the page is loaded, get all elements' positions and sizes, and store them in an array/hash/etc. If you design the data structure well, you should be able to find an element at the given coordinates fairly quickly when you need it later.
Consider how often you will need to detect an element, and compare that to how often the elements on the page will change. You would be balancing the number of times you have to re-compute all the element locations (an expensive computation) against the number of times you'd actually use the computed information (relatively cheap, I hope).
Related
I am a beginner in web development & svg related topics.
I think this should be easy for you svg experts, but I am struggling with this for a while now. All the resources I found on this topic don't seem to work for my fairly simple goal.
My application should run in Chrome browser.
The DOM structure like this:
<div>
<svg>
<g>
<circle></circle>
</g>
<svg>
</div>
There could be way more svg tags or nested svg elements inside though, but this is the general setup.
The goal:
I want to click somewhere and find the svg child element (e.g. the circle) which is at the position of my click. The basic function I was thinking of was:
document.elementFromPoint(x, y)
The problems:
1) All the elements can have transformations, like translate, scale and rotation applied to them, which complicates things.
2) I only want to be able to click elements, which are children of an svg node.
Some of my tries:
https://jsfiddle.net/a6uLn9cn/3/
I tried to put "pointer-events:none" on the DIV and / or the SVG node. This way, I am able to avoid that these are "clickable". There are two problems here though: I need pointer-events on the SVG node, as it needs to be zoomed & panned, and also the click events on e.g. the circle "bubbles" up to the DIV afterwards somehow.
Another way to make sure that only children of an svg node are "clickable" can be to check the element.ownerSVGDocument for the found element. If it is null or undefined, I know the found element is not a svg child.
To tackle the problem of actually clicking on an element which has been transformed (either by itself or through its parent), I tried to use element.getBoundingClientRect() which gives me the surrounding rectangle of an element. With this, I made a function to determine if the click was inside of an elements surrounding rectangle:
e is the click event, elem is the found element recieved by document.elementFromPoint()
function clickIsInside(e, elem){
var clickX = e.clientX;
var clickY = e.clientY;
var boxLeft = elem.getBoundingClientRect().left;
var boxRight = elem.getBoundingClientRect().right;
var boxTop = elem.getBoundingClientRect().top;
var boxBottom = elem.getBoundingClientRect().bottom;
if(clickX >= boxLeft && clickX <= boxRight && clickY >= boxTop && clickY <= boxBottom){
return true;
} else {
return false;
}
}
But in my jsfiddle, you will not be able to "click" the path for example on the far right side of its boundingClientRect, which makes no sense to me if you imagine where the boundingClientRect is.
Also if i add the path to the DOM after i added the circles, I will not be able to click the circles anymore, but only the path. I totally understand why, but what if i need to click both?
I am not sure if I was able to clearify my troubles here, but I will be very thankful for any input you guys can possibly give me. Thank you very much.
I found out that most of the things i did in my question are just complicating things. The solution was just to check whether the found element has any ownerSVGDElement, and to not dispatch a click event if it does not. Applied transformations on the elements do not seem to be any problem, since document.elementFromPoint() is robust enough to find transformed elements. This question seems useless now, so I answered & closed it just for the rare condition that anybody will ever encounter or try such brainless things as i did.
Edit: Seems like i can only accept this as answer in 2 days.
I have added visually hidden jump links to my website, that appear when they are focused (similar to e.g. GitHub, just press Tab once on the home page).
I want to test this behaviour with Capybara. I can't check for e.g. 'visibility: true/false', because the links are not really hidden (otherwise screenreaders would't see them), but only moved out from the viewport by absolute positioning. When they are focused, they are placed at their original position in the viewport.
So I guess I have to check the X and Y coordinate, but Capybara doesn't seem to offer a native method for getting them? Is this true? And if so, how would I get them for e.g. an element '#jump_to_content'? By executing some JavaScript?
Update
I found some inspiration here: https://content.pivotal.io/blog/testing-accessibility-with-rspec-and-capybara
But this doesn't seem to work for my configration:
link.native.location
NoMethodError: undefined method `location' for #<Capybara::Poltergeist::Node tag="a">
You are correct, Capybara does not offer a native method for getting an element's position in the page, but if you have access to jQuery you can use Capybara's page.evaluate_script method to easily determine an element's position. It would look something like this:
page.evaluate_script("$('.menu > div:nth-child(1)').offset().top")
The .offset() method allows us to retrieve the current position of an element relative to the document. Contrast this with .position(), which retrieves the current position relative to the offset parent
Just note that
While it is possible to get the coordinates of elements with visibility:hidden set, display:none is excluded from the rendering tree and thus has a position that is undefined.
Using capybara's page.evaluate_script will allow you to execute some JavaScript and create an assertion off the return value:
expect(page.evaluate_script("document.querySelectorAll('a#jump-to-content')[0].style.top;")).to eq('-8000px')
According to this SO answer, the best way to use JavaScript to find the position of an element is to use getBoundingClientRect(); as it returns values that are relative to the viewport.
It feels more readable to me to find the element with Capybara instead of JavaScript, and then find its position. I'm using Selenium and Chrome.
link = find("a", text: "content")
link.evaluate_script("this.getBoundingClientRect().left;")
link.evaluate_script("this.getBoundingClientRect().right;")
link.evaluate_script("this.getBoundingClientRect().top;")
link.evaluate_script("this.getBoundingClientRect().bottom;")
Some browsers (like Chrome) also have:
link.evaluate_script("this.getBoundingClientRect().x;")
link.evaluate_script("this.getBoundingClientRect().y;")
link.evaluate_script("this.getBoundingClientRect().height;")
link.evaluate_script("this.getBoundingClientRect().width;")
You can also do
link.rect.y
link.rect.height
link.rect.x
link.rect.width
As per Nuri's answer, the best way you can go is asking the browser directly(headless browsers don't read css files, afaik).
However, rather than asking the style of the element, you might want to directly check its position by using offsetTop. So something like
expect(page.evaluate_script("document.querySelector('a#jump-to-content').offsetTop;")).to eq('-8000px')
If you need more control, you could also run it all in javascript:
expect(page.evaluate_script("document.querySelector('a#jump-to-content').offsetTop < document.querySelector('body').offsetTop;").to be_true
edit:
The problem seems to be that the font size isnt explicitly set and is set by the css class only. so style.fontSize always returns an empty string
if there another way to return the font size?
var allMainFrameElems = parent.main.document.getElementsByTagName('*');
for (i=0; i < allMainFrameElems.length; i++){
if(allMainFrameElems[i].style.fontSize != null){
alert(llMainFrameElems[i].style.fontSize);
}
}
If the fontSize style in not explicitly set on an element (e.g. <p style="font-size:12pt;">...</p>), you won't be able to get it from anywhere. Font-sizes are most often set in your CSS classes, which are not reachable from your element's properties, and the elements do not have any font-size related properties.
In order to even come close to doing this you will need to do some tricks and will not be able to definatively determine font size. Basically you will have to manipulate the page a great deal on every element (not good) just to determine this.
See this fiddle page, especially the pixelPerEm function I tossed together very quickly. http://jsfiddle.net/MarkSchultheiss/vc8Zy/
It is not very clean at the moment and IF I get time I might try to make it better but it might give you something to start with even if it is NOT very pretty.
EDIT: basic explanation is to utilize the em css, inject an element with a known setting, calculate the pixel offset on the injection and then remove the injected element. None of that is pretty and all of it is error/bug prone or has potential for issues.
I am using a drag script to move an object along the x axis.
Along that same axis there is another object.
I want to use onmouseover so when the cursor is over that object and I'm dragging my object, something happens.
Now, this won't work because while dragging, the dragged object is always over other objects.
How can I make JavaScript ignore the dragged object?
Edit : I use http://www.webtoolkit.info/javascript-drag-and-drop.html for dragging.
I recently encountered this issue and ended up using this jQuery plugin:
http://www.48design.de/news/2009/11/20/kollisionsabfrage-per-jquery-plugin-update-v11-8/
This is great for detecting object collision. However, to determine when the mouse cursor is over the object, the trick I used was to create a 1x1 absolute inline-block div and always have that be positioned where the mouse cursor is.
So, when the user is dragging, to check and see if objects are colliding, use:
if ($('.cursorBlock').collidesWith('.staticBlock')) {
// objects are colliding
} else {
// objects are not colliding
}
So if you keep the 1x1 .cursorBlock with the mouse cursor as you drag, when the cursor is over the .staticBlock element, the .cursorBlock element will be too, and the collision will be detected, even though the real object you're "dragging" will be between the two.
I know you didn't tag your question with jQuery, but I thought I'd answer anyhow in case this helps you.
Have a look at the ondragenter event:
https://developer.mozilla.org/en/DragDrop/Drag_and_Drop
http://msdn.microsoft.com/en-us/library/ms536925%28v=vs.85%29.aspx
I used to use cumulativeOffset() and getDimensions() to calculate the bounding box of elements, but just realized that cumulativeOffset returns the top left corner oft the start of an element. Meaning: if an inline element wraps, and the next line is more to the left than the start, I get a displaced bounding box.
After researching a bit, I found that I can use getClientRects() to get all rects. I could then go through, and just take the left position of the rect that's most to the left.
I was wondering if there is a better way of doing this... I just didn't find a boundingBox() prototype function. Did I overlook it?
Edit: I also just found out that getClientRects() is not supported by all browser, so this is no solution.
I don't see a boundingBox() function either, but I wonder if using the same technique (cumulativeOffset() and getDimensions()) on the parent via: getOffsetParent() would do what you want. getOffSetParent():
"Returns element’s closest positioned
ancestor. If none is found, the body
element is returned."
Which should account for word-wrapping where the second line is further left.
I've never heard of a way to do this. You could set it position:relative, drop a 1x1 absolutely positioned div into it, position it right:0, get that div's LEFT + WIDTH, and subtract the offset width of the original inline item from that value.
Saying that, total hack, maybe you need to rethink the reason you want to do this!
The solution given by dfitzhenry seems not working in the case of multiline inline elements.
Here's my Javascript solution : get your inline element nextSibling, check if it is an inline element, otherwise create a new inline element, add it to your inline element's parent and then get its offsetLeft:
var parentContainer = inline_elm.parentNode;
var nextsib = inline_elm.nextSibling;
var remove_next_sibling = false;
if(!nextsib || nextsib.clientWidth != 0){
remove_next_sibling = true;
var temp= document.createElement('span');
parentContainer.insertBefore(temp, nextsib);
nextsib = temp;
}
var inline_bounding_right = nextsib.offsetLeft;
if(remove_next_sibling) parentContainer.removeChild(nextsib);
This is an old post, but the standard method now to get a bounding box is getBoundingClientRect(), which is supported in all major browsers and has had at least partial support since 2009 in most browsers. Enjoy!
P.S. getClientRects() is also very useful for getting the individual bounding boxes of the wrapped text. It seems to have the same browser support as getBoundingClientRect(), since the one depends on the other, and this source suggests that it's well supported.