<div class="container">
<div class="jumps">Jump to: <a href="#end" id="link">bottom</a> | <a href="#top">top</a></div>
<section class="panel pin">
<div class="standard">
<h2>Standard behavior</h2>
<div class="boxes">
<div class="box box-1 blue">1</div>
<div class="box box-2 red">2</div>
<div class="box box-3 green">3</div>
<div class="box box-4 purple">4</div>
<div class="box box-5">5</div>
</div>
</div>
<div class="features">
<div class="boxes">
<div class="box box-1 blue">1</div>
<div class="box box-2 red">2</div>
<div class="box box-3 green">3</div>
<div class="box box-4 purple">4</div>
<div class="box box-5">5</div>
</div>
<h2>With features:</h2>
<div style="display: inline-block">
<div id="checkboxes">
<label><input id="checkboxPO" type="checkbox" value="preventOverlaps" checked/> <code>preventOverlaps</code></label>
<label><input id="checkboxFSE" type="checkbox" value="fastScrollEnd" checked/> <code>fastScrollEnd</code></label>
</div>
</div>
</div>
</section>
<section class="panel move-1 gray"></section>
<section class="panel move-2 blue"></section>
<section class="panel move-3 red"></section>
<section class="panel move-4 green"></section>
<section class="panel move-5 purple"></section>
<section class="panel spacer gray"></section>
<a name="end"></a>
</div>
<div class="explanation-container">
<div class="explanation">
<div class="tab purple">Explanation</div>
<div class="content">
<section class="panel purple">
<div>
<p>Scroll down <strong>slowly</strong> to trigger <i>non-scrubbing</i> animations. Looks great, right? Then try scrolling <i>fast</i> and notice how the animations overlap! </p>
<ul>
<li><code>preventOverlaps: true</code> kicks in when the ScrollTrigger is about to affect its animation (e.g. a toggleAction) - it finds other [prior] ScrollTrigger-based animations and forces them to their end state to avoid overlaps.</li>
<li> <code>fastScrollEnd: true</code> kicks in only when you <strong>LEAVE</strong> the ScrollTrigger's area; it simply says <i>"how fast was the scroll onLeave/onLeaveBack? If it's above the 2500px/second, force the animation to its end immediately"</i>.</li>
</ul>
</div>
</section>
<section class="panel gray">
<div>
<h2>Advanced configuration</h2>
<ul>
<li>If <code>preventOverlaps</code> is a <strong>string</strong>, it acts as a group name so that you can control which ScrollTriggers affect each other's preventOverlaps behavior. It's like setting <code>preventOverlaps: true</code> but only for other ScrollTriggers with the matching string. For example, you could assign <code>preventOverlaps: "group1"</code> (or any arbitrary string) to 3 of your ScrollTriggers so that each of them only prevents overlaps from the other ScrollTriggers that have that same "group1" value.
</li>
<li>If <code>preventOverlaps</code> is a <strong>function</strong>, it'll be called <i>before</i> the non-scrub animation is affected (unlike other callbacks which occur <i>after</i> it's affected), so you can do whatever you want in preparation. All ScrollTriggers have a <code>getTrailing()</code> method you can use to get an Array of all ScrollTriggers that precede this one in the updating order according to the current scroll direction. Use the <code>endAnimation()</code> method to force a ScrollTrigger's animation to jump to its end state according to its direction (so if it's going backwards, it'll <code>.progress(0)</code> if it's forward, it'll <code>.progress(1)</code>).
<pre class="prettyprint">preventOverlaps: (self) => {
self.getTrailing().forEach((trigger) => {
trigger.endAnimation();
});
}</pre>
</li>
<li>If <code>fastScrollEnd</code> is a <strong>number</strong>, it will be interpreted as the velocity threshold in pixels-per-second, like <code>fastScrollEnd: 1000</code> would only activate if the velocity is more than 1000 px/second in either direction when leaving the ScrollTrigger's active area.</li>
</ul>
</div>
</section>
</div>
</div>
</div>
<header>
<a href="https://greensock.com/scrolltrigger">
<img class="greensock-icon" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/scroll-trigger-logo-light.svg" width="200" height="64" />
</a>
</header>
* {
box-sizing: border-box;
}
.jumps {
color: dodgerblue;
z-index: 5;
top: 20px;
left: 50%;
transform: translateX(-50%);
position: fixed;
color: #777;
}
.jumps a {
color: dodgerblue;
}
body {
background-color: #222;
color: #ccc;
font-size: 17px;
line-height: 1.4;
font-weight: 300;
overscroll-behavior: none;
}
h1, h2 {
color: white;
font-weight: 400;
margin-bottom: 2rem;
}
h1 {
font-size: 40px;
}
h1, h2, p, li {
max-width: none;
}
.panel p, .panel li {
color: #ddd;
font-weight: 300;
}
.panel {
font-weight: 300;
color: white;
height: auto;
min-height: 100vh;
}
.container {
position: relative;
display: flex;
flex-flow: column;
align-items: center;
}
.pin {
background-color: #111;
min-height: 100vh;
z-index: 1;
display: flex;
flex-direction: column;
width: 94%;
padding: 0;
position: fixed;
}
.standard, .features {
width: 100%;
padding: 5px;
}
.standard {
border-bottom: 2px solid #555;
}
.features h2 {
margin-bottom: 0;
}
.boxes {
position: relative;
width: 100%;
display: flex;
justify-content: space-between;
}
.box {
width: 5%;
position: relative;
background-color: dodgerblue;
padding: 1%;
border-radius: 10px;
transform: scale(0);
}
.blue {
background-color: dodgerblue;
background-image: none;
}
.red {
background-color: #a90000;
background-image: none;
}
.purple {
background-color: #541c61;
background-image: none;
}
.green {
background-color: #3c7413;
background-image: none;
}
.gray {
background-color: #111;
background-image: none;
}
#checkboxes {
padding: 12px;
background-color: #111;
text-align: center;
display: flex;
justify-content: space-evenly;
flex-wrap: wrap;
}
#checkboxes input {
width: 20px;
height: 20px;
vertical-align: middle;
}
#checkboxes label {
white-space: nowrap;
margin: 0 14px;
}
.panel p code, .panel li code {
background-color: rgba(0,0,0,0.25);
color: white;
padding: 2px 6px;
}
.panel li {
margin-bottom: 10px;
}
.box-5 {
visibility: hidden;
}
.explanation {
position: relative;
top: 0;
transform: translateY(-50px);
text-align: center;
max-width: 900px;
margin: 0 auto;
display: inline-block;
/*min-width: 80%;*/
/*left: 50%;*/
}
.explanation-container {
text-align: center;
left: 0;
right: 0;
top: 100vh;
bottom: 0;
position: fixed;
z-index: 100;
overflow: visible;
}
.explanation .tab {
height: 50px;
font-size: 20px;
color: white;
text-align: center;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
padding: 0 30px;
line-height: 50px;
display: inline-block;
cursor: pointer;
transform: translateY(1px);
border-top: 1px solid #aa70c8;
}
.explanation .content {
overflow: auto;
height: calc(100vh - 65px);
border: 1px solid #555;
}
.explanation .panel {
height: auto;
min-height: auto;
padding: 40px;
}
.explanation h2 {
margin: 0;
}
.explanation p, .explanation h2, .explanation ul {
max-width: 800px;
}
.prettyprinted {
overflow: auto;
max-width: 80vw;
}
@media (max-height: 500px) {
.panel h1, .panel h2 {
font-size: 24px;
}
.panel p {
font-size: 17px;
}
#checkboxes input {
width: 12px;
height: 12px;
}
#checkboxes label {
font-size: 14px;
margin: 0 5px;
}
.explanation .tab {
height: 32px;
line-height: 32px;
font-size: 18px;
padding: 0 14px;
}
.explanation {
transform: translateY(-32px);
}
}
gsap.registerPlugin(ScrollTrigger, ScrollToPlugin);
let boxes = gsap.utils.toArray(".boxes"),
checkboxPO = document.querySelector("#checkboxPO"),
checkboxFSE = document.querySelector("#checkboxFSE");
function setup() {
ScrollTrigger.getAll().forEach(t => {t.scroll(0); t.kill(true); });
gsap.set(".box", {
scale: (i, target) => target.classList.contains("box-1") ? 1 : 0
});
boxes.forEach((container, i) => {
let preventOverlaps = i === 1 && checkboxPO.checked && "group1",
fastScrollEnd = i === 1 && checkboxFSE.checked,
box = gsap.utils.toArray(".box", container);
box.pop();
box.forEach((el, i) => {
let tl = gsap.timeline({
scrollTrigger: {
trigger: ".move-" + (i + 2),
start: "top center",
end: "+=100%",
toggleActions: "play none none reverse",
preventOverlaps: preventOverlaps,
fastScrollEnd: fastScrollEnd
}
});
if (i) {
tl.fromTo(el, {scale: 0}, {scale: 1, duration: 0.5, immediateRender: false, ease: "back"}, 0);
}
tl.fromTo(el, {left: "0%"}, {left: "23.75%", duration: 2, immediateRender: false, ease: "power1.inOut"}, i ? "-=0.25" : 0);
tl.to(el, {scale: 0.6, backgroundColor: "#555", duration: 0.5, opacity: 0.5, ease: "power1.in"});
});
});
}
checkboxPO.addEventListener("change", setup);
checkboxFSE.addEventListener("change", setup);
setup();
// explanation tab
let isOpen;
document.querySelector(".explanation .tab").addEventListener("click", () => {
isOpen = !isOpen;
gsap.to(".explanation", {
top: isOpen ? ((window.innerHeight - 65) / window.innerHeight * -100) + "vh" : "0",
ease: "power4",
duration: 0.5
});
});
// jump links
gsap.utils.toArray(".jumps a").forEach((el, i) => {
el.addEventListener("click", e => gsap.to(window, {scrollTo: i ? 0 : "max", overwrite: true}) && e.preventDefault());
});
// making the code pretty/formatted.
PR.prettyPrint();