Check if element is visible on screen [duplicate] - javascript

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
jQuery - Check if element is visible after scroling
I'm trying to determine if an element is visible on screen. In order to to this, I'm trying to find the element's vertical position using offsetTop, but the value returned is not correct. In this case, the element is not visible unless you scroll down. But despite of this, offsetTop returns a value of 618 when my screen height is 703, so according to offsetTop the element should be visible.
The code I'm using looks like this:
function posY(obj)
{
var curtop = 0;
if( obj.offsetParent )
{
while(1)
{
curtop += obj.offsetTop;
if( !obj.offsetParent )
{
break;
}
obj = obj.offsetParent;
}
} else if( obj.y )
{
curtop += obj.y;
}
return curtop;
}
Thank you in advance!

--- Shameless plug ---
I have added this function to a library I created
vanillajs-browser-helpers: https://github.com/Tokimon/vanillajs-browser-helpers/blob/master/inView.js
-------------------------------
Intersection Observer
In modern browsers you can use the IntersectionObserver which detects where an element is on the screen or compared to a parent.
The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.
Today I would probably lean toward this API if I need to detect and react to when an element has entered or exited the screen.
But for a quick test/lookup when you just want to verify if an emelemt is currently on screen I would go with the version just below using the getBoundingClientRect.
Using getBoundingClientRect
Short version
This is a lot shorter and should do it as well:
function checkVisible(elm) {
var rect = elm.getBoundingClientRect();
var viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
return !(rect.bottom < 0 || rect.top - viewHeight >= 0);
}
with a fiddle to prove it: http://jsfiddle.net/t2L274ty/1/
Longer version
And a version with threshold and mode included:
function checkVisible(elm, threshold, mode) {
threshold = threshold || 0;
mode = mode || 'visible';
var rect = elm.getBoundingClientRect();
var viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
var above = rect.bottom - threshold < 0;
var below = rect.top - viewHeight + threshold >= 0;
return mode === 'above' ? above : (mode === 'below' ? below : !above && !below);
}
and with a fiddle to prove it: http://jsfiddle.net/t2L274ty/2/
A more traditional way to do it
As BenM stated, you need to detect the height of the viewport + the scroll position to match up with your top position. The function you are using is ok and does the job, though its a bit more complex than it needs to be.
If you don't use jQuery then the script would be something like this:
function posY(elm) {
var test = elm, top = 0;
while(!!test && test.tagName.toLowerCase() !== "body") {
top += test.offsetTop;
test = test.offsetParent;
}
return top;
}
function viewPortHeight() {
var de = document.documentElement;
if(!!window.innerWidth)
{ return window.innerHeight; }
else if( de && !isNaN(de.clientHeight) )
{ return de.clientHeight; }
return 0;
}
function scrollY() {
if( window.pageYOffset ) { return window.pageYOffset; }
return Math.max(document.documentElement.scrollTop, document.body.scrollTop);
}
function checkvisible( elm ) {
var vpH = viewPortHeight(), // Viewport Height
st = scrollY(), // Scroll Top
y = posY(elm);
return (y > (vpH + st));
}
Using jQuery is a lot easier:
function checkVisible( elm, evalType ) {
evalType = evalType || "visible";
var vpH = $(window).height(), // Viewport Height
st = $(window).scrollTop(), // Scroll Top
y = $(elm).offset().top,
elementHeight = $(elm).height();
if (evalType === "visible") return ((y < (vpH + st)) && (y > (st - elementHeight)));
if (evalType === "above") return ((y < (vpH + st)));
}
This even offers a second parameter. With "visible" (or no second parameter) it strictly checks whether an element is on screen. If it is set to "above" it will return true when the element in question is on or above the screen.
See in action: http://jsfiddle.net/RJX5N/2/
I hope this answers your question.

Could you use jQuery, since it's cross-browser compatible?
function isOnScreen(element)
{
var curPos = element.offset();
var curTop = curPos.top;
var screenHeight = $(window).height();
return (curTop > screenHeight) ? false : true;
}
And then call the function using something like:
if(isOnScreen($('#myDivId'))) { /* Code here... */ };

Related

Start jQuery animation when content in visible viewport [duplicate]

I'm loading elements via AJAX. Some of them are only visible if you scroll down the page. Is there any way I can know if an element is now in the visible part of the page?
This should do the trick:
function isScrolledIntoView(elem)
{
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elemTop = $(elem).offset().top;
var elemBottom = elemTop + $(elem).height();
return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
}
Simple Utility Function
This will allow you to call a utility function that accepts the element you're looking for and if you want the element to be fully in view or partially.
function Utils() {
}
Utils.prototype = {
constructor: Utils,
isElementInView: function (element, fullyInView) {
var pageTop = $(window).scrollTop();
var pageBottom = pageTop + $(window).height();
var elementTop = $(element).offset().top;
var elementBottom = elementTop + $(element).height();
if (fullyInView === true) {
return ((pageTop < elementTop) && (pageBottom > elementBottom));
} else {
return ((elementTop <= pageBottom) && (elementBottom >= pageTop));
}
}
};
var Utils = new Utils();
Usage
var isElementInView = Utils.isElementInView($('#flyout-left-container'), false);
if (isElementInView) {
console.log('in view');
} else {
console.log('out of view');
}
This answer in Vanilla:
function isScrolledIntoView(el) {
var rect = el.getBoundingClientRect();
var elemTop = rect.top;
var elemBottom = rect.bottom;
// Only completely visible elements return true:
var isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight);
// Partially visible elements return true:
//isVisible = elemTop < window.innerHeight && elemBottom >= 0;
return isVisible;
}
Using IntersectionObserver API
(native in modern browsers)
It's easy & efficient to determine if an element is visible in the viewport, or in any scrollable container, by using an observer.
The need to attach a scroll event and manually checking on the event callback is eliminated, which is more efficient:
// define an observer instance
var observer = new IntersectionObserver(onIntersection, {
root: null, // default is the viewport
threshold: .5 // percentage of target's visible area. Triggers "onIntersection"
})
// callback is called on intersection change
function onIntersection(entries, opts){
entries.forEach(entry =>
entry.target.classList.toggle('visible', entry.isIntersecting)
)
}
// Use the observer to observe an element
observer.observe( document.querySelector('.box') )
// To stop observing:
// observer.unobserve(entry.target)
span{ position:fixed; top:0; left:0; }
.box{ width:100px; height:100px; background:red; margin:1000px; transition:.75s; }
.box.visible{ background:green; border-radius:50%; }
<span>Scroll both Vertically & Horizontally...</span>
<div class='box'></div>
Supported by modern browsers, including mobile browsers. Not supported in IE - View browsers support table
Update: use IntersectionObserver
The best method I have found so far is the jQuery appear plugin. Works like a charm.
Mimics a custom "appear" event, which fires when an element scrolls into view or otherwise becomes visible to the user.
$('#foo').appear(function() {
$(this).text('Hello world');
});
This plugin can be used to prevent unnecessary requests for content that's hidden or outside the viewable area.
Here's my pure JavaScript solution that works if it's hidden inside a scrollable container too.
Demo here (try resizing the window too)
var visibleY = function(el){
var rect = el.getBoundingClientRect(), top = rect.top, height = rect.height,
el = el.parentNode
// Check if bottom of the element is off the page
if (rect.bottom < 0) return false
// Check its within the document viewport
if (top > document.documentElement.clientHeight) return false
do {
rect = el.getBoundingClientRect()
if (top <= rect.bottom === false) return false
// Check if the element is out of view due to a container scrolling
if ((top + height) <= rect.top) return false
el = el.parentNode
} while (el != document.body)
return true
};
EDIT 2016-03-26: I've updated the solution to account for scrolling past the element so it's hidden above the top of the scroll-able container.
EDIT 2018-10-08: Updated to handle when scrolled out of view above the screen.
Plain vanilla to check if element (el) is visible in scrollable div (holder)
function isElementVisible (el, holder) {
holder = holder || document.body
const { top, bottom, height } = el.getBoundingClientRect()
const holderRect = holder.getBoundingClientRect()
return top <= holderRect.top
? holderRect.top - top <= height
: bottom - holderRect.bottom <= height
}
Usage with jQuery:
var el = $('tr:last').get(0);
var holder = $('table').get(0);
var isVisible = isElementVisible(el, holder);
jQuery Waypoints plugin goes very nice here.
$('.entry').waypoint(function() {
alert('You have scrolled to an entry.');
});
There are some examples on the site of the plugin.
How about
function isInView(elem){
return $(elem).offset().top - $(window).scrollTop() < $(elem).height() ;
}
After that you can trigger whatever you want once the element is in view like this
$(window).scroll(function(){
if (isInView($('.classOfDivToCheck')))
//fire whatever you what
dothis();
})
That works for me just fine
Tweeked Scott Dowding's cool function for my requirement-
this is used for finding if the element has just scrolled into the screen i.e it's top edge .
function isScrolledIntoView(elem)
{
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elemTop = $(elem).offset().top;
return ((elemTop <= docViewBottom) && (elemTop >= docViewTop));
}
WebResourcesDepot wrote a script to load while scrolling that uses jQuery some time ago. You can view their Live Demo Here. The beef of their functionality was this:
$(window).scroll(function(){
if ($(window).scrollTop() == $(document).height() - $(window).height()){
lastAddedLiveFunc();
}
});
function lastAddedLiveFunc() {
$('div#lastPostsLoader').html('<img src="images/bigLoader.gif">');
$.post("default.asp?action=getLastPosts&lastPostID="+$(".wrdLatest:last").attr("id"),
function(data){
if (data != "") {
$(".wrdLatest:last").after(data);
}
$('div#lastPostsLoader').empty();
});
};
Most answers here don't take into account that an element can also be hidden because it is scrolled out of view of a div, not only of the whole page.
To cover that possibility, you basically have to check if the element is positioned inside the bounds of each of its parents.
This solution does exactly that:
function(element, percentX, percentY){
var tolerance = 0.01; //needed because the rects returned by getBoundingClientRect provide the position up to 10 decimals
if(percentX == null){
percentX = 100;
}
if(percentY == null){
percentY = 100;
}
var elementRect = element.getBoundingClientRect();
var parentRects = [];
while(element.parentElement != null){
parentRects.push(element.parentElement.getBoundingClientRect());
element = element.parentElement;
}
var visibleInAllParents = parentRects.every(function(parentRect){
var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left);
var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top);
var visiblePercentageX = visiblePixelX / elementRect.width * 100;
var visiblePercentageY = visiblePixelY / elementRect.height * 100;
return visiblePercentageX + tolerance > percentX && visiblePercentageY + tolerance > percentY;
});
return visibleInAllParents;
};
It also lets you specify to what percentage it has to be visible in each direction.
It doesn't cover the possibility that it may be hidden due to other factors, like display: hidden.
This should work in all major browsers, since it only uses getBoundingClientRect. I personally tested it in Chrome and Internet Explorer 11.
isScrolledIntoView is a very needful function, so I tried it, it works for elements not heigher than the viewport, but if the element is bigger as the viewport it does not work. To fix this easily change the condition
return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
to this:
return (docViewBottom >= elemTop && docViewTop <= elemBottom);
See demo here: http://jsfiddle.net/RRSmQ/
This considers any padding, border or margin the element has as well as elements larger than the viewport itself.
function inViewport($ele) {
var lBound = $(window).scrollTop(),
uBound = lBound + $(window).height(),
top = $ele.offset().top,
bottom = top + $ele.outerHeight(true);
return (top > lBound && top < uBound)
|| (bottom > lBound && bottom < uBound)
|| (lBound >= top && lBound <= bottom)
|| (uBound >= top && uBound <= bottom);
}
To call it use something like this:
var $myElement = $('#my-element'),
canUserSeeIt = inViewport($myElement);
console.log(canUserSeeIt); // true, if element is visible; false otherwise
Here is another solution:
<script type="text/javascript">
$.fn.is_on_screen = function(){
var win = $(window);
var viewport = {
top : win.scrollTop(),
left : win.scrollLeft()
};
viewport.right = viewport.left + win.width();
viewport.bottom = viewport.top + win.height();
var bounds = this.offset();
bounds.right = bounds.left + this.outerWidth();
bounds.bottom = bounds.top + this.outerHeight();
return (!(viewport.right < bounds.left || viewport.left > bounds.right || viewport.bottom < bounds.top || viewport.top > bounds.bottom));
};
if( $('.target').length > 0 ) { // if target element exists in DOM
if( $('.target').is_on_screen() ) { // if target element is visible on screen after DOM loaded
$('.log').html('<div class="alert alert-success">target element is visible on screen</div>'); // log info
} else {
$('.log').html('<div class="alert">target element is not visible on screen</div>'); // log info
}
}
$(window).on('scroll', function(){ // bind window scroll event
if( $('.target').length > 0 ) { // if target element exists in DOM
if( $('.target').is_on_screen() ) { // if target element is visible on screen after DOM loaded
$('.log').html('<div class="alert alert-success">target element is visible on screen</div>'); // log info
} else {
$('.log').html('<div class="alert">target element is not visible on screen</div>'); // log info
}
}
});
</script>
See it in JSFiddle
function isScrolledIntoView(elem) {
var docViewTop = $(window).scrollTop(),
docViewBottom = docViewTop + $(window).height(),
elemTop = $(elem).offset().top,
elemBottom = elemTop + $(elem).height();
//Is more than half of the element visible
return ((elemTop + ((elemBottom - elemTop)/2)) >= docViewTop && ((elemTop + ((elemBottom - elemTop)/2)) <= docViewBottom));
}
I needed to check visibility in elements inside scrollable DIV container
//p = DIV container scrollable
//e = element
function visible_in_container(p, e) {
var z = p.getBoundingClientRect();
var r = e.getBoundingClientRect();
// Check style visiblilty and off-limits
return e.style.opacity > 0 && e.style.display !== 'none' &&
e.style.visibility !== 'hidden' &&
!(r.top > z.bottom || r.bottom < z.top ||
r.left > z.right || r.right < z.left);
}
Building off of this great answer, you can simplify it a little further using ES2015+:
function isScrolledIntoView(el) {
const { top, bottom } = el.getBoundingClientRect()
return top >= 0 && bottom <= window.innerHeight
}
If you don't care about the top going out of the window and just care that the bottom has been viewed, this can be simplified to
function isSeen(el) {
return el.getBoundingClientRect().bottom <= window.innerHeight
}
or even the one-liner
const isSeen = el => el.getBoundingClientRect().bottom <= window.innerHeight
There is a plugin for jQuery called inview which adds a new "inview" event.
Here is some code for a jQuery plugin that doesn't use events:
$.extend($.expr[':'],{
inView: function(a) {
var st = (document.documentElement.scrollTop || document.body.scrollTop),
ot = $(a).offset().top,
wh = (window.innerHeight && window.innerHeight < $(window).height()) ? window.innerHeight : $(window).height();
return ot > st && ($(a).height() + ot) < (st + wh);
}
});
(function( $ ) {
$.fn.inView = function() {
var st = (document.documentElement.scrollTop || document.body.scrollTop),
ot = $(this).offset().top,
wh = (window.innerHeight && window.innerHeight < $(window).height()) ? window.innerHeight : $(window).height();
return ot > st && ($(this).height() + ot) < (st + wh);
};
})( jQuery );
I found this in a comment here ( http://remysharp.com/2009/01/26/element-in-view-event-plugin/ ) by a bloke called James
The easiest solution I found for this is Intersection Observer API:
var observer = new IntersectionObserver(function(entries) {
if(entries[0].isIntersecting === true)
console.log('Element has just become visible in screen');
}, { threshold: [0] });
observer.observe(document.querySelector("#main-container"));
I have such a method in my application, but it does not use jQuery:
/* Get the TOP position of a given element. */
function getPositionTop(element){
var offset = 0;
while(element) {
offset += element["offsetTop"];
element = element.offsetParent;
}
return offset;
}
/* Is a given element is visible or not? */
function isElementVisible(eltId) {
var elt = document.getElementById(eltId);
if (!elt) {
// Element not found.
return false;
}
// Get the top and bottom position of the given element.
var posTop = getPositionTop(elt);
var posBottom = posTop + elt.offsetHeight;
// Get the top and bottom position of the *visible* part of the window.
var visibleTop = document.body.scrollTop;
var visibleBottom = visibleTop + document.documentElement.offsetHeight;
return ((posBottom >= visibleTop) && (posTop <= visibleBottom));
}
Edit : This method works well for I.E. (at least version 6). Read the comments for compatibility with FF.
I prefer using jQuery expr
jQuery.extend(jQuery.expr[':'], {
inview: function (elem) {
var t = $(elem);
var offset = t.offset();
var win = $(window);
var winST = win.scrollTop();
var elHeight = t.outerHeight(true);
if ( offset.top > winST - elHeight && offset.top < winST + elHeight + win.height()) {
return true;
}
return false;
}
});
so you can use it this way
$(".my-elem:inview"); //returns only element that is in view
$(".my-elem").is(":inview"); //check if element is in view
$(".my-elem:inview").length; //check how many elements are in view
You can easly add such code inside scroll event function etc. to check it everytime user will scroll the view.
The Javascript code could be written as :
window.addEventListener('scroll', function() {
var element = document.querySelector('#main-container');
var position = element.getBoundingClientRect();
// checking whether fully visible
if(position.top >= 0 && position.bottom <= window.innerHeight) {
console.log('Element is fully visible in screen');
}
// checking for partial visibility
if(position.top < window.innerHeight && position.bottom >= 0) {
console.log('Element is partially visible in screen');
}
});
and in react js written as:
componentDidMount() {
window.addEventListener('scroll', this.isScrolledIntoView);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.isScrolledIntoView);
}
isScrolledIntoView() {
var element = document.querySelector('.element');
var position = element.getBoundingClientRect();
// checking whether fully visible
if (position.top >= 0 && position.bottom <= window.innerHeight) {
console.log('Element is fully visible in screen');
}
// checking for partial visibility
if (position.top < window.innerHeight && position.bottom >= 0) {
console.log('Element is partially visible in screen');
}
}
If you want to tweak this for scrolling item within another div,
function isScrolledIntoView (elem, divID)
{
var docViewTop = $('#' + divID).scrollTop();
var docViewBottom = docViewTop + $('#' + divID).height();
var elemTop = $(elem).offset().top;
var elemBottom = elemTop + $(elem).height();
return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
}
You can make use of jquery plugin "onScreen" to check if the element is in the current viewport when you scroll.
The plugin sets the ":onScreen" of the selector to true when the selector appears on the screen.
This is the link for the plugin which you can include in your project.
"http://benpickles.github.io/onScreen/jquery.onscreen.min.js"
You can try the below example which works for me.
$(document).scroll(function() {
if($("#div2").is(':onScreen')) {
console.log("Element appeared on Screen");
//do all your stuffs here when element is visible.
}
else {
console.log("Element not on Screen");
//do all your stuffs here when element is not visible.
}
});
HTML Code:
<div id="div1" style="width: 400px; height: 1000px; padding-top: 20px; position: relative; top: 45px"></div> <br>
<hr /> <br>
<div id="div2" style="width: 400px; height: 200px"></div>
CSS:
#div1 {
background-color: red;
}
#div2 {
background-color: green;
}
An example based off of this answer to check if an element is 75% visible (i.e. less than 25% of it is off of the screen).
function isScrolledIntoView(el) {
// check for 75% visible
var percentVisible = 0.75;
var elemTop = el.getBoundingClientRect().top;
var elemBottom = el.getBoundingClientRect().bottom;
var elemHeight = el.getBoundingClientRect().height;
var overhang = elemHeight * (1 - percentVisible);
var isVisible = (elemTop >= -overhang) && (elemBottom <= window.innerHeight + overhang);
return isVisible;
}
A more efficient version of this answer:
/**
* Is element within visible region of a scrollable container
* #param {HTMLElement} el - element to test
* #returns {boolean} true if within visible region, otherwise false
*/
function isScrolledIntoView(el) {
var rect = el.getBoundingClientRect();
return (rect.top >= 0) && (rect.bottom <= window.innerHeight);
}
Modified the accepted answer so that the element has to have it's display property set to something other than "none" to quality as visible.
function isScrolledIntoView(elem) {
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elemTop = $(elem).offset().top;
var elemBottom = elemTop + $(elem).height();
var elemDisplayNotNone = $(elem).css("display") !== "none";
return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop) && elemDisplayNotNone);
}
Here is a way to achieve the same thing using Mootools, in horizontal, vertical or both.
Element.implement({
inVerticalView: function (full) {
if (typeOf(full) === "null") {
full = true;
}
if (this.getStyle('display') === 'none') {
return false;
}
// Window Size and Scroll
var windowScroll = window.getScroll();
var windowSize = window.getSize();
// Element Size and Scroll
var elementPosition = this.getPosition();
var elementSize = this.getSize();
// Calculation Variables
var docViewTop = windowScroll.y;
var docViewBottom = docViewTop + windowSize.y;
var elemTop = elementPosition.y;
var elemBottom = elemTop + elementSize.y;
if (full) {
return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom)
&& (elemBottom <= docViewBottom) && (elemTop >= docViewTop) );
} else {
return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
}
},
inHorizontalView: function(full) {
if (typeOf(full) === "null") {
full = true;
}
if (this.getStyle('display') === 'none') {
return false;
}
// Window Size and Scroll
var windowScroll = window.getScroll();
var windowSize = window.getSize();
// Element Size and Scroll
var elementPosition = this.getPosition();
var elementSize = this.getSize();
// Calculation Variables
var docViewLeft = windowScroll.x;
var docViewRight = docViewLeft + windowSize.x;
var elemLeft = elementPosition.x;
var elemRight = elemLeft + elementSize.x;
if (full) {
return ((elemRight >= docViewLeft) && (elemLeft <= docViewRight)
&& (elemRight <= docViewRight) && (elemLeft >= docViewLeft) );
} else {
return ((elemRight <= docViewRight) && (elemLeft >= docViewLeft));
}
},
inView: function(full) {
return this.inHorizontalView(full) && this.inVerticalView(full);
}});
This method will return true if any part of the element is visible on the page. It worked better in my case and may help someone else.
function isOnScreen(element) {
var elementOffsetTop = element.offset().top;
var elementHeight = element.height();
var screenScrollTop = $(window).scrollTop();
var screenHeight = $(window).height();
var scrollIsAboveElement = elementOffsetTop + elementHeight - screenScrollTop >= 0;
var elementIsVisibleOnScreen = screenScrollTop + screenHeight - elementOffsetTop >= 0;
return scrollIsAboveElement && elementIsVisibleOnScreen;
}
There are over 30 answers to this question, and none of them use the amazingly simple, pure JS solution that I have been using. There is no need to load jQuery just to solve this, as many others are pushing.
In order to tell if the element is within the viewport, we must first determine the elements position within the body. We do not need to do this recursively as I once thought. Instead, we can use element.getBoundingClientRect().
pos = elem.getBoundingClientRect().top - document.body.getBoundingClientRect().top;
This value is the Y difference between the top of the object and the top of the body.
We then must tell if the element is within view. Most implementations ask if the full element is within the viewport, so this is what we shall cover.
First of all, the top position of the window is: window.scrollY.
We can get the bottom position of the window by adding the window's height to its top position:
var window_bottom_position = window.scrollY + window.innerHeight;
Lets create a simple function for getting the element's top position:
function getElementWindowTop(elem){
return elem && typeof elem.getBoundingClientRect === 'function' ? elem.getBoundingClientRect().top - document.body.getBoundingClientRect().top : 0;
}
This function will return the element's top position within the window or it will return 0 if you pass it something other than an element with the .getBoundingClientRect() method. This method has been around for a long time, so you shouldn't have to worry about your browser not supporting it.
Now, our element's top position is:
var element_top_position = getElementWindowTop(element);
And or element's bottom position is:
var element_bottom_position = element_top_position + element.clientHeight;
Now we can determine if the element is within the viewport by checking if the element's bottom position is lower than the viewport's top position and by checking if the element's top position is higher than the viewport's bottom position:
if(element_bottom_position >= window.scrollY
&& element_top_position <= window_bottom_position){
//element is in view
else
//element is not in view
From there, you can perform the logic to add or remove an in-view class on your element, which you can then handle later with transition effects in your CSS.
I am absolutely amazed that I did not find this solution anywhere else, but I do believe that this is the cleanest and most effective solution, and it doesn't require you to load jQuery!

How to calculate if 50% of element is in viewport?

I'm currently using getBoundingClientRect() to work out if an element enters the viewport. What I really need to do though is to check whether 50% (or any given percentage) of the element has entered the viewport (i'm checking on scroll). If it is visible then I update some text on the page to say yes, if it isn't then the text says no.
I can't seem to get my head around the logic and its starting to drive me crazy, is anyone able to help?
Current code below!
isBannerInView: function (el, y) {
var _this = this,
elemTop,
elemBottom,
elemHeight,
isVisible;
for (var i = 0; i < el.length; i++) {
var pos = banners.indexOf(el[i]);
elemTop = el[i].getBoundingClientRect().top;
elemBottom = el[i].getBoundingClientRect().bottom;
elemHeight = el[i].getBoundingClientRect().height;
isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight);
_this.updateResults(el[i], pos, isVisible);
};
},
updateResults: function (el, pos, isVisible) {
var isInView = isVisible ? 'Yes' : 'No';
document.querySelectorAll('.results')[0].getElementsByTagName('span')[pos].innerHTML = isInView;
},
jsBin demo
/**
* inViewport jQuery plugin by Roko C.B. stackoverflow.com/questions/24768795/
*
* Returns a callback function with an argument holding
* the current amount of px an element is visible in viewport
* (The min returned value is 0 (element outside of viewport)
* The max returned value is the element height + borders)
*/
;(function($, win) {
$.fn.inViewport = function(cb) {
return this.each(function(i,el) {
function visPx(){
var elH = $(el).outerHeight(),
H = $(win).height(),
r = el.getBoundingClientRect(), t=r.top, b=r.bottom;
return cb.call(el, Math.max(0, t>0? Math.min(elH, H-t) : (b<H?b:H)));
}
visPx();
$(win).on("resize scroll", visPx);
});
};
}(jQuery, window));
$("#banner").inViewport(function( px ){
var h = $(this).height();
var isHalfVisible = px >= h/2;
$(this).css({background: isHalfVisible?"green":"red"});
});
#banner{
height:600px;
background:red;
margin:1500px 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="banner">I'll turn GREEN when I'm more than 50% in viewport</div>
So the plugin is taken from https://stackoverflow.com/a/26831113/383904
P.S: since listening to scroll events is quite expensive you might want to add to the code an events Throttle/Debounce delay method.

Watching for multiple elements without multiple if statements

I have a snippet that on scroll it checks wether an element is in the current viewport.
I now want to add multiple elements into the mix, but I wanted to avoid doing multiple if statements checking for each, I know the following code doesn't work but it is an example of how I would like to do it, is there a way of doing it this way?
var listOfPanels = $('#item2, #item2, #item3, #item4, #item5');
$(window).scroll(function(event) {
// if the element we're actually looking for exists
if (listOfPanels.length){
// check if the element is in the current view using the attached function
// and the event hasn't already fired
if (isElementInViewport(listOfPanels)) {
// do something
}
}
});
try this:
function isElementInViewport(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while(el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top < (window.pageYOffset + window.innerHeight) &&
left < (window.pageXOffset + window.innerWidth) &&
(top + height) > window.pageYOffset &&
(left + width) > window.pageXOffset
);
}
var listOfPanels = $('#item2, #item2, #item3, #item4, #item5');
$(window).scroll(function(event) {
if (listOfPanels.length){
listOfPanels.each(function(){
if (isElementInViewport($(this)[0])) {
console.log($(this).attr('id') + ' in viewport');
}
});
}
});
(isElementInViewport js method brought from: How to tell if a DOM element is visible in the current viewport?)
hope that helps.

How to check if an element is in the view of the user with jquery

I have a very big draggable div in my window. This div has a smaller window.
<div id="draggable-area" style="width:500px;height:500px;overflow:hidden">
<div id="draggable" style="width:5000px;height:5000px">
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
....
</ul>
</div>
</div>
How can I know if the li element is visible in the user viewport (I mean really visible, not in the overflow area)?
To check if an element is in the current veiwport:
function elementInViewport(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while(el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top >= window.pageYOffset &&
left >= window.pageXOffset &&
(top + height) <= (window.pageYOffset + window.innerHeight) &&
(left + width) <= (window.pageXOffset + window.innerWidth)
);
}
(Source)
For a more robust method, I'd recommend Viewport Selectors, which allow you to just do:
$("#elem:in-viewport")
have a look at this plugin
It give's you the option to do the following selectors
$(":in-viewport")
$(":below-the-fold")
$(":above-the-top")
$(":left-of-screen")
$(":right-of-screen")
https://github.com/sakabako/scrollMonitor
var scrollMonitor = require("./scrollMonitor"); // if you're not using require, you can use the scrollMonitor global.
var myElement = document.getElementById("itemToWatch");
var elementWatcher = scrollMonitor.create( myElement );
elementWatcher.enterViewport(function() {
console.log( 'I have entered the viewport' );
});
elementWatcher.exitViewport(function() {
console.log( 'I have left the viewport' );
});
elementWatcher.isInViewport - true if any part of the element is visible, false if not.
elementWatcher.isFullyInViewport - true if the entire element is visible [1].
elementWatcher.isAboveViewport - true if any part of the element is above the viewport.
elementWatcher.isBelowViewport - true if any part of the element is below the viewport.
For a more up-to-date way using getBoundingClientRect():
var isInViewport = function (elem) {
var bounding = elem.getBoundingClientRect();
return (
bounding.top >= 0 &&
bounding.left >= 0 &&
bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
bounding.right <= (window.innerWidth || document.documentElement.clientWidth)
);
};
Returns true if the element in completely in the viewport, and false if it’s not.
var myElem = document.querySelector('#draggable');
if (isInViewport(myElem)) {
// Do something...
}
Complete explanation found here.
My solution is using the given code example, and it will show you an overall idea of how to determine whether the li element is visible. Check out the jsFiddle which contains code from your question.
The jQuery .offset() method allows us to retrieve the current position of an element relative to the document. If you click on an li element inside the draggable, your offset from the top will be between 0 and 500 and the offset from the left should be between 0 and 500. If you call the offset function of an item that is not currently visible, the offset will either be less than 0 or greater than 500 from either the top or left offset.
If its not a daunting task I always like to code what I need from 'scrath' it gives me more flexibility when having to modify or debug, hence why I would recommend looking into using jQuery's offset function instead of using a plugin. If what you are trying to accomplish is fairly simple, using your own function will give you one less library to load.
I m using (checks whether an element is at least partially in the view) following code:
var winSize;
function getWindowSize() {
var winW,WinH = 0;
if (document.body && document.body.offsetWidth) {
winW = document.body.offsetWidth;
winH = document.body.offsetHeight;
}
if (document.compatMode == 'CSS1Compat' &&
document.documentElement &&
document.documentElement.offsetWidth) {
winW = document.documentElement.offsetWidth;
winH = document.documentElement.offsetHeight;
}
if (window.innerWidth && window.innerHeight) {
winW = window.innerWidth;
winH = window.innerHeight;
}
return {w:winW, h:winH};
}
winSize = getWindowSize();
function inView(element) {
var box = element.getBoundingClientRect();
if ((box.bottom < 0) || (box.top > winSize.h)){
return false;
}
return true;
}

Need to calculate offsetRight in javascript

I need to calculate the offsetRight of a DOM object. I already have some rather simple code for getting the offsetLeft, but there is no javascript offsetRight property. If I add the offsetLeft and offsetWidth, will that work? Or is there a better way?
function getOffsetLeft(obj)
{
if(obj == null)
return 0;
var offsetLeft = 0;
var tmp = obj;
while(tmp != null)
{
offsetLeft += tmp.offsetLeft;
tmp = tmp.offsetParent;
}
return offsetLeft;
}
function getOffsetRight(obj)
{
if (obj == null)
return 0;
var offsetRight = 0;
var tmp = obj;
while (tmp != null)
{
offsetRight += tmp.offsetLeft + tmp.offsetWidth;
tmp = tmp.offsetParent;
}
return offsetRight;
}
Cannot be more simpler than this:
let offsetright = window.innerWidth - obj.offsetLeft - obj.offsetWidth
UPDATED POST TO CLARIFY SOME GOTCHAS:
// Assuming these variables:
const elem = document.querySelector('div'),
body = document.body,
html = document.documentElement;
Here are several approaches:
/* Leveraging the viewport AND accounting for possible overflow to the right */
const offsetRight = body.clientWidth - elem.getBoundingClientRect().right
// OR
const offsetRight = body.scrollWidth - elem.getBoundingClientRect().right
// OR
const offsetRight = html.scrollWidth - elem.getBoundingClientRect().right
OR
/*
* Likely the safest option:
* Doesn't depend on the viewport
* Accounts for overflow to the right
* Works even if the user is scrolled to the right some
* NOTE: This ends at the <html> element,
* but you may want to modify the code to end at the <body>
*/
const getOffsetRight = e => {
let left = e.offsetWidth + e.offsetLeft;
const traverse = eRef => {
eRef = eRef.offsetParent; // `.offsetParent` is faster than `.parentElement`
if (eRef) {
left += eRef.offsetLeft;
traverse(eRef);
}
};
traverse(e);
return html.scrollWidth - left;
};
const offsetRight = getOffsetRight(elem);
Import considerations:
Are you using box-sizing: border-box; for all your elements?
Is there margin-left set on the <body> or <html> elements you need to account for?
Does the <body> have a fixed width but centered such as with margin: 0 auto;
Those things will help determine which method to use, and if you want to modify the CSS and/or the JavaScript to account for those use cases.
ORIGINAL POST:
A few choices:
If you want "offsetRight" relative to the viewport, use element.getBoundingClientRect().right;
Your example is good simply subracting the parent width from the element's width + offsetLeft.
Lastly, to be relative to the document, and to speed up traversing (offsetParent):
In this example, I'm positioning a pseudo dropdown element below the
referenced element, but because I'm avoiding some tricky z-index
issues and want to have the element be referenced from the right and
expand out left, I had to append it to the body element, and the get
the "offsetRight" from the original parent.
...
// Set helper defaults
dropdownElem.style.left = 'auto';
dropdownElem.style.zIndex = '10';
// Get the elem and its offsetParent
let elem = dropdownElemContainer;
let elemOffsetParent = elem.offsetParent;
// Cache widths
let elemWidth = elem.offsetWidth;
let elemOffsetParentWidth = 0;
// Set the initial offsets
let top = elem.offsetHeight; // Because I want to visually append the elem at the bottom of the referenced bottom
let right = 0;
// Loop up the DOM getting the offsetParent elements so you don't have to traverse the entire ancestor tree
while (elemOffsetParent) {
top += elem.offsetTop;
elemOffsetParentWidth = elemOffsetParent.offsetWidth;
right += elemOffsetParentWidth - (elem.offsetLeft + elemWidth); // Most important line like your own example
// Move up the DOM
elem = elemOffsetParent;
elemOffsetParent = elemOffsetParent.offsetParent;
elemWidth = elemOffsetParentWidth;
}
// Set the position and show the elem
dropdownElem.style.top = top + 'px';
dropdownElem.style.right = right + 'px';
dropdownElem.style.display = 'block';
//Object references
function getObject(id) {
var object = null;
if (document.layers) {
object = document.layers[id];
} else if (document.all) {
object = document.all[id];
} else if (document.getElementById) {
object = document.getElementById(id);
}
return object;
}
//Get pixel dimensions of screen
function getDimensions(){
var winW = 630, winH = 460;
if (document.body && document.body.offsetWidth) {
winW = document.body.offsetWidth;
winH = document.body.offsetHeight;
}
if (document.compatMode=='CSS1Compat' && document.documentElement && document.documentElement.offsetWidth ) {
winW = document.documentElement.offsetWidth;
winH = document.documentElement.offsetHeight;
}
if (window.innerWidth && window.innerHeight) {
winW = window.innerWidth;
winH = window.innerHeight;
}
return{"width":winW, "height":winH}
}
//Get the location of element
function getOffsetRight(elem){
element=getObject(elem)
var width = element.offsetWidth
var right = 0;
while (element.offsetParent) {
right += element.offsetLeft;
element = element.offsetParent;
}
right += element.offsetLeft;
right = getDimensions()["width"]-right
right -= width
return right
}
This is not bullet-proof but you can usually get the "offsetRight" by calling: getOffsetRight("[object.id]")
If you are interested in using some Js library then try the following functionality of prototype js
http://api.prototypejs.org/dom/element/offset/

Categories