How detect which child element is visible after scrolling the parent div? - javascript

I would like to emulate something like "current page" using divs (like a PDF reader)
document.addEventListener("DOMContentLoaded", function(event) {
var container = document.getElementById("container");
container.onscroll = function() {
let position = container.scrollTop;
let divs = document.querySelectorAll('.page');
for (div of divs) {
//???
}
}
});
#container {
width: 400px;
height: 600px;
overflow: auto;
}
.page {
width: 400px;
}
.red {
background-color: red;
height: 600px;
}
.blue {
background-color: blue;
height: 400px;
}
Current page: <span id="page-counter">1</span>
<div id='container'>
<div id="div-1" class="page red"></div>
<div id="div-2" class="page blue"></div>
<div id="div-3" class="page red"></div>
<div id="div-4" class="page blue"></div>
</div>
So, I would like to know the best way to, for example, change span page-counter text to "3" when the third div "appears".
Something like this: https://i.imgur.com/rXQ2Bw8.png
Thanks in advance
Celso

Since this question never tagged jQuery, here's a pure Javascript solution that simulates the behavior you're looking for to the best of my knowledge. The solution calculates the amount of pixels of each child element currently visible within the container. If the amount is bigger or equal to half the size of the container, it assumes this is the page your visitor is looking at.
function getVisibleHeight(element){
const container = document.getElementById("container");
let scrollTop = container.scrollTop;
let scrollBot = scrollTop + container.clientHeight;
let containerRect = container.getBoundingClientRect();
let eleRect = element.getBoundingClientRect();
let rect = {};
rect.top = eleRect.top - containerRect.top,
rect.right = eleRect.right - containerRect.right,
rect.bottom = eleRect.bottom - containerRect.bottom,
rect.left = eleRect.left - containerRect.left;
let eleTop = rect.top + scrollTop;
let eleBot = eleTop + element.offsetHeight;
let visibleTop = eleTop < scrollTop ? scrollTop : eleTop;
let visibleBot = eleBot > scrollBot ? scrollBot : eleBot;
return visibleBot - visibleTop;
}
document.addEventListener("DOMContentLoaded", function(event) {
const container = document.getElementById("container");
const divs = document.querySelectorAll('.page');
container.addEventListener("scroll", () => {
for(let i=0; i<divs.length; i++){
const containerHeight = container.clientHeight;
// Gets the amount of pixels currently visible within the container
let visiblePageHeight = getVisibleHeight(divs[i]);
// If the amount of visible pixels is bigger or equal to half the container size, set page
if(visiblePageHeight >= containerHeight / 2){
document.getElementById('page-counter').innerText = i+1;
}
}
}, false);
});
#container {
width: 400px;
height: 300px;
overflow: auto;
}
.page {
width: 380px;
}
.red {
background-color: red;
height: 300px;
}
.blue {
background-color: blue;
height: 200px;
}
Current page: <span id="page-counter">1</span>
<div id='container'>
<div id="div-1" class="page red"></div>
<div id="div-2" class="page blue"></div>
<div id="div-3" class="page red"></div>
<div id="div-4" class="page blue"></div>
</div>

The general approach here would be to write a function that determines if a given HTML element is in the viewport. You could run the check as the user scrolls. See the snippet below for an example with jQuery. I'm not necessarily saying this is the best way to do this, but it seems to be working. Start scrolling to see the IDs appear.
function isInViewPort(element) {
// Function will determine if any part of the element is in the viewport.
let $el = $("#" + element);
let windowScrollTop = $(window).scrollTop();
let windowHeight = $(window).height();
let windowBottom = windowScrollTop + windowHeight;
let elementTop = $el.offset().top;
let elementOuterHeight = $el.outerHeight();
let elementBottom = elementTop + elementOuterHeight;
let isAboveViewPort = elementBottom < windowScrollTop;
let isBelowViewPort = windowBottom < elementTop;
return !(isAboveViewPort || isBelowViewPort);
}
let currentDiv;
$("#container").on("scroll", function() {
$("#container").find("div").each(function() {
if (isInViewPort(this.id) && currentDiv !== this.id) {
$("#page").html("Current ID is " + this.id)
currentDiv = this.id;
}
});
});
#container {
overflow: auto;
height: 300px;
}
.red {
background-color: red;
height: 600px;
}
.blue {
background-color: blue;
height: 400px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span id="page"></span>
<div id='container'>
<div id="div-1" class="page red"></div>
<div id="div-2" class="page blue"></div>
<div id="div-3" class="page red"></div>
<div id="div-4" class="page blue"></div>
</div>

you can use the is visible feature in jQuery. Just give each div a unique ID or class.
if( $("#uniqueIdHere").is(':visible'))
$(".page3Selector").addClass('active');
and then to remove the active class you could pair it up with an else statement to remove the class of the inactive div.

Related

Check if child element is 100% visible inside a parent div that has overflow hidden

Good day,
I have a dynamic carousel that I want to check if the first slide and the last slide is 100% visible for the user and then it needs to trigger a function, currently I'm using getBoundingClientRect() but it uses the viewport and not the parent div. the parent div is not full width but is 80% of the viewport
This is my code to check the first slide, and it works with viewport:
JavaScript:
function isInViewport(el) {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
const box = document.querySelector('.firstSlide');
const message = document.querySelector('#inView');
console.log(box);
document.addEventListener('click', function () {
const messageText = isInViewport(box) ?
'Yess its in view' :
'Yess its not in view';
message.textContent = messageText;
}, {
passive: true
});
HTML:
<div class="carousel" style="grid-template-columns: repeat(5, 585px); overfow:hidden, width:80%; margin:auto">
<div class="image-container"><img src="image/url" alt=""></div>
<div class="image-container"><img src="image/url" alt=""></div>
<div class="image-container"><img src="image/url" alt=""></div>
<div class="image-container"><img src="image/url" alt=""></div>
<div class="image-container firstSlide"><img src="image/url" alt=""></div>
</div>
<p id="inView">Yess its in view</p>
Is there a way so that it check if the child element is 100% in view of the parent element and not the entire viewport?
There is two ways to solve this issue.
If you want to use getBoundingClientRect you can get the bounding box for the parent element. Calculate where this box is in the viewport. Then instead of comparing where the slide is compared to the viewbox, compare it to the boundingbox of the parent.
(Best practice) The new fancy way. Use the Intersection Observer API, that api can be used to check if elements are in the viewport but can also be used directly to see where a element is compared to a ancestry element. The new api is also running by the browser and will not block the main thread which is really good for performance.
Here is a link to the Intersection Observer Api: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
The following code can determine if one bounding client rectangle is completely inside another.
const d1 = document.getElementById('d1');
const d2 = document.getElementById('d2');
const p1 = document.getElementById('p1');
const p2 = document.getElementById('p2');
const r1 = document.getElementById('r1');
const r2 = document.getElementById('r2');
function isPVisible(element, container)
{
const elRect = element.getBoundingClientRect();
const conRect = container.getBoundingClientRect();
let result = false;
if(elRect.x >= conRect.x && elRect.y >= conRect.y
&& elRect.x + elRect.width <= conRect.x + conRect.width
&& elRect.y + elRect.height <= conRect.y + conRect.height)
{
result = true
}
return result;
}
console.log(isPVisible(p1, d1));
console.log(isPVisible(p2, d2));
#d1
{
width: 40px;
height: 10px;
overflow: hidden;
border: 1px blue solid;
}
#p1
{
width: 50px;
height: 30px;
border: 1px red solid;
}
#d2
{
width: 90px;
height: 50px;
overflow: hidden;
border: 1px blue solid;
}
#p2
{
width: 50px;
height: 30px;
border: 1px red solid;
}
<div id="d1">
<p id="p1">hello</p>
</div>
<div id="d2">
<p id="p2">hello</p>
</div>
<p>
<span id="r1"></span>
<span id="r2"></span>
</p>

Multiple sticky sections with horizontal scrolling

I'm trying to make a page with multiple sticky sections with horizontal scrolling (so when you're scrolling vertically as normal, you're forced to go through the horizontal gallery)
I'm referencing this codepen (https://codepen.io/johnhubler/pen/RwoPRBG) as my JS knowledge is very poor. But, as you can see in the codepen, it is only working in the first sticky section, and the second one stays still.
var windowWidth = window.innerWidth;
var horLength = document.querySelector(".element-wrapper").scrollWidth;
var horLength2 = document.querySelector(".element-wrapper2").scrollWidth;
var distFromTop = document.querySelector(".horizontal-section").offsetTop;
var distFromTop2 = document.querySelector(".horizontal-section2").offsetTop;
var scrollDistance = distFromTop + horLength - windowWidth;
var scrollDistance2 = distFromTop2 + horLength2 - windowWidth;
document.querySelector(".horizontal-section").style.height = horLength + "px";
document.querySelector(".horizontal-section2").style.height = horLength2 + "px";
window.onscroll = function(){
var scrollTop = window.pageYOffset;
if (scrollTop >= distFromTop && scrollTop <= scrollDistance) {
document.querySelector(".element-wrapper").style.transform = "translateX(-"+(scrollTop - distFromTop)+"px)";
}
if (scrollTop >= distFromTop2 && scrollTop <= scrollDistance2) {
document.querySelector(".element-wrapper2").style.transform = "translateX(-"+(scrollTop - distFromTop2)+"px)";
}
}
I'm planning to add around 4 of the same sticky sections, so I'd like to know how to make it work in all of them. If there is a better alternative/resource/etc.(if possible, vanilla JS or something very easy to follow) please let me know.
Thank you
I made an optimized and working version of your code.
This array lists the classes of packaging elements. This way you can add as many galleries as you want by simply adding a new class to the array.
var array = ['.horizontal-section', '.horizontal-section2'];
Example:
var array = ['.horizontal-section', '.horizontal-section2'];
window.onscroll = function () {
var windowWidth = window.innerWidth;
var scrollTop = window.pageYOffset;
array.forEach(el => {
var wrap = document.querySelector(el);
var elWrap = wrap.querySelector(".element-wrapper");
var horLength = elWrap.scrollWidth;
var distFromTop = wrap.offsetTop;
var scrollDistance = distFromTop + horLength - windowWidth;
wrap.style.height = horLength + "px";
if (scrollTop >= distFromTop && scrollTop <= scrollDistance) {
elWrap.style.transform = "translateX(-" + (scrollTop - distFromTop) + "px)";
}
});
}
* {
box-sizing: border-box;
}
body {
padding: 0;
margin: 0;
}
.bumper {
width: 100%;
height: 1800px;
background-color: #f3f3f3;
}
.horizontal-section,
.horizontal-section2 {
padding: 100px 0;
background-color: pink;
}
.sticky-wrapper,
.sticky-wrapper2 {
position: sticky;
top: 100px;
width: 100%;
overflow: hidden;
}
.element-wrapper,
.element-wrapper2 {
position: relative;
display: flex;
}
.element {
width: 500px;
height: 400px;
background-color: purple;
margin: 0 20px 0 0;
flex-shrink: 0;
}
<div class="bumper"></div>
<div class="horizontal-section">
<div class="sticky-wrapper">
<div class="element-wrapper">
<div class="element"></div>
<div class="element"></div>
<div class="element"></div>
<div class="element"></div>
<div class="element"></div>
</div>
</div>
</div>
<div class="bumper"></div>
<div class="horizontal-section2">
<div class="sticky-wrapper">
<div class="element-wrapper">
<div class="element"></div>
<div class="element"></div>
<div class="element"></div>
<div class="element"></div>
<div class="element"></div>
</div>
</div>
</div>
<div class="bumper"></div>

Padding aspect ratio hack, but based on height

Most of you know a trick that makes element scale at a fixed aspect ratio, for example:
<div style="width:100%;position:relative">
<div style="padding-bottom:100%;background:tomato;height:0;">square (1:1)</div>
</div>
(jsfiddle https://jsfiddle.net/0dda7939/ )
I want the same thing, but based on height not based on width.
<div style="height:100%;position:relative">
<div style="??? background:tomato;">square (1:1)</div>
</div>
Is there any way to do this without JS and without using VW/VH units (since parent container might not always be window)?
With JS it can be done easily. The problem with doing by CSS is applying width equal to the height. If you are comfortable to use SASS, then you can do that. Otherwise, JS (jQuery) solution is given below:
HTML:
<div class="parent">
<div class="child">I am a square (1:1)</div>
</div>
CSS:
.parent {
border: 1px solid black;
width:auto;
height: 200px;
position:relative
}
.child {
background:tomato;
height:inherit;
}
JS:
let parentWidth = $('.parent').width();
let parentHeight = $('.parent').height();
let childWidth = $('.child').width();
let childHeight = $('.child').height();
let minSize = 0;
$('.child').width(childHeight);
$(window).on('resize', function () {
parentWidth = $('.parent').width();
parentHeight = $('.parent').height();
childWidth = $('.child').width();
childHeight = $('.child').height();
minSize = Math.min(parentWidth, parentHeight);
if (childWidth >= parentWidth || childHeight >= parentHeight) {
if (childWidth >= parentWidth && childHeight >= parentHeight) {
$('.child').height(minSize);
$('.child').width(minSize);
} else if (childWidth >= parentWidth) {
$('.child').width(parentWidth);
$('.child').height(parentWidth);
} else {
$('.child').height(parentHeight);
$('.child').width(parentHeight);
}
} else {
$('.child').height(minSize);
$('.child').width(minSize);
}
});
Try adding this code::
<div style="display: table; width: 100%; height: 100%; text-align: center;">
<div style="display: table-cell; vertical-align: middle; background:tomato;">square (1:1)</div>
</div>

Get Element on Scroll using Jquery

i have been trying to get Element when i scroll . basically my aim is to get Element while scrolling
e.g i have
<div id='ParentDiv' style="overflow-x:auto">
<div id="1" style="height:50px"> 1 <div>
<div id="2" style="height:100px">2</div>
<div id="3" style="height:20px>3<div>
</div>
is there any way to get id of Div when i scroll to element as ids are dynamic
regards
I don't know if it's what you are looking for :
$("div").on('mousewheel DOMMouseScroll', function(e) {
var id = $(this).attr("id");
console.log(id);
})
You can use this code. On #parent scroll, id of element that is in screen, written in console.
$("#parent").scroll(function() {
var winHeight = $(this).height();
var scrollTop = $(this).scrollTop();
$(".child").each(function(index){
var elemHeight = $(this).height();
var elementTop = $(this).position().top;
if (elementTop < scrollTop + winHeight && scrollTop < elementTop + elemHeight)
console.log($(this).attr("id"));
});
});
#parent {
height: 150px;
overflow: auto;
}
.child {
height: 300px;
}
#child1 {
background: red;
}
#child2 {
background: blue;
}
#child3 {
background: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="parent">
<div id="child1" class="child"></div>
<div id="child2" class="child"></div>
<div id="child3" class="child"></div>
</div>

Sticky div on scroll

See this: http://jsfiddle.net/3yx5C/1/
I am trying to make a DIV(the green DIV) from the right column:
1. to be fixed when it meets the HEADER;
2. to be NOT-fixed when you scroll to the top and it meets the other DIVs above(*the grey DIVs);
What I can't achieve is the second part. Any ideas?
I have to mention that the grey DIVs on the right might be more than two, with flexible heights, and they can't be wrapped.
<div id="HEADER"></div>
<div id="WRAPPER">
<div class="layout_right">
<div style="height: 80px; background: gray;"></div>
<div style="height: 80px; background: gray;"></div>
<div id="right_ads">I am sticky!</div>
</div>
<div class="layout_middle">
<div style="width: 300px; height: 200px; background: beige;"></div>
<div style="width: 300px; height: 200px; background: pink;"></div>
<div style="width: 300px; height: 200px; background: blue;"></div>
</div>
</div>
<script type='text/javascript'>
window.addEvent('domready', function() {
function sticky_AD() {
var headerHeight2 = $('HEADER').getSize().y;
var window_top = $(window).getScroll().y + headerHeight2 + 20;
var div_top = $('right_ads').getPosition().y;
if (window_top > div_top){
$('right_ads').addClass('fixed_AD').setStyles({'top': headerHeight2 + 20});
} else {
$('right_ads').removeClass('fixed_AD').setStyles({'top':'auto'});
}
}
$(window).addEvent('scroll', function(){
sticky_AD();
});
sticky_AD();
});
</script>
Check this:
Demo here
I added a new variable var dist = $('right_ads').getPosition().y; to store the original position and use it later and changed your else to a new if : if (window_top < dist) {
Code:
function sticky_AD() {
var headerHeight2 = $('HEADER').getSize().y;
var window_top = $(window).getScroll().y + headerHeight2 + 20;
var div_top = $('right_ads').getPosition().y;
if (window_top > div_top) {
$('right_ads').addClass('fixed_AD').setStyles({
'top': headerHeight2 + 20
});
}
if (window_top < dist) {
$('right_ads').removeClass('fixed_AD').setStyles({
'top': 'auto'
});
}
}
$(window).addEvent('scroll', function () {
sticky_AD();
});
var dist = $('right_ads').getPosition().y;
sticky_AD();

Categories

Resources