JSFiddle - React, Tailwind, and code Playground
by HDL52
HTML
<script src="https://unpkg.co/gsap@3/dist/gsap.min.js"></script>
<script src="https://unpkg.com/gsap@3/dist/ScrollTrigger.min.js"></script>
<section>
<h1>Vertical image loop with scroll acceleration with gsap</h1>
<h2 class="credit"><a href="https://thisisadvantage.com" target="_blank">Made by Advantage</a></h2>
</section>
<div class="gallery">
<div class="col">
<div class="image">
<img src="https://raw.githubusercontent.com/Hongda-OSU/PicGo-2.3.1/master/img1u6qw3ad64bz8nsedrd2wddgrax5wew.jpg">
</div>
<div class="image">
<img src="https://raw.githubusercontent.com/Hongda-OSU/PicGo-2.3.1/master/imgl7nga0eot9mfzdog0r7hja2avzx8jzf.jpg">
</div>
<div class="image">
<img src="https://raw.githubusercontent.com/Hongda-OSU/PicGo-2.3.1/master/imgodr8vukcwco0h34p0ngc84462vs454j.jpg">
</div>
</div>
<div class="col">
<div class="image">
<img src="https://raw.githubusercontent.com/Hongda-OSU/PicGo-2.3.1/master/imgj1udos782ybmzugwd1ltkozc9zxj3gz.jpg">
</div>
<div class="image">
<img src="https://raw.githubusercontent.com/Hongda-OSU/PicGo-2.3.1/master/img4gb8ou3p2t5vv347i6lopc13wtm82qw.jpg">
</div>
<div class="image">
<img src="https://raw.githubusercontent.com/Hongda-OSU/PicGo-2.3.1/master/img578ut571o3a1a8kj2su9jra6dz7p5ey.jpg">
</div>
</div>
<div class="col">
<div class="image">
<img src="https://cdn.jsdelivr.net/gh/Hongda-OSU/PicGo/image/8shhfm8bsq648izivnrc32jq9e0d62e.jpg">
</div>
<div class="image">
<img src="https://cdn.jsdelivr.net/gh/Hongda-OSU/PicGo/image/oojeu86ioebk0vnvnicn0q59u030jfv.jpg">
</div>
<div class="image">
<img src="https://cdn.jsdelivr.net/gh/Hongda-OSU/PicGo/image/2xu5x71d85ctcsi9vsh3oa5vn6fskhq.jpg">
</div>
</div><a
</div>
CSS
body {
font-family: Termina, sans-serif;
color: #111;
background: #eee;
overflow-x: hidden;
}
section {
padding: 10vw;
height: 100%;
min-height: 500vh;
overflow: visible;
display: flex;
flex-direction: column;
justify-content: center;
overflow-y: visible;
}
.credit {
text-align: center;
z-index: 999;
font-size: 0.8rem;
position: fixed;
bottom: 1rem;
right: 1rem;
writing-mode: vertical-rl;
text-orientation: mixed;
a {
color: #fff;
}
}
* {
box-sizing: border-box;
}
h1 {
font-weight: 800;
margin: 2rem auto;
font-size: clamp(3vw, 2rem, 4rem);
text-align: center;
z-index: 999;
max-width: 800px;
mix-blend-mode: difference;
pointer-events: none;
color: white;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: grid;
place-items: center;
}
.gallery {
z-index: 1;
display: flex;
flex-direction: row;
justify-content: center;
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 50%;
transform: translateX(-50%);
overflow: visible;
@media (max-width: 768px) {
width: 160%;
}
}
.col {
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
align-self: flex-start;
justify-self: flex-start;
}
.col:nth-child(2) {
align-self: flex-end;
justify-self: flex-end;
}
.image {
width: 100%;
height: clamp(500px, 50vw, 700px);
filter: saturate(0);
object-fit: cover;
padding: 1rem;
}
.image:hover {
z-index: 99999999999 !important;
filter: saturate(1);
}
img {
transition: 0.3s ease-out;
overflow: hidden;
box-shadow:
0 2.8px 2.2px rgba(0, 0, 0, 0.034),
0 6.7px 5.3px rgba(0, 0, 0, 0.048),
0 12.5px 10px rgba(0, 0, 0, 0.06),
0 22.3px 17.9px rgba(0, 0, 0, 0.072),
0 41.8px 33.4px rgba(0, 0, 0, 0.086),
0 100px 80px rgba(0, 0, 0, 0.12);
width: 100%;
height: 100%;
object-fit: cover;
}
JavaScript
console.clear()
gsap.registerPlugin(ScrollTrigger)
const additionalY = {
val: 0,
}
let additionalYAnim
let offset = 0
const cols = gsap.utils.toArray(".col")
cols.forEach((col, i) => {
const images = col.childNodes
// DUPLICATE IMAGES FOR LOOP
images.forEach((image) => {
var clone = image.cloneNode(true)
col.appendChild(clone)
})
// SET ANIMATION
images.forEach((item) => {
let columnHeight = item.parentElement.clientHeight
let direction = i % 2 !== 0 ? "+=" : "-=" // Change direction for odd columns
gsap.to(item, {
y: direction + Number(columnHeight / 2),
duration: 20,
repeat: -1,
ease: "none",
modifiers: {
y: gsap.utils.unitize((y) => {
if (direction == "+=") {
offset += additionalY.val
y = (parseFloat(y) - offset) % (columnHeight * 0.5)
} else {
offset += additionalY.val
y = (parseFloat(y) + offset) % -Number(columnHeight * 0.5)
}
return y
}),
},
})
})
})
const imagesScrollerTrigger = ScrollTrigger.create({
trigger: "section",
start: "top 50%",
end: "bottom 50%",
onUpdate: function (self) {
const velocity = self.getVelocity()
if (velocity > 0) {
if (additionalYAnim) additionalYAnim.kill()
additionalY.val = -velocity / 2000
additionalYAnim = gsap.to(additionalY, {
val: 0,
})
}
if (velocity < 0) {
if (additionalYAnim) additionalYAnim.kill()
additionalY.val = -velocity / 3000
additionalYAnim = gsap.to(additionalY, {
val: 0,
})
}
},
})