Sticky div on scroll - javascript

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();

Related

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>

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

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.

How can i change header on a div when scroll?

I'll create a black and white background website. I want the header to be replaced with translateY when the background is black. I want to change slowly in the transition area. How can i do that?
function headerEffect() {
var win = $(window).scrollTop();
var blackspace = $('.black').offset().top;
var headw = $('.logo-white');
var headb = $('.logo-black');
var where = window.pageYOffset || document.documentElement.scrollTop;
if (win > blackspace) {
headw.css({
'transform': 'translateY(' + (win - blackspace - 42) + 'px' + ')'
});
}
}
headerEffect();
$(window).scroll(headerEffect);
header {
position: fixed;
font-size: 30px;
font-weight: bold;
}
header .logo-white {
color: #fff;
}
header .logo-black {
color: #000;
}
.black {
background: #000;
height: 300px;
width: 100%;
}
.white {
background: #fff;
height: 300px;
width: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<header>
<div class="logo-black">LOGO</div>
<div class="logo-white">LOGO</div>
</header>
<div class="white"></div>
<div class="black"></div>
<div class="white"></div>
<div class="black"></div>
<div class="white"></div>
<div class="black"></div>
<div class="white"></div>
<div class="black"></div>
<div class="white"></div>
https://jsfiddle.net/br1fmhou/
I write console.log('123') to make sure that you are scroll in black div
function headerEffect() {
var win = $(window).scrollTop();
var blackspace = $('.black').offset().top;
var headw = $('.logo-white');
var headb = $('.logo-black');
var where = window.pageYOffset || document.documentElement.scrollTop;
$('.black').each(function() {
if (win >= ($(this).offset().top - 10) &&
win <= ($(this).offset().top + $(this).height())) {
console.log('123');
headw.css({
'transform': 'translateY('+( win - blackspace - 42) + 'px' +')'
});
}
})
}
https://jsfiddle.net/br1fmhou/2/

Text overlap detection with weird behaviour

I'm trying to detect when a div.text is on top of another (within the parent div#element), the background of the parent should change depending the existance of the overlap.
The issue is that when there's no overlap the script still detects one, this may be caused because each .text compares their position with itself over again.
$(".text").each(function() {
var self_text = $(this),
self_textid = self_text.attr('id'),
self_textLeft = self_text.position().left,
self_textTop = self_text.position().top,
self_textWidth = self_text.width(),
self_textHeight = self_text.height();
$(".text").each(function() {
var self_shape = $(this),
self_shapeLeft = self_shape.position().left,
self_shapeTop = self_shape.position().top,
self_shapeWidth = self_shape.width(),
self_shapeHeight = self_shape.height();
// check if .text overlaps
if (
(self_textLeft + self_textWidth) > self_shapeLeft &&
self_textLeft < (self_shapeLeft + self_shapeWidth) &&
(self_textTop + self_textHeight) > self_shapeTop &&
self_textTop < (self_shapeTop + self_shapeHeight)
) {
// overlap
$('#elements').css('background', 'red');
} else {
// no overlap
$('#elements').css('background', 'green')
}
});
});
#elements,
.text {
position: absolute;
}
.text {
width: 50px;
background: blue;
}
#elements {
height: 250px;
width: 400px;
background: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="elements">
<div class="text" style="left: 20px; top: 20px;">text1</div>
<div class="text" style="left: 100px; top: 50px;">text2</div>
<div class="text" style="left: 10px; top: 200px;">text3</div>
<div class="text" style="left: 10px; top: 45px;">text4</div>
</div>
How can I tell the script to ignore itself whenever it loops again without changing the .text class, also is there any way that code could be improved ?
It's because you include the div itself to check against, try adding it to a not:
var $textDivs = $(".text"); // cache this so you are not performing the lookup every time in your loop
$textDivs.each(function() {
var self_text = $(this),
self_textid = this.id,
self_textLeft = self_text.position().left,
self_textTop = self_text.position().top,
self_textWidth = self_text.width(),
self_textHeight = self_text.height(),
hasNotMatched = true;
$textDivs.not(self_text).each(function() { // added to a not so not checked against itself
var self_shape = $(this),
self_shapeLeft = self_shape.position().left,
self_shapeTop = self_shape.position().top,
self_shapeWidth = self_shape.width(),
self_shapeHeight = self_shape.height();
// check if .text overlaps
if (
(self_textLeft + self_textWidth) > self_shapeLeft &&
self_textLeft < (self_shapeLeft + self_shapeWidth) &&
(self_textTop + self_textHeight) > self_shapeTop &&
self_textTop < (self_shapeTop + self_shapeHeight)
) {
// overlap
$('#elements').css('background', 'red');
hasNotMatched = false;
return false; // break out of each loop so as no need to process anymore and so overlap doesn't turn back green
} else {
// no overlap
$('#elements').css('background', 'green')
}
});
return hasNotMatched; // this will break out of outer loop if matched on inner loop
});
#elements,
.text {
position: absolute;
}
.text {
width: 50px;
background: blue;
}
#elements {
height: 250px;
width: 400px;
background: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="elements">
<div class="text" style="left:20px; top:20px;">text1</div>
<div class="text" style="left:100px; top:50px;">text2</div>
<div class="text" style="left:90px; top:40px;">text3</div>
<div class="text" style="left:190px; top:30px;">text4</div>
</div>
You can exclude the current element with
$(".text").not($(this))
There's also an issue with the logic of your solution - if the last elements checked don't overlap it will change the background to green. One way to handle this is to set the background to green as the default. And if an overlap is detected change the background to red.
There are a few more optimizations that could be made here - like once an overlap has been detected it could exit.
Like this:
$(".text").each(function() {
$('#elements').css('background', 'green');
var self_text = $(this),
self_textid = self_text.attr('id'),
self_textPosition = self_text.position(),
self_textLeft = self_textPosition.left,
self_textTop = self_textPosition.top,
self_textWidth = self_text.width(),
self_textHeight = self_text.height();
$(".text").not($(this)).each(function() {
var self_shape = $(this),
self_shapePosition = self_shape.position(),
self_shapeLeft = self_shapePosition.left,
self_shapeTop = self_shapePosition.top,
self_shapeWidth = self_shape.width(),
self_shapeHeight = self_shape.height();
// check if .text overlaps
if (
(self_textLeft + self_textWidth) > self_shapeLeft &&
self_textLeft < (self_shapeLeft + self_shapeWidth) &&
(self_textTop + self_textHeight) > self_shapeTop &&
self_textTop < (self_shapeTop + self_shapeHeight)
) {
// overlap
$('#elements').css('background', 'red');
}
});
});
#elements,
.text {
position: absolute;
}
.text {
width: 50px;
background: blue;
}
#elements {
height: 250px;
width: 400px;
background: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="elements">
<div class="text" style="left: 20px; top: 20px;">text1</div>
<div class="text" style="left: 100px; top: 50px;">text2</div>
<div class="text" style="left: 10px; top: 200px;">text3</div>
<div class="text" style="left: 10px; top: 45px;">text4</div>
</div>

Freeze selected item when scrolling

I have a requirement, where I need to freeze the selected item from list of items in a container to top, when the selected item is in top fold of the container. and when the selected item is in bottom fold of the container, I need to stick it to the bottom.
If the selected item is in visible fold, nothing should happen. I mean the selected item should be in normal flow with other adjacent items.
I somehow managed to solve this to some extent. But when I scroll up, when the selected item is sticked above of the container, the selected item is hiding. This behavior is happening even when I scroll down, when the selected item is sticked to the bottom of the container.
Here is the Fiddle
$('.item').click(function () {
$('.item').removeClass('select').removeClass('pAbsolute');
$(this).addClass('select');
});
$('.parent').scroll(function () {
var $selected = $('.item.select');
var cTop = $selected.offset().top;
var cHeight = $selected.height();
var pHeight = $(this).height();
if (cTop < 0) {
$selected.css({
'top': $(this).scrollTop(),
'bottom': ''
}).addClass('pAbsolute');
} else if (cTop > pHeight - cHeight) {
$selected.css({
'bottom': -$(this).scrollTop(),
'top': ''
}).addClass('pAbsolute');
} else {
$selected.css({
'top': '',
'bottom': ''
}).removeClass('pAbsolute');
}
});
You have to use a consistent value to keep the initial offset relative to the container when you select it.
Then, calculate the offset and scroll value,
If cTop < 0, which means its top out of box, stick to top.
If cTop + cHeight > pHeight, which means its view is out of bottom block, set to bottom.
Else stay in position.
Edit:
When selecting a new Item, as the previous item may have .pAbsolute attr, the relative position of current item may change, but we can get the offset change by track the offset before and after those class add/remove actions.
Then we can add the missing height by change the scrollTop of the container manually.
var offset;
$('.item').click(function () {
// This is the offset in container before class change.
offset = this.offsetTop;
$('.item').removeClass('select').removeClass('pAbsolute');
$(this).addClass('select');
// Calculate the difference
var distortion = offset - this.offsetTop;
// Remove the distortion by manual scroll.
var $parent = $(this).parent();
$parent.scrollTop($parent.scrollTop() - distortion);
offset = this.offsetTop;
});
$('.parent').scroll(function () {
var $selected = $('.item.select');
var cTop = offset - $(this).scrollTop();
var cHeight = $selected.height();
var pHeight = $(this).height();
if (cTop < 0) {
$selected.css({
'top': $(this).scrollTop(),
'bottom': ''
}).addClass('pAbsolute');
} else if (cTop + cHeight > pHeight) {
$selected.css({
'bottom': -$(this).scrollTop(),
'top': ''
}).addClass('pAbsolute');
} else {
$selected.css({
'top': '',
'bottom': ''
}).removeClass('pAbsolute');
}
});
body, html {
padding: 0;
margin: 0;
}
.parent {
overflow: auto;
height: 200px;
position: relative;
}
.item {
padding: 10px 15px;
background-color: tomato;
width: 100%;
}
.item.select {
background-color: beige;
}
.pAbsolute {
position: absolute;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="parent">
<div class="item select">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item">7</div>
<div class="item">8</div>
<div class="item">9</div>
<div class="item">10</div>
<div class="item">11</div>
<div class="item">12</div>
<div class="item">13</div>
<div class="item">14</div>
<div class="item">15</div>
<div class="item">16</div>
<div class="item">17</div>
<div class="item">18</div>
<div class="item">19</div>
<div class="item">20</div>
<div class="item">21</div>
<div class="item">22</div>
<div class="item">23</div>
<div class="item">24</div>
<div class="item">25</div>
<div class="item">26</div>
<div class="item">27</div>
<div class="item">28</div>
<div class="item">29</div>
<div class="item">30</div>
<div class="item">31</div>
<div class="item">32</div>
<div class="item">33</div>
<div class="item">34</div>
<div class="item">35</div>
<div class="item">36</div>
<div class="item">37</div>
<div class="item">38</div>
<div class="item">39</div>
<div class="item">40</div>
<div class="item">41</div>
</div>
This solution uses a bottom and top header who are filled in with the selected values and showed/hidden when necessary:
Working Fiddle
Javascript:
function stickItems($parent, itemClass, selectClass) {
// Attach dummy element items
$parent.prepend('<div class="' + itemClass + ' sticky top"></div>');
$parent.append('<div class="' + itemClass + ' sticky bottom"></div>');
var $items = $('.' + itemClass),
$stickyTop = $('.' + itemClass + '.sticky.top'),
$stickyBottom = $('.' + itemClass + '.sticky.bottom');
// Click event registering
$items.click(function (e) {
if (!$(e.target).hasClass('sticky')) {
$items.removeClass(selectClass);
$stickyTop.css('display', 'none');
$stickyBottom.css('display', 'none');
$(this).addClass(selectClass);
}
});
// Scroll event
$parent.scroll(function () {
var $self = $(this);
var $selected = $('.' + itemClass + '.' + selectClass);
var cTop = $selected.offset().top;
var pTop = $self.offset().top;
var cHeight = $selected.height();
var pHeight = $self.height();
if (cTop - pTop <= 0) {
$stickyTop.html($selected.html()).css({
'display': 'block',
'top': $(this).scrollTop()
});
$stickyBottom.css('display', 'none');
} else if (cTop > pTop && cTop < pTop + pHeight) {
$stickyTop.css('display', 'none');
$stickyBottom.css('display', 'none');
} else {
$stickyTop.css('display', 'none');
$stickyBottom.html($selected.html()).css({
'display': 'block',
'bottom': -$(this).scrollTop()
});
}
});
}
stickItems($('.parent'), 'item', 'select');
Css:
body, html {
padding: 0;
margin: 0;
}
body {
padding-top: 200px;
}
.parent {
overflow-x: hidden;
overflow-y: auto;
height: 200px;
position: relative;
}
.item {
padding: 10px 15px;
background-color: tomato;
}
.item.select {
background-color: beige;
}
.item.sticky {
background-color: beige;
display: none;
position: absolute;
left: 0;
right: 0;
z-index: 1;
}
Html:
<div class="parent">
<div class="item sticky top"></div>
<div class="item select">1</div>
<div class="item">2</div>
<!-- ... -->
<div class="item">39</div>
<div class="item">40</div>
<div class="item">41</div>
<div class="item sticky bottom"></div>
</div>

Categories

Resources