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.
Related
I'm working on a site for a client and trying to implement custom parallax functionality. I have used the following code -
var inView = function(element) {
// get window height
var windowHeight = window.innerHeight;
// Get Element Height
var elementHeight = element.clientHeight;
// get number of pixels that the document is scrolled
var scrollY = window.scrollY || window.pageYOffset;
// get current scroll position (distance from the top of the page to the bottom of the current viewport)
var scrollPosition = scrollY + windowHeight;
var elementPosition = element.getBoundingClientRect().top + scrollY;
var elementScrolled = elementPosition + element.clientHeight + windowHeight
// is scroll position greater than element position? (is element in view?)
if (scrollPosition > elementPosition && scrollPosition < elementScrolled) {
return true;
}
return false;
}
// Get all the elements to be parallaxed
const parallaxElements = {
element: document.querySelectorAll('#header-image img'),
ratio: 0.25
}
// The parallax function
const parallax = elements => {
let items = [...elements.element],
itemRatio = elements.ratio
if ('undefined' !== items && items.length > 0 ) {
items.forEach( item => {
if ( inView(item) == true ) {
item.style.transform = 'translate3d(0, ' + (itemRatio * (window.innerHeight - item.getBoundingClientRect().top)) + 'px ,0)'
}
})
}
}
//If element is in viewport, set its position
parallax(parallaxElements)
//Call the function on scroll
window.onscroll = () => {
parallax(parallaxElements)
}
It's working ok except that when the page is loaded initially and the user starts scrolling, the position of element (#header-image img in this case) changes abruptly. I did some digging and noticed that the value of getBoundingClientRect().top is causing the issue.
When the page is loaded, it has some value, and as soon as the user starts scrolling, it abruptly changes to another value.
I am not able to figure out why this is happening. getBoundingClientRect().top is supposed to get the value of element from top of viewport, right?
Any help is greatly appreciated. Thanks!
Pls check the screenshot of inspect element here -
https://i.stack.imgur.com/RYDvK.jpg
I have implemented an onscroll function for a div that worked like this:
window.onscroll = function () {
document.getElementById("faceExtractor").style.top = (($(window).scrollTop()) + 50) + "px";
};
That worked fine. Now I made the div element also draggable, which means after someone dragged the div a but down and then scrolled the div was put again 50px below the top of the page due to the onscroll function. I wanted to solve that, so that the div keeps it position while scrolling even when someone drageed it a a specific position and I tried this:
window.onscroll = function () {
topDistance = document.getElementById("faceExtractor").getBoundingClientRect().top;
if (topDistance < 50){
document.getElementById("faceExtractor").style.top = (($(window).scrollTop()) + 50) + "px";
} else {
var gap = ($(window).scrollTop()) + topDistance;
document.getElementById("faceExtractor").style.top = gap + "px";
}
};
But then the onscroll doesn't work at all. How can I modify my onscroll so that the div keeps it position and is not always put to the top of the page or a static position?
window.onscroll = function () {
positionFromTop = document.getElementById("faceExtractor").offsetTop;
topDistance = document.body.parentElement.scrollTop+positionFromTop;
if (topDistance < 50){
document.getElementById("faceExtractor").style.top = topDistance+ "px";
} else {
var gap = topDistance;
document.getElementById("faceExtractor").style.top = gap + "px";
}
};
Try this method. And, give 'position:absolute' for 'faceExtractor' Div.
Could you please tell me how how to highlight bottom li when user scrolling in a div? I have one container div, in which there are four divs. In the footer I also have four li (first, second, third,fourth). I want to select the li (background become red)when the user scrolls the respectively div's.
Example
When the code runs, the first li should be selected it background become red because the first div is in the view port. If the user scrolls and moves to the second div, the second li should be selected. And so on.
I tried like that
https://jsbin.com/giwizufotu/edit?html,css,js,output
(function(){
'use strict';
$(function(){
$( "#container" ).scroll(function() {
console.log('scrlling');
if (elementInViewport2($('#first'))) {
// The element is visible, do something
console.log('first visible')
} else {
console.log('second visible')
}
});
})
function elementInViewport2(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
);
}
})()
I don't want to use plugin
https://jsbin.com/borohoheji/edit?html,css,js,console,output
Look at what i did i check if the element is visible with .is(':visible')
You can work from there and do exactly what you want
Change your code to:
(function(){
'use strict';
$(function(){
$( "#container" ).scroll(function() {
console.log('scrlling');
if (elementInViewport($('#first'))) {
// The element is visible, do something
console.log('first visible')
} else {
console.log('second visible')
}
});
$( "#container >div" ).hover(
function() {
$(this).css('color', 'yellow');
});
})
First, do the following :
give all the text divs a classname eg 'para', to make them more readily selectable as a collection.
establish a ul.fC li.active {...} directive in your style sheet to give the desired visual effect.
Then :
(function() {
'use strict';
$(function() {
var $container = $("#container"),
$paras = $container.children(".para"), // the four text divs.
$listElements = $(".footer ul.fC li"), // the four li elements in the footer.
oldIndex = -1;
$container.scroll(function() {
var index = $paras.index($paras.filter(visibleY).eq(0)); // position of the first visible text div.
if(index !== oldIndex) { // avoid unnecessary work
$listElements.eq(oldIndex).removeClass('active'); // remove highlight
$listElements.eq(index).addClass('active'); // add highlight
oldIndex = index; // remember index for next event turn
}
}).trigger('scroll');
function visibleY() {
// based on http://stackoverflow.com/a/21627295/3478010
var el = this; // because function is called as a .filter() callback.
var rect = el.getBoundingClientRect(),
top = rect.top,
height = rect.height,
el = el.parentNode;
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);
// Check its within the document viewport
return top <= document.documentElement.clientHeight;
};
});
})();
As written above, the change of style will happen in response to paras exiting/entering the container's top edge.
The behaviour can be changed to respond to paras exiting/entering the container's bottom edge by replacing :
var index = $paras.index($paras.filter(visibleY).eq(0)); // position of the first visible para.
with :
var index = $paras.index($paras.filter(visibleY).last()); // position of the last visible para.
Choose whichever is more desirable.
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... */ };
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/