What's the proper way to get the position of an element on the page relative to the viewport (rather than the document). jQuery.offset
function seemed promising:
Get the current coordinates of the first element, or set the
coordinates of every element, in the set of matched elements, relative
to the document.
But that's relative to the document. Is there an equivalent method that returns offsets relative to the viewport?
Look into the Dimensions plugin, specifically scrollTop()
/scrollLeft()
. Information can be found at http://api.jquery.com/scrollTop.
The easiest way to determine the size and position of an element is to call its
getBoundingClientRect() method. This method returns element positions in viewport coordinates. It expects no arguments and returns an object with
properties left, right, top, and bottom. The left and top properties give the X and Y
coordinates of the upper-left corner of the element and the right and bottom properties
give the coordinates of the lower-right corner.
element.getBoundingClientRect(); // Get position in viewport coordinates
Supported everywhere.
Here are two functions to get the page height and the scroll amounts (x,y) without the use of the (bloated) dimensions plugin:
// getPageScroll() by quirksmode.com
function getPageScroll() {
var xScroll, yScroll;
if (self.pageYOffset) {
yScroll = self.pageYOffset;
xScroll = self.pageXOffset;
} else if (document.documentElement && document.documentElement.scrollTop) {
yScroll = document.documentElement.scrollTop;
xScroll = document.documentElement.scrollLeft;
} else if (document.body) {// all other Explorers
yScroll = document.body.scrollTop;
xScroll = document.body.scrollLeft;
}
return new Array(xScroll,yScroll)
}
// Adapted from getPageSize() by quirksmode.com
function getPageHeight() {
var windowHeight
if (self.innerHeight) { // all except Explorer
windowHeight = self.innerHeight;
} else if (document.documentElement && document.documentElement.clientHeight) {
windowHeight = document.documentElement.clientHeight;
} else if (document.body) { // other Explorers
windowHeight = document.body.clientHeight;
}
return windowHeight
}
jQuery.offset
needs to be combined with scrollTop
and scrollLeft
as shown in this diagram:
Demo:
function getViewportOffset($e) {
var $window = $(window),
scrollLeft = $window.scrollLeft(),
scrollTop = $window.scrollTop(),
offset = $e.offset(),
rect1 = { x1: scrollLeft, y1: scrollTop, x2: scrollLeft + $window.width(), y2: scrollTop + $window.height() },
rect2 = { x1: offset.left, y1: offset.top, x2: offset.left + $e.width(), y2: offset.top + $e.height() };
return {
left: offset.left - scrollLeft,
top: offset.top - scrollTop,
insideViewport: rect1.x1 < rect2.x2 && rect1.x2 > rect2.x1 && rect1.y1 < rect2.y2 && rect1.y2 > rect2.y1
};
}
$(window).on("load scroll resize", function() {
var viewportOffset = getViewportOffset($("#element"));
$("#log").text("left: " + viewportOffset.left + ", top: " + viewportOffset.top + ", insideViewport: " + viewportOffset.insideViewport);
});
body { margin: 0; padding: 0; width: 1600px; height: 2048px; background-color: #CCCCCC; }
#element { width: 384px; height: 384px; margin-top: 1088px; margin-left: 768px; background-color: #99CCFF; }
#log { position: fixed; left: 0; top: 0; font: medium monospace; background-color: #EEE8AA; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<!-- scroll right and bottom to locate the blue square -->
<div id="element"></div>
<div id="log"></div>
Here is a function that calculates the current position of an element within the viewport:
/**
* Calculates the position of a given element within the viewport
*
* @param {string} obj jQuery object of the dom element to be monitored
* @return {array} An array containing both X and Y positions as a number
* ranging from 0 (under/right of viewport) to 1 (above/left of viewport)
*/
function visibility(obj) {
var winw = jQuery(window).width(), winh = jQuery(window).height(),
elw = obj.width(), elh = obj.height(),
o = obj[0].getBoundingClientRect(),
x1 = o.left - winw, x2 = o.left + elw,
y1 = o.top - winh, y2 = o.top + elh;
return [
Math.max(0, Math.min((0 - x1) / (x2 - x1), 1)),
Math.max(0, Math.min((0 - y1) / (y2 - y1), 1))
];
}
The return values are calculated like this:
Usage:
visibility($('#example')); // returns [0.3742887830933581, 0.6103752759381899]
Demo:
function visibility(obj) {var winw = jQuery(window).width(),winh = jQuery(window).height(),elw = obj.width(),
elh = obj.height(), o = obj[0].getBoundingClientRect(),x1 = o.left - winw, x2 = o.left + elw, y1 = o.top - winh, y2 = o.top + elh; return [Math.max(0, Math.min((0 - x1) / (x2 - x1), 1)),Math.max(0, Math.min((0 - y1) / (y2 - y1), 1))];
}
setInterval(function() {
res = visibility($('#block'));
$('#x').text(Math.round(res[0] * 100) + '%');
$('#y').text(Math.round(res[1] * 100) + '%');
}, 100);
#block { width: 100px; height: 100px; border: 1px solid red; background: yellow; top: 50%; left: 50%; position: relative;
} #container { background: #EFF0F1; height: 950px; width: 1800px; margin-top: -40%; margin-left: -40%; overflow: scroll; position: relative;
} #res { position: fixed; top: 0; z-index: 2; font-family: Verdana; background: #c0c0c0; line-height: .1em; padding: 0 .5em; font-size: 12px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="res">
<p>X: <span id="x"></span></p>
<p>Y: <span id="y"></span></p>
</div>
<div id="container"><div id="block"></div></div>
I found that the answer by cballou was no longer working in Firefox as of Jan. 2014. Specifically, if (self.pageYOffset)
didn't trigger if the client had scrolled right, but not down - because 0
is a falsey number. This went undetected for a while because Firefox supported document.body.scrollLeft
/Top
, but this is no longer working for me (on Firefox 26.0).
Here's my modified solution:
var getPageScroll = function(document_el, window_el) {
var xScroll = 0, yScroll = 0;
if (window_el.pageYOffset !== undefined) {
yScroll = window_el.pageYOffset;
xScroll = window_el.pageXOffset;
} else if (document_el.documentElement !== undefined && document_el.documentElement.scrollTop) {
yScroll = document_el.documentElement.scrollTop;
xScroll = document_el.documentElement.scrollLeft;
} else if (document_el.body !== undefined) {// all other Explorers
yScroll = document_el.body.scrollTop;
xScroll = document_el.body.scrollLeft;
}
return [xScroll,yScroll];
};
Tested and working in FF26, Chrome 31, IE11. Almost certainly works on older versions of all of them.