I want to know how to get the X and Y position of HTML elements such as img
and div
in JavaScript relative to the browser window.
if using jQuery, the dimensions plugin is excellent and allows you specify exactly what you want.
e.g.
Relative position, absolute position, absolute position without padding, with padding...
It goes on, let's just say there is a lot you can do with it.
Plus the bonus of using jQuery is it's lightweight file size and easy use, you won't go back to JavaScript without it afterwards.
I successfully used Andy E's solution to position a bootstrap 2 modal depending on what link in a table row a user clicks on. The page is a Tapestry 5 page and javascript below is imported in the java page class.
javascript:
function setLinkPosition(clientId){
var bodyRect = document.body.getBoundingClientRect(),
elemRect = clientId.getBoundingClientRect(),
offset = elemRect.top - bodyRect.top;
offset = offset + 20;
$('#serviceLineModal').css("top", offset);
}
My modal code:
<div id="serviceLineModal" class="modal hide fade add-absolute-position" data-backdrop="static"
tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" style="top:50%;">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
<h3 id="myModalLabel">Modal header</h3>
</div>
<div class="modal-body">
<t:zone t:id="modalZone" id="modalZone">
<p>You selected service line number: ${serviceLineNumberSelected}</p>
</t:zone>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
<!-- <button class="btn btn-primary">Save changes</button> -->
</div>
The link in the loop:
<t:loop source="servicesToDisplay" value="service" encoder="encoder">
<tr style="border-right: 1px solid black;">
<td style="white-space:nowrap;" class="add-padding-left-and-right no-border">
<a t:type="eventLink" t:event="serviceLineNumberSelected" t:context="service.serviceLineNumber"
t:zone="pageZone" t:clientId="modalLink${service.serviceLineNumber}"
onmouseover="setLinkPosition(this);">
<i class="icon-chevron-down"></i> <!-- ${service.serviceLineNumber} -->
</a>
</td>
And the java code in the page class:
void onServiceLineNumberSelected(String number){
checkForNullSession();
serviceLineNumberSelected = number;
addOpenServiceLineDialogCommand();
ajaxResponseRenderer.addRender(modalZone);
}
protected void addOpenServiceLineDialogCommand() {
ajaxResponseRenderer.addCallback(new JavaScriptCallback() {
@Override
public void run(JavaScriptSupport javascriptSupport) {
javascriptSupport.addScript("$('#serviceLineModal').modal('show');");
}
});
}
Hope this helps someone, this post helped out.
The cleanest approach I have found is a simplified version of the technique used by jQuery's offset
. Similar to some of the other answers it starts with getBoundingClientRect
; it then uses the window
and the documentElement
to adjust for scroll position as well as things like the margin on the body
(often the default).
var rect = el.getBoundingClientRect();
var docEl = document.documentElement;
var rectTop = rect.top + window.pageYOffset - docEl.clientTop;
var rectLeft = rect.left + window.pageXOffset - docEl.clientLeft;
var els = document.getElementsByTagName("div");
var docEl = document.documentElement;
for (var i = 0; i < els.length; i++) {
var rect = els[i].getBoundingClientRect();
var rectTop = rect.top + window.pageYOffset - docEl.clientTop;
var rectLeft = rect.left + window.pageXOffset - docEl.clientLeft;
els[i].innerHTML = "<b>" + rectLeft + ", " + rectTop + "</b>";
}
div {
width: 100px;
height: 100px;
background-color: red;
border: 1px solid black;
}
#rel {
position: relative;
left: 10px;
top: 10px;
}
#abs {
position: absolute;
top: 250px;
left: 250px;
}
<div id="rel"></div>
<div id="abs"></div>
<div></div>
While this is very likely to be lost at the bottom of so many answers, the top solutions here were not working for me.
As far as I could tell neither would any of the other answers have helped.
Situation:
In an HTML5 page I had a menu that was a nav element inside a header (not THE header but a header in another element).
I wanted the navigation to stick to the top once a user scrolled to it, but previous to this the header was absolute positioned (so I could have it overlay something else slightly).
The solutions above never triggered a change because .offsetTop was not going to change as this was an absolute positioned element. Additionally the .scrollTop property was simply the top of the top most element... that is to say 0 and always would be 0.
Any tests I performed utilizing these two (and same with getBoundingClientRect results) would not tell me if the top of the navigation bar ever scrolled to the top of the viewable page (again, as reported in console, they simply stayed the same numbers while scrolling occurred).
Solution
The solution for me was utilizing
window.visualViewport.pageTop
The value of the pageTop property reflects the viewable section of the screen, therefore allowing me to track where an element is in reference to the boundaries of the viewable area.
Probably unnecessary to say, anytime I am dealing with scrolling I expect to use this solution to programatically respond to movement of elements being scrolled.
Hope it helps someone else.
IMPORTANT NOTE: This appears to work in Chrome and Opera currently & definitely not in Firefox (6-2018)... until Firefox supports visualViewport I recommend NOT using this method, (and I hope they do soon... it makes a lot more sense than the rest).
UPDATE:
Just a note regarding this solution.
While I still find what I discovered to be very valuable for situations in which "...programmatically respond to movement of elements being scrolled." is applicable. The better solution for the problem that I had was to use CSS to set position: sticky on the element. Using sticky you can have an element stay at the top without using javascript (NOTE: there are times this will not work as effectively as changing the element to fixed but for most uses the sticky approach will likely be superior)
UPDATE01:
So I realized that for a different page I had a requirement where I needed to detect the position of an element in a mildly complex scrolling setup (parallax plus elements that scroll past as part of a message).
I realized in that scenario that the following provided the value I utilized to determine when to do something:
let bodyElement = document.getElementsByTagName('body')[0];
let elementToTrack = bodyElement.querySelector('.trackme');
trackedObjPos = elementToTrack.getBoundingClientRect().top;
if(trackedObjPos > 264)
{
bodyElement.style.cssText = '';
}
Hope this answer is more widely useful now.
/**
*
* @param {HTMLElement} el
* @return {{top: number, left: number}}
*/
function getDocumentOffsetPosition(el) {
var position = {
top: el.offsetTop,
left: el.offsetLeft
};
if (el.offsetParent) {
var parentPosition = getDocumentOffsetPosition(el.offsetParent);
position.top += parentPosition.top;
position.left += parentPosition.left;
}
return position;
}
Thank ThinkingStiff for the answer, this is only another version.
If you are using jQuery, this could be a simple solution:
<script>
var el = $("#element");
var position = el.position();
console.log( "left: " + position.left + ", top: " + position.top );
</script>