Change image during scroll when the text becomes visible - javascript

I have this script that changes the image(s) when the text reaches the top of the screen. I want it in reverse. I would like to have the image(s) change when the text becomes visible from a certain height (say 50px) from bottom. How do I go about this?
JS Fiddle: https://jsfiddle.net/ramo427e/
HTML:
<div class="main">
<div class="container">
<div class="row">
<div class="col-md-12">
<h1>This is sparta and what comes whatever after. This is sparta and what comes whatever after. This is sparta and what comes whatever after. </h1><h1>This is sparta and what comes whatever after. This is sparta and what comes whatever after. This is sparta and what comes whatever after. </h1>
</div>
<div class="col-md-6">
<section id="one">
<div class="content">
<h1>first heading</h1>
<p>description</p>
</div>
</section>
<section id="two">
<div class="content">
<h1>second heading</h1>
<p>description</p>
</div>
</section>
<section id="three">
<div class="content">
<h1>third heading</h1>
<p>description</p>
</div>
</section>
<section id="four">
<div class="content">
<h1>fourth heading</h1>
<p>description</p>
</div>
</section>
</div>
<div class="col-md-6">
<div class="image"></div>
</div>
</div>
<div class="row">
<div class="col-12">
<h1>This is sparta and what comes whatever after. This is sparta and what comes whatever after. This is sparta and what comes whatever after. </h1>
</div>
</div>
</div>
</div>
CSS:
*{
margin:0;
padding:0;
}
h1{
font-size: 18px;
font-weight: strong;
}
p{
font-size: 12px;
}
.main{
width:100%;
height:auto;
position: relative;
}
.image {
background-image:url('https://i.postimg.cc/FRxNp6yG/hr-ax.png');
background-size:100% 100%;
background-attachment:fixed;
transition: 1000ms;
width: 200px;
height: 200px;
position: fixed;
}
#one, #two, #three, #four {
min-height: 150px;
}
JS:
$(window).on("scroll touchmove", function() {
if ($(document).scrollTop() >= $("#one").position().top && $(document).scrollTop() < $("#two").position().top) {
$('.image').css('background-image', 'url(https://i.postimg.cc/FRxNp6yG/hr-ax.png)')
};
if ($(document).scrollTop() >= $("#two").position().top && $(document).scrollTop() < $("#three").position().top) {
$('.image').css('background-image', 'url(https://i.postimg.cc/wvz9hzm7/hr-bx.png)')
};
if ($(document).scrollTop() >= $("#three").position().top && $(document).scrollTop() < $("#four").position().top) {
$('.image').css('background-image', 'url(https://i.postimg.cc/FRxNp6yG/hr-ax.png)')
};
if ($(document).scrollTop() >= $("#four").position().top) {
$('.image').css('background-image', 'url(https://i.postimg.cc/wvz9hzm7/hr-bx.png)')
};
});

on the scroll, you can get the position of the all element. And calculate whether it is visible or not. Based on the switch case you can change image.
Sample:
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');
}
});
More:
https://usefulangle.com/post/113/javascript-detecting-element-visible-during-scroll

Select the text with jQuery and correct the height relative to that and use that to trigger the event for Example:
if($(document).scrollTop() >= $("#one .content h1").position().top - 50){
$('.image').css('background-image', 'url(https://i.postimg.cc/FRxNp6yG/hr-ax.png)')
}

Related

How to make infinite Scrolling?

I am creating a Slider with horizontal Scrolling effect But I stuck at a point how Can I make the slider scroll infinitely Like in my code you can see After Item 6 it stops Scrolling and I have to scroll backward but I want it like after Item 6, Item 1 will come again Something like this https://durimel.io/nel Here you can see the scrolling is infinite?
So can anyone help in this?
let container = document.querySelector(".container")
let container1 = document.querySelector(".container1")
window.onscroll = ()=>{
container.style.left = `${-window.scrollY}px`
container1.style.right = `${-window.scrollY}px`
}
let currentpos = container.getBoundingClientRect().left
let currentpos1 = container1.getBoundingClientRect().left
let callDisort = () =>{
let newPos = container.getBoundingClientRect().left;
let newPos1 = container1.getBoundingClientRect().left;
let diff = newPos - currentpos;
let speed = diff * 0.50
container.style.transform = `skewX(${speed}deg)`
currentpos = newPos
container1.style.transform = `skewX(${speed}deg)`
currentpos = newPos
requestAnimationFrame(callDisort)
}
console.log(currentpos)
callDisort()
*{
margin:0;
padding:0;
box-sizing:border-box;
font-family: arial;
}
html,body{
height:3000px;
overflow-X:hidden;
}
.container{
position:fixed;
display:flex;
justify-content: space-between;
top:30vh;
width: 3000px;
transition:transform 0.15s;
will-change:transform;
border:2px solid green;
}
.container1{
position:fixed;
display:flex;
justify-content: space-evenly;
top:45vh;
width: 3000px;
transition:transform 0.15s;
will-change:transform;
border:2px solid green;
}
.box{
position:relative;
}
.box h2{
font-size:4em;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="container">
<div class="box one">
<h2>Item 1</h2>
</div>
<div class="box two">
<h2>Item 2</h2>
</div>
<div class="box three">
<h2>Item 3</h2>
</div>
<div class="box four">
<h2>Item 4</h2>
</div>
<div class="box five">
<h2>Item 5</h2>
</div>
<div class="box six">
<h2>Item 6</h2>
</div>
</div>
<div class="container1">
<div class="box one">
<h2>Item 1</h2>
</div>
<div class="box two">
<h2>Item 2</h2>
</div>
<div class="box three">
<h2>Item 3</h2>
</div>
<div class="box four">
<h2>Item 4</h2>
</div>
<div class="box five">
<h2>Item 5</h2>
</div>
<div class="box six">
<h2>Item 6</h2>
</div>
</div>
</body>
</html>
The method the example (https://durimel.io/nel) uses differs and is not really infinite. It is limited to the max values the css properties left and transform: translate3d() support. But its enough for a normal use.
It changes the position of each box as soon as it is out of view depending on the direction and moving it behind the "last" or before the "first" using transform: translate3d() and left: ....
Overall here is version of the method i mentioned. I recommend testing it in on jsfiddle or run the snippet in "Full Page"-View because of the mouse-wheel-scrolling behavior from unscrollable iframe-childs and a scrollable parents can't be prevented.
Update:
Added a simple speed detection routine to allow faster/slower scrolling.
Also fixed the selector for the observer at the end of the js part
const eventHandler = (e) => {
document.querySelectorAll(".boxes-container").forEach(container => {
const cur = +container.dataset.cur || 0;
container.dataset.before = container.dataset.cur;
container.dataset.scrollspeed = (+container.dataset.scrollspeed || 0) +1;
setTimeout(() => {
container.dataset.scrollspeed = +container.dataset.scrollspeed -1;
}, 33 * +container.dataset.scrollspeed);
let moveByPixels = Math.round(e.deltaY / (6 - Math.min(+container.dataset.scrollspeed,5)));
if (container.dataset.direction == "invert") {
moveByPixels *= -1;
}
container.style.left = `${cur + -moveByPixels}px`;
container.dataset.cur = cur + -moveByPixels;
});
};
window.addEventListener("wheel", eventHandler);
window.addEventListener("mousewheel", eventHandler);
const observer = new IntersectionObserver((entries, opts) => {
entries.forEach(entry => {
entry.target.classList.toggle('visible', entry.isIntersecting);
});
document.querySelectorAll(".boxes-container").forEach(container => {
const before = (+container.dataset.before || 0),
current = (+container.dataset.cur || 0),
diff = before - current,
boxes = [...container.querySelectorAll(".box")],
visible = [...container.querySelectorAll(".box.visible")],
first = boxes.indexOf(visible[0]),
last = boxes.indexOf(visible[visible.length - 1]),
adjust = (by) => {
container.dataset.cur = +container.dataset.cur + by;
container.dataset.before = +container.dataset.before + by;
container.style.left = +container.dataset.cur + 'px';
};
if (diff >= 0) {
if (first > 0) { // move the first to the end
const box = boxes[0];
box.parentNode.append(box);
adjust(box.clientWidth);
}
} else {
if (last == 0 || first == 0) { // move the to first
const box = boxes[boxes.length - 1];
box.parentNode.prepend(box);
adjust(-box.clientWidth);
}
}
})
}, { // trigger on any percent value
threshold: new Array(101).fill(0).map((n, i) => +(i / 100).toFixed(2))
});
document.querySelectorAll(".boxes-container .box").forEach(el => observer.observe(el));
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Sans-Serif;
}
.boxes-container {
position: fixed;
display: flex;
flex-wrap: nowrap;
flex-direction: row;
white-space: nowrap;
min-width: -min-content;
}
.v30 {
top: 30vh;
}
.v60 {
top: 60vh;
}
.box {
position: relative;
margin: 0 !important;
padding: 0 50px;
}
.box h2 {
font-size: 5rem;
}
<div class="boxes-container">
<div class="box">
<h2>0</h2>
</div>
<div class="box">
<h2>1</h2>
</div>
<div class="box">
<h2>2</h2>
</div>
<div class="box">
<h2>3</h2>
</div>
<div class="box">
<h2>4</h2>
</div>
<div class="box">
<h2>5</h2>
</div>
<div class="box">
<h2>6</h2>
</div>
</div>
<div class="boxes-container v30" data-direction="invert">
<div class="box">
<h2>A</h2>
</div>
<div class="box">
<h2>B</h2>
</div>
<div class="box">
<h2>C</h2>
</div>
<div class="box">
<h2>D</h2>
</div>
<div class="box">
<h2>E</h2>
</div>
<div class="box">
<h2>F</h2>
</div>
<div class="box">
<h2>G</h2>
</div>
</div>
<div class="boxes-container v60">
<div class="box">
<h2>0</h2>
</div>
<div class="box">
<h2>1</h2>
</div>
<div class="box">
<h2>2</h2>
</div>
<div class="box">
<h2>3</h2>
</div>
<div class="box">
<h2>4</h2>
</div>
<div class="box">
<h2>5</h2>
</div>
<div class="box">
<h2>6</h2>
</div>
</div>
A few comments on this solution.
If the container is set to display:flex; justify-content:space-around; then the space between the items will change as you scroll (the more items, the closer packed they are). Changing to justify-content:flex-start; with a fixed width for each .box delivers the best result.
Adding a debounce fn greatly improved (and simplified) the job. At least, the console logs are underwhelming(!).
If you scroll the scroll wheel very fast, you might hit the end of the carousel before it re-populates. To make that easier to see, change the delay from 50 to 500 (milliseconds).
The debounce is saying, "only plunk more boxes into the container when 50ms has elapsed since the last scroll event. You might prefer a throttle function instead, where it will run at most one re-population every 50ms (or as you set the debounceDelay value).
The HTML,Body height needs to be set to a very large number - in this demo it is now set to 30,000. The .container width should be auto, and it (the .container width) will increase every time new items are plonked into the container.
Most important: the $ and $$ are not jQuery. They are pure vanilla javaScript shorthand for document.querySelector() and document.querySelectorAll()
The demo is best viewed full page (link at top right of demo window).
const $ = document.querySelector.bind(document);
const $$ = document.querySelectorAll.bind(document);
let kontainer = $(".container");
const boxes = $$('.box');
const debounceDelay = 50; //change to 100 for better performance
const updateCarousel = debounce(function(e){
console.log(scrollY +' // '+ kontainer.getBoundingClientRect().right)
const currKontainerWidth = kontainer.getBoundingClientRect().right;
if ( currKontainerWidth - scrollY < 300 ){
for (let i=0, j=boxes.length; j > i; i++){
kontainer.appendChild(boxes[i].cloneNode(true));
}
}
}, debounceDelay);
window.addEventListener('scroll', updateCarousel, false);
window.onscroll = () => {
kontainer.style.left = `${-window.scrollY}px`;
}
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
*{
margin:0;
padding:0;
box-sizing:border-box;
font-family: arial;
}
html,body{
height:30000px;
overflow-X:hidden;
}
.container{
position:fixed;
display:flex;
justify-content: flex-start;
top:30vh;
width: auto;
transition:transform 0.15s;
will-change:transform;
border:2px solid green;
}
.box{
position:relative;
min-width:250px;
}
.box h2{
font-size:4em;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="container">
<div class="box one">
<h2>Item 1</h2>
</div>
<div class="box two">
<h2>Item 2</h2>
</div>
<div class="box three">
<h2>Item 3</h2>
</div>
<div class="box four">
<h2>Item 4</h2>
</div>
<div class="box five">
<h2>Item 5</h2>
</div>
<div class="box six">
<h2>Item 6</h2>
</div>
</div>
</body>
</html>
Best viewed "full page" (top right link after clicking Run Code Snippet)

On click scroll up or down 100vh pure javascript

I am trying to implement a simple button that on click will scroll the page either up or down 100vh between my sections. I can see plenty of examples doing this with jQuery but I'm looking for a pure javascript solution. I'm not to sure how to achieve it.
Appreciate any advice.
HTML
<section class="section section-1">
<div class="btn"></div>
</section>
<section class="section section-2">
<div class="btn"></div>
</section>
<section class="section section-3">
<div class="btn"></div>
</section>
CSS
.section {
width: 100%;
height: 100vh:
}
This is what I have come up with so far
for (var s = 0; s < btn.length; s++) {
btn[s].addEventListener('click', function(){
window.scrollBy(0,1000);
});
}
There are a few ways to get viewport size in JavaScript. Using one of these ways, you should be able to scroll as you are with the viewport size in place of your 1000.
For instance, if I wanted to scroll exactly the height of one viewport with window.innerHeight:
let pageHeight = window.innerHeight;
window.scrollBy(0, pageHeight);
document.querySelectorAll('.btn').forEach(btn => {
btn.addEventListener('click', function() {
let scrollDistance = document.documentElement.clientHeight;
if (btn.className.split(' ').includes('scroll-up')) {
scrollDistance *= -1;
}
window.scrollBy(0, scrollDistance);
});
});
body {
margin: 0;
}
.section {
width: 100%;
height: 100vh;
}
.section-1 {
background-color: blue;
}
.section-2 {
background-color: red;
}
.section-3 {
background-color: green;
}
<section class="section section-1">
<div class="btn scroll-down">V</div>
<div class="btn scroll-up">^</div>
</section>
<section class="section section-2">
<div class="btn scroll-down">V</div>
<div class="btn scroll-up">^</div>
</section>
<section class="section section-3">
<div class="btn scroll-down">V</div>
<div class="btn scroll-up">^</div>
</section>

Keep div vertically centered to visible part of parent element

Currently I have a header element with the following code
<header>
<div class="container">
<div class="row" style="display: flex; justify-content: center">
<div class="col-xs-12 text-center" style="align-self: center">
<h1>My header</h1>
<span id="slogan">my slogan</span>
</div>
</div>
</div>
</header>
The header element starts at the beginning of body and ends 75 px short of the window bottom.
header {
height: calc(100vh - 75px);
}
When I start scrolling downwards, the header element starts moving up with the header and the slogan span. However, I want to keep the h1 and span elements vertically centered to the visible part of the header element, somewhat like on the iStockPhoto website. Notice how you scroll down the search form stays vertically centered to the background image container.
How do I implement something similar?
After a little bit of trial and error I created the effect with the following code.
<header>
<div class="container">
<div class="row" style="display: flex; justify-content: center">
<div class="col-xs-12 text-center" style="align-self: center">
<h1>Header</h1>
<span id="slogan">my slogan</span>
</div>
</div>
</div>
</header>
<section style="height: 1000px">
<div class="container">
<div class="row">
<div class="col-xs-12">
<p>Lorem ipsum dolor sit amet...</p>
</div>
</div>
</div>
</section>
$(function() {
var initialTitleHeight = $("header h1").position().top,
initialSloganBottom = $("header span").position().top + $("header span").height(),
headerHeight = $("header").height(),
headerMaxMargin = headerHeight - (initialSloganBottom - initialTitleHeight + 50);
$(window).scroll(function(event) {
var scroll = $(window).scrollTop(),
marginTop = (initialTitleHeight + scroll < headerMaxMargin) ? initialTitleHeight + scroll : headerMaxMargin;
$("header h1").css({
marginTop: marginTop + "px"
});
});
})
header {
height: calc(100vh - 75px);
background-color: #F90;
.container, .row {
height: 100%;
}
}
You can try it here.

Viewport - jQuery selectors for finding elements in viewport

In my project every div has a video so I'm trying to check if a div is in viewport, so if it is that video to start playing and if it's not to pause or to stop. I'm jusing jekyll.
For example my html code for only one div looks like this :
<div class="container">
<div class="row">
<input type="button" id="play" value="Play"></input>
<input type="button" id="pause" value="Pause"></input>
<br/><br/>
<div class="col-lg-5 col-lg-offset-1 col-sm-push-6 col-sm-6">
<hr class="section-heading-spacer">
<div class="clearfix"></div>
<h2 class="section-heading">Vjetersia</h2>
<div class="lead"><p class="justify"> Vjetersia</p></div>
</div>
<div class="col-lg-5 col-sm-pull-6 col-sm-6">
<div class="gifv-player" id="">
<video preload="none" loop="loop">
<source type="video/webm" src="all_files/1.webm" />
</video>
<img src="example.png" alt="Animated Gif" />
</div>
</div>
<div class="col-lg-3 col-sm-pull-2 col-sm-2"></div>
</div>
</div>
I tried to do it on button click and it works:
$( document ).ready(function() {
player = new GifvPlayer();
$("#play").click(function() {
$('.gifv-player').find('video').show();
$('.gifv-player').find('video')[0].play();
});
$("#pause").click(function() {
$('.gifv-player').find('video')[0].pause();
});
});
but how can i modify it so it can work on stroll if a div is visible to start to play its video ? Any help?
easy quick and dirty example how you could implement it. play/pause like changing colors of the divs ... http://jsfiddle.net/7mkdj4ak/1/
var scrollPosition = $(window).scrollTop();
var windowViewHeight = $(window).height();
var videoWrapHeight = $('.container').outerHeight();
$(window).on('scroll', function(){
scrollPosition = $(window).scrollTop();
playVideos(scrollPosition);
});
$(window).on('resize', function(){
windowViewHeight = $(window).height();
});
var playVideos = function(scrollPosition) {
$('.container').each(function(i){
var thisContainerTopPosition = $(this).offset().top;
var thisContainerBottomPosition = thisContainerTopPosition + videoWrapHeight;
if(
thisContainerTopPosition >= scrollPosition &&
thisContainerBottomPosition <= (scrollPosition + windowViewHeight )
) {
/* div is in view PLaY */
$(this).css('background-color','orange');
} else {
/* div is out of view PausE */
$(this).css('background-color','#afafaf');
}
});
};
playVideos(scrollPosition);
.container {
width: 100%;
height: 100px;
background-color: #afafaf;
margin: 20px 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<h1>my Video 1</h1>
</div>
<div class="container">
<h1>my Video 2</h1>
</div>
<div class="container">
<h1>my Video 3</h1>
</div>
<div class="container">
<h1>my Video 4</h1>
</div>
You can refer this demo
.growme {
background-color: #990000;
height: 0;
width: 0;
position: absolute;
filter: alpha(opacity=0);
opacity: 0;
top:0;
left:0;
bottom:0;
right:0;
margin:auto;
}
DEMO HERE

Complex continuous scroll loop

I have a code similar to:
<div id='right-column'>
<div id='results'>
<div id='result1>
<div class='main'></div>
<div class='details'></div>
</div>
<!-- ... -->
<div id='result50>
<div class='main'></div>
<div class='details'></div>
</div>
</div>
</div>
the total number of results depends of the ajax query, I insert all the results dynamically in one go.
div.main is always visible (fixed height) and div.details "unfolds/folds" below div.main when the user clicks on a result div.
the details div height can vary.
If #results scrollHeight is bigger than #right-column height, I would like to create a continuous scroll loop.
In this case, scrolling past #result50 would show #result1, scrolling before #result1 would show #result50.
I can't .append() the first child to the bottom as in some cases a portion of a result can be seen on top and at the bottom of the column.
I can't duplicate a result unless I detect if .details is unfolded/folded.
The fact that the height of a result can change when a user unfolds the .details div, makes it even more complicated...
Here is an example of continuous scroll loop (2 columns):
$(document).ready(function() {
var num_children = $('#up-left').children().length;
var child_height = $('#up-left').height() / num_children;
var half_way = num_children * child_height / 2;
$(window).scrollTop(half_way);
function crisscross() {
$('#up-left').css('bottom', '-' + window.scrollY + 'px');
$('#down-right').css('bottom', '-' + window.scrollY + 'px');
var firstLeft = $('#up-left').children().first();
var lastLeft = $('#up-left').children().last();
var lastRight = $('#down-right').children().last();
var firstRight = $('#down-right').children().first();
if (window.scrollY > half_way ) {
$(window).scrollTop(half_way - child_height);
lastRight.appendTo('#up-left');
firstLeft.prependTo('#down-right');
} else if (window.scrollY < half_way - child_height) {
$(window).scrollTop(half_way);
lastLeft.appendTo('#down-right');
firstRight.prependTo('#up-left');
}
}
$(window).scroll(crisscross);
});
div#content {
width: 100%;
height: 100%;
position: absolute;
top:0;
right:0;
bottom:0;
left:0;
}
#box {
position: relative;
vertical-align:top;
width: 100%;
height: 200px;
margin: 0;
padding: 0;
}
#up-left {
position:absolute;
z-index:4px;
left: 0;
top: 0px;
width: 50%;
margin: 0;
padding: 0;
}
#down-right {
position:fixed;
bottom: 0px;
z-index: 5px;
left: 50%;
width: 50%;
margin: 0;
padding: 0;
}
h1 {margin: 0;padding: 0;color:#fff}
.black {background: black;}
.white {background: grey;}
.green {background: green;}
.brown {background: brown;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<div id="content">
<div id="up-left">
<div id="box" class="brown">
<h1>ONE</h1>
</div>
<div id="box" class="black">
<h1>TWO</h1>
</div>
<div id="box" class="white">
<h1>THREE</h1>
</div>
<div id="box" class="black">
<h1>FOUR</h1>
</div>
<div id="box" class="white">
<h1>FIVE</h1>
</div>
<div id="box" class="black">
<h1>SIX</h1>
</div>
</div><!-- #up-left -->
<div id="down-right">
<div id="box" class="white">
<h1>SIX</h1>
</div>
<div id="box" class="black">
<h1>FIVE</h1>
</div>
<div id="box" class="white">
<h1>FOUR</h1>
</div>
<div id="box" class="black">
<h1>THREE</h1>
</div>
<div id="box" class="white">
<h1>TWO</h1>
</div>
<div id="box" class="green">
<h1>ONE</h1>
</div>
</div><!-- #down-right -->
</div><!-- .content -->
(fiddle: http://jsfiddle.net/franckl/wszg1d6c/)
Any hint/ideas on how I could do it ?
Move items to top or bottom based on scroll direction
You can use jQuery's .append() and .prepend() to move items without cloning them.
You'll use similar techniques to infinite scrolling with lazy loading (AJAX), but in this scenario you want to handle scrolling up as well as down, and instead of loading new content from the server, you're just recycling existing DOM elements in the list.
Below I demonstrate one technique. I store the scroll position in the element's .data cache for easy retrieval when detecting scrolling direction. I chose to detect scrolling direction to avoid making unnecessary variable assignments upfront to improve performance. Otherwise, you'd be getting elements and doing math for a scroll event that isn't going to happen in that direction.
The scroll handler:
$('#right-column').on('scroll', function (e) {
var $this = $(this),
$results = $("#results"),
scrollPosition = $this.scrollTop();
if (scrollPosition > ($this.data('scroll-position') || 0)) {
// Scrolling down
var threshold = $results.height() - $this.height() - $('.result:last-child').height();
if (scrollPosition > threshold) {
var $firstResult = $('.result:first-child');
$results.append($firstResult);
scrollPosition -= $firstResult.height();
$this.scrollTop(scrollPosition);
}
} else {
// Scrolling up
var threshold = $('.result:first-child').height();
if (scrollPosition < threshold) {
var $lastResult = $('.result:last-child');
$results.prepend($lastResult);
scrollPosition += $lastResult.height();
$this.scrollTop(scrollPosition);
}
}
$this.data('scroll-position', scrollPosition)
});
A complete working example:
$('#right-column').on('scroll', function (e) {
var $this = $(this),
$results = $("#results"),
scrollPosition = $this.scrollTop();
if (scrollPosition > ($this.data('scroll-position') || 0)) {
// Scrolling down
var threshold = $results.height() - $this.height() - $('.result:last-child').height();
if (scrollPosition > threshold) {
var $firstResult = $('.result:first-child');
$results.append($firstResult);
scrollPosition -= $firstResult.height();
$this.scrollTop(scrollPosition);
}
} else {
// Scrolling up
var threshold = $('.result:first-child').height();
if (scrollPosition < threshold) {
var $lastResult = $('.result:last-child');
$results.prepend($lastResult);
scrollPosition += $lastResult.height();
$this.scrollTop(scrollPosition);
}
}
$this.data('scroll-position', scrollPosition)
});
$('#results').on('click', '.result', function (e) {
$(this).find('.details').toggle();
});
$('#newNumber').on('input', function (e) {
var results = '';
for (var n = 1; n <= $(this).val(); n++) {
results +=
'<div class="result" id="result' + n + '">' +
' <div class="main">Result ' + n + '</div>' +
' <div class="details">Details for result ' + n + '</div>' +
'</div>';
}
$('#results').html(results);
});
body {
font-family: sans-serif;
}
h1 {
font: bold 2rem/1 Georgia, serif;
}
p {
line-height: 1.5;
margin-bottom: 1em;
}
label {
font-weight: bold;
margin-bottom: 1em;
}
.column {
box-sizing: border-box;
float: left;
width: 50%;
height: 100vh;
padding: 1em;
overflow: auto;
}
#right-column {
background-color: LemonChiffon;
}
.result {
padding: 1em;
cursor: pointer;
}
.result .main {
height: 2em;
font-weight: bold;
line-height: 2;
}
.result .details {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class=
"column" id="left-column">
<p>Existing DOM elements are moved to the top or bottom of the list depending on your scroll direction.</p>
<label>Change the number of results to display
<input id="newNumber" type="number" value="10" />
</div>
<div class=
"column" id="right-column">
<div id="results">
<div id="result1" class="result">
<div class="main">Result 1</div>
<div class="details">Details for result 1</div>
</div>
<div id="result2" class="result">
<div class="main">Result 2</div>
<div class="details">Details for result 2</div>
</div>
<div id="result3" class="result">
<div class="main">Result 3</div>
<div class="details">Details for result 3</div>
</div>
<div id="result4" class="result">
<div class="main">Result 4</div>
<div class="details">Details for result 4</div>
</div>
<div id="result5" class="result">
<div class="main">Result 5</div>
<div class="details">Details for result 5</div>
</div>
<div id="result6" class="result">
<div class="main">Result 6</div>
<div class="details">Details for result 6</div>
</div>
<div id="result7" class="result">
<div class="main">Result 7</div>
<div class="details">Details for result 7</div>
</div>
<div id="result8" class="result">
<div class="main">Result 8</div>
<div class="details">Details for result 8</div>
</div>
<div id="result9" class="result">
<div class="main">Result 9</div>
<div class="details">Details for result 9</div>
</div>
<div id="result10" class="result">
<div class="main">Result 10</div>
<div class="details">Details for result 10</div>
</div>
</div>
</div>
A complete working example on CodePen, if you prefer.

Categories