I've seen dozens of scripts that can catch the x and y position of an element/object within the page. But I am always having trouble with catching the x and y when the webpage is using margins at the body, or other elements, absolute/relative elements, such like that.
Is there a solution which provides the exact position, no matter what margins or paddings are used?
I use following code to move div box to follow cursor in this Web IME site
function xy(x) {
o = document.getElementById(x);
var l =o.offsetLeft; var t = o.offsetTop;
while (o=o.offsetParent)
l += o.offsetLeft;
o = document.getElementById(x);
while (o=o.offsetParent)
t += o.offsetTop;
return [l,t];
}
Its return an array [left,top],
Getting the exact position is simply a matter of adding the offsetLefts and offsetTops recursively to the offsetParents:
function getPos(ele){
var x=0;
var y=0;
while(true){
x += ele.offsetLeft;
y += ele.offsetTop;
if(ele.offsetParent === null){
break;
}
ele = ele.offsetParent;
}
return [x, y];
}
Btw, this solution would probably run twice faster than the other solution above since we only loop once.
offsetParent and other offset functions are old... use the getBoundingClientRect
function... use this:
function getAbsPosition(element) {
var rect = element.getBoundingClientRect();
return {x:rect.left,y:rect.top}
}
now you can use it like this
<div style="margin:50px;padding:50px;" id="myEl">lol</div>
<script type="text/javascript">
var coords = getAbsPosition( document.getElementById('myEl') );
alert( coords.x );alert( coords.y );
</script>
Don't worry... no matter how much margin or position or padding the element has, it always works
Here are some improvements on #Pacerier 's code in my own answer:
function getPos(el, rel)
{
var x=0, y=0;
do {
x += el.offsetLeft;
y += el.offsetTop;
el = el.offsetParent;
}
while (el != rel)
return {x:x, y:y};
}
If is just used as getPos(myElem) will return global position. If a second element is included as an argument (i.e. getPos(myElem, someAncestor)) that is an ancestor/parent of the first (at least somewhere up the chain) then it will give the position relative to that ancestor. If rel is not given (i.e. is left undefined), then it purposefully uses != instead of !== because it should stop when el gets to null and rel is undefined as well, so the non-strict equality is purposeful, don't change it. It also returns an object, since that's a bit more readable in usage than an array (and so you can do something like getPos().x).
This is derived from Pacerier's solution, so if you're gonna upvote this, consider upvoting his too.
Related
I have a div that floats to the left, and some text that wraps around it.
I want to be able to move the div up and down with javascript. As I do so, I want the text to flow around it (above it and below it as needed).
I hope it is clear what I am trying to achieve. Something like how text might behave if you position an object in a word document.
I have already looked into it a bit, and the conclusion I am coming to is that it is only possible if the div is contained within the same parent element as the text. If you want to move the div up and down, you would have to move its position within the text itself (e.g. take a sentence from behind and move it infront).
Doing something like giving it a top margin simply extends the block which text has to flow around, and making it position relative means that the text flows around its original position, and the relatively positioned div overlaps the text. It all makes perfect sense, but makes it very difficult to achieve what I want.
Is there anything in html/css that might allow for what I want, or any plugin that does what I have described.
Thanks
Marcels link is v interesting actually.
I might be tempted to fudge it and get a close approximation, since its simple and gets you half way there.
http://jsfiddle.net/tromm/e3YHb/
var img = $('#whale');
var nextP = img.next();
var previousP = img.prev();
$('#move-down').click(function() {
nextP = img.next();
img.detach();
img.insertAfter(nextP)
});
$('#move-up').click(function() {
previousP = img.prev();
img.detach();
img.insertBefore(previousP)
});
Essentially moving the through the s. in the fiddle i also floated the image so it almost-kinda-sorta looks like its doing what you expect. :)
This is what I was working on (if it is of any use to anyone). It attempts to move the element though the text, checking offsetTop to see when it has actually moved to a new line (I only did the down motion). I would then have moved on to figure out how to call this multiple times when dragging an element. Too much effort for what I need (I would have to take into account elements within elements), and it looks like it will be in html one day anyway, so I will just accept Tom's solution.
function down(el) {
var parentDiv = el.parentNode;
var next = el.nextSibling;
var prev = el.previousSibling;
var match, word;
var offset = el.offsetTop;
while ((match = /^\s*\S+\s*/.exec(next.nodeValue)) !== null && el.offsetTop === offset) {
word = match[0];
if (prev === null) {
prev = parentDiv.insertBefore(document.createTextNode(''), el);
}
prev.nodeValue += word;
next.nodeValue = next.nodeValue.substring(word.length);
}
if (el.offsetTop !== offset) {
return;
}
var nextDiv = findNextDiv(parentDiv);
if (nextDiv === null) {
return;
}
parentDiv.removeChild(el);
if (nextDiv.firstChild === null) {
nextDiv.appendChild(el);
}
else {
nextDiv.insertBefore(el, nextDiv.firstChild);
if (el.offsetTop === offset) {
down(el);
}
}
}
function findNextDiv(el) {
var next;
while ((next = el.nextSibling) !== null) {
if (next.tagName === 'DIV') {
return next;
}
el = next;
}
return null;
}
next.onclick = function() {
move('left', li_items[0]);
};
var move = function(direction, el) {
pos = el.style[direction].split('px')[0];
pos = parseInt(pos, 10) + 10;
el.style[direction] = pos + 'px';
};
I'm using the simple code above to try and move an element. Now when I breakpoint on this, the value of el.style[direction] is: " ". So then when i try to do anything with it, it breaks. Why would this be? Isn't style.left supposed to return an integer?
Why would this be?
Presumably because it hasn't been set to anything.
Isn't style.left supposed to return an integer?
No. It is supposed to return a string containing the value of the CSS left property as set directly on the element (either by setting the JS property itself or by using a style attribute). It does not get a value from the cascade and it should only be an integer if the value is 0 (since all other lengths require units).
See How to get computed style of a HTMLElement if you want to get the computed value for the property rather than what I described in the previous paragraph.
style provides the original style as calculated from the CSS, not the updated and possibly dynamic style. You probably want currentStyle instead.
next.onclick = function() {
move('left', li_items[0]);
};
var move = function(direction, el) {
var lft = document.defaultView.getComputedStyle(el)[direction];
pos = parseFloat(lft);
pos = parseInt(pos, 10) + 10;
el.style[direction] = pos + 'px';
};
Note: like Elliot said you'll have to get the currentStyle/computedStyle. Here's a way to make it cross-browser, however when applying styles via JS, this is one good case where some sort of framework (eg Prototype [Scriptaculous], jQuery) would be useful.
Just a comment.
In your code:
> pos = el.style[direction].split('px')[0];
> pos = parseInt(pos, 10) + 10;
The split in the first line is superfluous, in the second line parseInt will convert (say) 10px to the number 10 just as effectively (and more efficiently) than what you have.
pos = parseInt(el.style[direction], 10);
On my web page I have two divs, A and B:
The DOM looks as follows (simplified). The main thing to note here, is that the divs are on the same level in the DOM hierarchy and they are not ordered by anything. Also, the outer div does not only contain A and B but also more divs C,D,E etc. B may not necessarily be overlapped by A. It could also be C, lying behind A.
<div>
<div style="...">B</div>
<div style="...">A</div>
</div>
A click handler is attached to the outer div, capturing mouse click events on A or B in the bubbling phase. A click into the intersection of A and B will cause an click event on A which bubbles up to my click handler that is now fired.
Now the problem: Under certain conditions the handler decides that the event should not be handled by A but should belong to B. If an event should be handled by B, the same click handler will be fired, but the event objects currentTarget property will be B instead of A.
How can I achieve this?
I've already looked at css3 pointer-handler and some event dispatching methods in JS, but could not really come up with a solution here.
Possible solution 1 (not cross-browser compatible) ?:
I think i might be possible to use the pointer-events css3 property. If the handler decides that the event should be handled by B, it sets the pointer-events to none. Then, it retriggers the mouse click. Question here: Is it possible to retrigger a mouse click with only the coordinates, not specifying a specific element?
Anyway, the support of pointer-events is limited: http://caniuse.com/pointer-events
You can try to pass the event to B (using jQuery):
(pseudo-code)
var $A = $('#A'), $B = $('#B');
$B.on('click', function () {
console.log('B clicked');
});
$('#outer').on('click', function (evt) {
if (Math.random() < 0.1) { // << pseudo condition
$B.trigger(evt);
}
});
http://jsfiddle.net/hzErh/
Update:
using a function like this:
// this function should (did not enough testing) return all elements at a given
// point in the order of top to bottom.
var elementsFromPoint = function (x, y, root) {
var buffer = [], result = [], el, i = 0, lim;
while ((el = document.elementFromPoint(x, y)) && [root, document.body, document.body.parentNode].indexOf(el) < 0) {
result.push(el);
buffer.push({el: el, visibility: el.style.visibility});
el.style.visibility = 'hidden';
}
for (lim = buffer.length; i < lim; i += 1) {
buffer[i].el.style.visibility = buffer[i].visibility;
}
return result;
};
you can get the elements for a given x/y coordinate. It's a bit hacky and needs more testing though.
$('#outer').on('click', function (evt) {
var elements = elementsFromPoint(
evt.pageX + $(this).offset().left,
evt.pageY + $(this).offset().top,
this
);
console.log(elements);
});
demo: http://jsfiddle.net/hzErh/1/
Update 2: this version should give better performance for many elements:
// this function should (did not enough testing) return all elements at a given
// point, though the order is not representative for the visible order.
var elementsFromPoint = function (root, x, y) {
var result = [], i = 0, lim = root.childNodes.length, bb;
for (; i < lim; i += 1) {
bb = root.childNodes[i].getBoundingClientRect();
if (bb.left <= x && x <= bb.right && bb.top <= y && y <= bb.bottom) {
result.push(root.childNodes[i]);
}
}
return result;
};
demo: http://jsfiddle.net/CTdZp/2/
I'm trying to manipulate a div element. Although I have defined certain property values, I can't seem to get the initial values.
Here's the problem: Fiddle
I'm at the end of my rope... Thanks.
I think you have to use document.styleSheets, find your stylesheet, then loop through the cssRules(IE rules) list to match a selector to "#viewbase" and then you can access your style properties like you would have with inline styles, since the style info in there is also a CSSStyleDeclaration ( so you can use element.style.left and so on).
Sidenode: getComputedStyle doesn't work on most versions of IE. IE has currentStyle properties for each element, but it's not the same thing. Maybe a combination of both will also work.
UPDATE
Managed to get the initial positions, if that's what you need, you can reassign these values if i remember well.
function trigger(e) {
item = this;
if (!e) var e = window.event;
if (e.pageX || e.pageY) {
x1 = e.pageX;
y1 = e.pageY;
}
else if (e.clientX || e.clientY) {
x1 = e.clientX;
y1 = e.clientY;
}
document.getElementById("ex").innerHTML = x1;
document.getElementById("wye").innerHTML= y1;
// the stylesheet you defined, in a standalone context, it might
// have another index, (i.e. 0 if it's the only one)
list = document.styleSheets[2].cssRules;
for(var i=0;i<list.length;i++)
//match #viewbase
if(list[i].selectorText.toLowerCase() == "#"+item.id)
{ item = list[i];
break;
}
document.getElementById("xval").innerHTML = item.style.left;
document.getElementById("yval").innerHTML = item.style.top;
document.getElementById("parseX").innerHTML = parseInt(item.style.left, 10);
document.getElementById("parseY").innerHTML = parseInt(item.style.top, 10);
document.getElementById("debug").innerHTML = 'clicked!';
document.onmouseup = release;
}
The other version would be to use the following
var styleDef = window.getComputedStyle(item) || item.currentStyle;
document.getElementById("xval").innerHTML = styleDef.left;
document.getElementById("yval").innerHTML = styleDef.top;
The problem is that your style is not defined within the html but comes from css. In that case you need to use getComputedStyle to retrieve the styling information. Do something like
document.defaultView.getComputedStyle(item,null)
rather than item.style.
How about using item.offsetLeft and item.offsetTop. This will include margins and padding, but you should be able to easily compensate for this fact. Here is an updated JSFiddle.
document.getElementById("xval").innerHTML = item.offsetLeft;
document.getElementById("yval").innerHTML = item.offsetTop;
Again, in this case, these calls return 10, 10 respectively, instead of 0, 0, which you are looking for. That said, is this enough to move you past your issue?
I can't seem to figure out how to get the offsetTop of an element within a table. It works fine on elements outside tables, but all of the elements within a table return the same result, and it's usually at the top of the page. I tried this in Firefox and Chrome. How do I get the offsetTop of an element in a table?
offsetTop returns a value relative to offsetParent; you need to recursively add offsetParent.offsetTop through all of the parents until offsetParent is null. Consider using jQuery's offset() method.
EDIT: If you don't want to use jQuery, you can write a method like this (untested):
function offset(elem) {
if(!elem) elem = this;
var x = elem.offsetLeft;
var y = elem.offsetTop;
while (elem = elem.offsetParent) {
x += elem.offsetLeft;
y += elem.offsetTop;
}
return { left: x, top: y };
}