Reputation: 610
I am trying to reproduce the effect found on this page. So the idea is that when the user scroll the tabs reveal themselves. My take on this was to use jquery to translate them along the scroll. My problem is that I gave them specific scroll area during which they will be translated but if the scroll 'jumps' out of the area they will remain out of place.
I cannot find how to fix that, any help is welcome.
Also I'm sure there are smarter ways to achieve this, feel free to point me towards something you'd rather have done.
Here is my code or a codepen to test it
Thank you for reading me and have a nice day.
var hDoc = 0;
$(".classic, .holder, .holder__item").each(function () {
hDoc += $(this).height();
});
$("body").height(hDoc);
// since both our classic and holder are 100vh, once we scrolled more than classic.height we are passed the sticky element
var hPrev = $(".classic").height();
var itemHeight = $(".item1").height();
// console.log($(window).height());
$(function () {
$(window).scroll(function () {
var pageScrolled = $(window).scrollTop();
console.log("we scrolled: " + pageScrolled);
// if we scrolled the length of hPrev(.classic) but not more then hPrev and the size of one item
if (pageScrolled > hPrev && pageScrolled < hPrev + itemHeight) {
// we translate the first item by the length scrolled minus the .classic section divided by the window height
// this calculus cant go all the way up TO FIX
// var translate = ((pageScrolled - hPrev) / $(".classic").height()) * 100;
$(".item1").css({
transform: "translateY(-" + (pageScrolled - hPrev) + "px"
});
// "transform": "translateY("+ transform + $(window).scrollTop() +"px)"
} else if (
pageScrolled > hPrev + itemHeight &&
pageScrolled < hPrev + itemHeight * 2
) {
// var translate = ((pageScrolled - hPrev ) / $(window).height()) * 100;
$(".item2").css({
transform: "translateY(-" + (pageScrolled - (hPrev + itemHeight)) + "px"
});
}
});
});
// This function could be a for each
// bumper avant chaque item, quand on scroll dans son range le titre translate sur la droite
*,
*::before,
*::after {
box-sizing: border-box;
}
* {
margin: 0;
}
html,
body {
height: 100%;
scroll-behavior: smooth;
}
section {
height: 100vh;
}
.classic {
background-color: pink;
}
.holder {
text-align: center;
display: flex;
justify-content: center;
align-items: center;
// sets it as sticky so that we can have all the scroll space we need but still visually end on this section
position: sticky;
top: 0px;
overflow: hidden;
& h2 {
font-size: 6rem;
}
&__item {
height: 100vh;
width: 100vw;
position: absolute;
&.item1 {
height: calc(100vh - 80px) !important;
top: calc(100vh - 80px) !important;
border: 1px solid black;
background-color: teal;
}
&.item2 {
height: calc(100vh - 60px) !important;
top: calc(100vh - 60px) !important;
border: 1px solid red;
background-color: crimson;
}
&.item3 {
height: calc(100vh - 40px) !important;
top: calc(100vh - 40px) !important;
border: 1px solid purple;
background-color: DarkSalmon;
}
&.item4 {
height: calc(100vh - 20px) !important;
top: calc(100vh - 20px) !important;
background-color: tomato;
border: 1px solid blue;
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section class="classic">
</section>
<section class="holder">
<div class="holder__placeholder">
<h2>Hello world.</h2>
<p>Nice to see you again.</p>
</div>
<div class="holder__item item1">
<p>hello hello</p>
</div>
<div class="holder__item item2"></div>
<div class="holder__item item3"></div>
<div class="holder__item item4"></div>
</section>
Upvotes: 1
Views: 2016
Reputation: 206505
You don't need any JavaScript at all in order to achieve a similar effect:
make use CSS custom properties (variables) in HTML like style="--b:3; --t:0;"
(t
meaning the top index, and b
for bottom index), as multiplicator indexes for your sticky elements.
See how the CSS variables are used in CSS within calc()
/*QuickReset*/ *, *::before, *::after { margin: 0; box-sizing: border-box; }
html, body { scroll-behavior: smooth; }
body { font: clamp(11px, 2.5vmin, 18px)/1.4 sans-serif; color: #252525; }
article {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
font-size: 1.2em;
}
.holder {} /* Whatever you like */
.holder__head {
padding: 0.4em 2em;
position: sticky;
z-index: 1;
bottom: calc(var(--b) * 2em);
top: calc(var(--t) * 2em);
}
.holder__item {
padding: 2rem;
position: sticky;
height: 100vh;
top: calc((var(--t) + 1) * 1.6em);
padding-bottom: 8em;
}
/* Just some colors... */
.bg-0 { background: #000000; color: #b3ab9d;}
.bg-1 { background: #817466; }
.bg-2 { background: #a2a093; }
.bg-3 { background: #a5882a; }
.bg-4 { background: #b29d73; }
<article class="bg-0"><h1>How We Work</h1></article>
<article><h2>Welcome</h2><p>Scroll down to find out more</p></article>
<section class="holder">
<header class="holder__head bg-3" style="--t:0; --b:3;">1 STEP — IDENTIFYING STRENGTHS AND WEAKNESSES</header>
<article class="holder__item bg-3" style="--t:0;">
<h2>IDENTIFYING STRENGTHS AND WEAKNESSES</h2>
<p>OUR MULTIDISCIPLINARY TEAM IS HERE TO IDENTIFY BOTH YOUR STRENGTHS AND WEAKNESSES, WITH THE AMBITION TO UNDERSTAND HOW TO BEST ASSIST IN GROWING YOUR BRAND AND REACHING YOUR COMMUNITY.</p>
</article>
<header class="holder__head bg-0" style=" --t:1; --b:2;">2 STEP — PUTTING THE PIECESTOGETHER</header>
<article class="holder__item bg-0" style="--t:1;">
<h2>PUTTING THE PIECESTOGETHER</h2>
<p>BASED ON RESEARCH AND DISCUSSIONS, WE WILL SINGLE OUT THE BEST MEDIUM OR MEDIUMS TO TRANSLATE YOUR BRAND.</p>
</article>
<header class="holder__head bg-2" style="--t:2; --b:1;">3 STEP — FINE-TUNING YOUR CONTENT</header>
<article class="holder__item bg-2" style="--t:2;">
<h2>FINE-TUNING YOUR CONTENT</h2>
<p>BY PRESENTING OUR WORK TO YOU THROUGH OPEN DIALOGUE, WE WILL FINE-TUNE YOUR CONTENT BASED ON FEEDBACK AND OUR COLLECTIVE EXPERTISE.</p>
</article>
<header class="holder__head bg-1" style="--t:3; --b:0;">4 STEP — COMPLETING THE PICTURE</header>
<article class="holder__item bg-1" style="--t:3;">
<h2>COMPLETING THE PICTURE</h2>
<p>WHETHER PRODUCING PHOTOGRAPHY, FILM, CURATING YOUR DIGITAL PLATFORMS OR CREATING A COHESIVE BRAND IDENTITY AND STRATEGY, WE WILL PACKAGE UP ALL OUR WORK AND ASSETS INTO AN ORGANISED LIBRARY A PACKAGE THAT IS SIMPLE FOR YOU TO ENGAGE WITH.</p>
</article>
</section>
<article class="bg-4"><h2>First footer</h2></article>
<article class="bg-0"><h2>Second footer</h2></article>
Since adding manually those 0..N
and N..0
index multipliers can be tedious — see: Staggered animations using CSS on how such a task can be achieved using JavaScript's Element.style.setProperty():
Example:
const els = (sel, el) => (el || document).querySelectorAll(sel);
els(".holder").forEach((elHolder) => {
const elsHead = els(".holder__head", elHolder);
const elsItem = els(".holder__item", elHolder);
const tot = elsHead.length;
elsHead.forEach((elHead, i) => {
elHead.style.setProperty("--t", i);
elHead.style.setProperty("--b", tot - 1 - i);
});
elsItem.forEach((elItem, i) => {
elItem.style.setProperty("--t", i);
elItem.style.paddingBottom = `${tot * 1.2}em`
});
});
/*QuickReset*/ *, *::before, *::after { margin: 0; box-sizing: border-box; }
html, body { scroll-behavior: smooth; }
body { font: clamp(11px, 2.5vmin, 18px)/1.4 sans-serif; color: #252525; }
article {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
font-size: 1.2em;
}
.holder {} /* Whatever you like */
.holder__head {
padding: 0.4em 2em;
position: sticky;
z-index: 1;
bottom: calc(var(--b) * 2em);
top: calc(var(--t) * 2em);
}
.holder__item {
padding: 2rem;
position: sticky;
height: 100vh;
top: calc((var(--t) + 1) * 1.6em);
}
/* Just some colors... */
.bg-0 { background: #000000; color: #b3ab9d;}
.bg-1 { background: #817466; }
.bg-2 { background: #a2a093; }
.bg-3 { background: #a5882a; }
.bg-4 { background: #b29d73; }
<article class="bg-0"><h1>How We Work</h1></article>
<article><h2>Welcome</h2><p>Scroll down to find out more</p></article>
<section class="holder">
<header class="holder__head bg-3">1 STEP — IDENTIFYING STRENGTHS AND WEAKNESSES</header>
<article class="holder__item bg-3">
<h2>IDENTIFYING STRENGTHS AND WEAKNESSES</h2>
<p>OUR MULTIDISCIPLINARY TEAM IS HERE TO IDENTIFY BOTH YOUR STRENGTHS AND WEAKNESSES, WITH THE AMBITION TO UNDERSTAND HOW TO BEST ASSIST IN GROWING YOUR BRAND AND REACHING YOUR COMMUNITY.</p>
</article>
<header class="holder__head bg-0">2 STEP — PUTTING THE PIECESTOGETHER</header>
<article class="holder__item bg-0">
<h2>PUTTING THE PIECESTOGETHER</h2>
<p>BASED ON RESEARCH AND DISCUSSIONS, WE WILL SINGLE OUT THE BEST MEDIUM OR MEDIUMS TO TRANSLATE YOUR BRAND.</p>
</article>
<header class="holder__head bg-2">3 STEP — FINE-TUNING YOUR CONTENT</header>
<article class="holder__item bg-2">
<h2>FINE-TUNING YOUR CONTENT</h2>
<p>BY PRESENTING OUR WORK TO YOU THROUGH OPEN DIALOGUE, WE WILL FINE-TUNE YOUR CONTENT BASED ON FEEDBACK AND OUR COLLECTIVE EXPERTISE.</p>
</article>
<header class="holder__head bg-1">4 STEP — COMPLETING THE PICTURE</header>
<article class="holder__item bg-1">
<h2>COMPLETING THE PICTURE</h2>
<p>WHETHER PRODUCING PHOTOGRAPHY, FILM, CURATING YOUR DIGITAL PLATFORMS OR CREATING A COHESIVE BRAND IDENTITY AND STRATEGY, WE WILL PACKAGE UP ALL OUR WORK AND ASSETS INTO AN ORGANISED LIBRARY A PACKAGE THAT IS SIMPLE FOR YOU TO ENGAGE WITH.</p>
</article>
</section>
<article class="bg-4"><h2>First footer</h2></article>
<article class="bg-0"><h2>Second footer</h2></article>
Upvotes: 0
Reputation: 2214
I know its a bit late but as I don't ever like to be beaten by javascript I found a working solution that is stable, doesn't matter how quickly you scroll it works smoothly, may need to tweak the transform: translateX for each item1Move, item2Move etc in the css but this should be must more suitable for you as it uses IntersectionObserver
const divs = document.querySelectorAll(".holder__item")
const animClasses = [
"item1Move",
"item2Move",
"item3Move",
"item4Move",
]
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
const currentIndex = Array.from(divs).indexOf(entry.target);
//console.log(entry.boundingClientRect.y)
if (entry.isIntersecting) {
divs[currentIndex].classList.add(animClasses[currentIndex])
} else {
if (entry.boundingClientRect.y > 0) {
divs[currentIndex].classList.remove(animClasses[currentIndex])
}
}
})
})
divs.forEach(divs => {
observer.observe(divs)
});
*,
*::before,
*::after {
box-sizing: border-box;
}
* {
margin: 0;
}
html,
body {
height: 100%;
scroll-behavior: smooth;
}
section {
height: 100vh;
}
.classic {
background-color: pink;
}
.holder {
text-align: center;
display: flex;
position: relative;
justify-content: center;
align-items: center;
position: sticky;
top: 0px;
overflow: hidden;
}
.holder h2 {
font-size: 6rem;
}
.holder__item {
height: 100vh;
width: 100vw;
position: absolute;
transition: all 1s !important;
}
.holder__item.item1 {
height: calc(100vh - 80px) !important;
top: calc(100vh - 80px) !important;
border: 1px solid black;
background-color: teal;
}
.item1Move {
transform: translateY(-75vh) !important;
}
.holder__item.item2 {
height: calc(100vh - 60px) !important;
top: calc(100vh - 60px) !important;
border: 1px solid red;
background-color: crimson;
}
.item2Move {
transform: translateY(-70vh) !important;
}
.holder__item.item3 {
height: calc(100vh - 40px) !important;
top: calc(100vh - 40px) !important;
border: 1px solid purple;
background-color: DarkSalmon;
}
.item3Move {
transform: translateY(-65vh) !important;
}
.holder__item.item4 {
height: calc(100vh - 20px) !important;
top: calc(100vh - 20px) !important;
background-color: tomato;
border: 1px solid blue;
}
.item4Move {
transform: translateY(-60vh) !important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section class="classic">
</section>
<section class="holder">
<div class="holder__placeholder">
<h2>Hello world.</h2>
<p>Nice to see you again.</p>
</div>
<div class="holder__item item1">
<p>hello hello</p>
</div>
<div class="holder__item item2"></div>
<div class="holder__item item3"></div>
<div class="holder__item item4"></div>
</section>
https://codepen.io/ptahume/pen/MWVJoVG
Let me know what you think
Upvotes: -1
Reputation: 2214
So this works, but if you scroll too quickly it can be a bit buggy, it's best to test it in full view so check out: https://fiddle.jshell.net/PatrickHume/ob1shq6j/show or if that fails this should work https://jsfiddle.net/PatrickHume/ob1shq6j/show
jsfiddle: https://jsfiddle.net/PatrickHume/ob1shq6j/
I tried experimenting with jquery debounce & throttle, I even looked at using a timer to work out when the user stops scrolling and attempting to use that to re-position any miss positioned divs as per detect when user stops scrolling, the timer example is here:
https://jsfiddle.net/PatrickHume/pzcqt85s/
If the user scrolls relatively slowly it all works fine, but just a bit buggy if you scroll too quickly, that's why in the example in the jsfiddle and below I declare everything on page load to optimise performance
let limit = 5;
let timer = 0;
let hPrev = 0;
let itemHeight = 0;
let part1 = 0;
let part2 = 0;
let part3 = 0;
let part4 = 0;
let hDoc = 0;
let scrollPos = 0;
let item1 = null;
let item2 = null;
let item3 = null;
let item4 = null;
$(function() {
$(".classic, .holder, .holder__item").each(function() {
hDoc += $(this).height();
});
$("body").height(hDoc);
hPrev = $(".classic").height();
itemHeight = $(".item1").height();
part1 = Math.floor(hPrev + itemHeight);
part2 = Math.floor(hPrev + itemHeight * 2);
part3 = Math.floor(hPrev + itemHeight * 3);
part4 = Math.floor(hPrev + itemHeight * 4);
item1 = $(".item1");
item2 = $(".item2");
item3 = $(".item3");
item4 = $(".item4");
window.addEventListener("scroll", Scroll, false);
});
function Scroll() {
scrollPos = Math.floor($(document).scrollTop());
if (scrollPos > hPrev &&
scrollPos < part1) {
item1.css({
transform: "translateY(-" + Math.floor(scrollPos - hPrev) + "px"
});
}
if (
scrollPos > part1 &&
scrollPos < part2
) {
item2.css({
transform: "translateY(-" + Math.floor(scrollPos - part1) + "px"
});
}
if (
scrollPos > part2 &&
scrollPos < part3
) {
item3.css({
transform: "translateY(-" + Math.floor(scrollPos - part2) + "px"
});
}
if (
scrollPos > part3 &&
scrollPos < part4
) {
item4.css({
transform: "translateY(-" + Math.floor(scrollPos - part3) + "px"
});
}
}
*,
*::before,
*::after {
box-sizing: border-box;
}
* {
margin: 0;
}
html,
body {
height: 100%;
scroll-behavior: smooth;
}
section {
height: 100vh;
}
.classic {
background-color: pink;
}
.holder {
text-align: center;
display: flex;
justify-content: center;
align-items: center;
position: sticky;
top: 0px;
overflow: hidden;
}
.holder h2 {
font-size: 6rem;
}
.holder__item {
height: 100vh;
width: 100vw;
position: absolute;
}
.holder__item.item1 {
height: calc(100vh - 80px) !important;
top: calc(100vh - 80px) !important;
border: 1px solid black;
background-color: teal;
}
.holder__item.item2 {
height: calc(100vh - 60px) !important;
top: calc(100vh - 60px) !important;
border: 1px solid red;
background-color: crimson;
}
.holder__item.item3 {
height: calc(100vh - 40px) !important;
top: calc(100vh - 40px) !important;
border: 1px solid purple;
background-color: DarkSalmon;
}
.holder__item.item4 {
height: calc(100vh - 20px) !important;
top: calc(100vh - 20px) !important;
background-color: tomato;
border: 1px solid blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section class="classic">
</section>
<section class="holder">
<div class="holder__placeholder">
<h2>Hello world.</h2>
<p>Nice to see you again.</p>
</div>
<div class="holder__item item1">
<p>hello part 1</p>
</div>
<div class="holder__item item2">
<p>hello part 2</p>
</div>
<div class="holder__item item3">
<p>hello part 3</p>
</div>
<div class="holder__item item4">
<p>hello part 4</p>
</div>
</section>
AS buggy as this is, do you think it's usable?
Upvotes: -1