Code:
JavaScript
// Mobile optimized code
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
<script>
gsap.registerPlugin(ScrollTrigger);
const isMobile = window.innerWidth < 768;
// Desktop: all 14 positions
const desktopPositions = [
{ top: "0%", left: "0%" },
{ top: "0%", left: "10%" },
{ top: "0%", left: "60%" },
{ top: "16%", left: "30%" },
{ top: "16%", left: "40%" },
{ top: "16%", left: "90%" },
{ top: "32%", left: "65%" },
{ top: "32%", left: "75%" },
{ top: "48%", left: "0%" },
{ top: "64%", left: "30%" },
{ top: "64%", left: "50%" },
{ top: "64%", left: "90%" },
{ top: "80%", left: "20%" },
{ top: "80%", left: "70%" },
];
// Mobile: fewer, tighter positions (adjust to taste)
const mobilePositions = [
{ top: "5%", left: "10%" },
{ top: "5%", left: "55%" },
{ top: "30%", left: "30%" },
{ top: "55%", left: "5%" },
{ top: "55%", left: "60%" },
{ top: "80%", left: "35%" },
];
const positions = isMobile ? mobilePositions : desktopPositions;
const maxImages = positions.length;
// Hide images beyond the mobile limit
const allImages = document.querySelectorAll(".image-pop");
if (isMobile) {
allImages.forEach((img, i) => {
if (i >= maxImages) img.style.display = "none";
});
}
// Sizing values per breakpoint
const stackedSize = isMobile
? { scale: 1.2, width: "200px", height: "260px" }
: { scale: 1.5, width: "300px", height: "400px" };
const scatteredSize = isMobile
? { width: "100px", height: "120px" }
: { width: "175px", height: "200px" };
gsap.set(".image-pop", {
top: "50%",
left: "50%",
transform: "translate(-50%, -50%) scale(0)",
});
ScrollTrigger.create({
trigger: ".image-pop",
start: "top 100%",
onEnter: () => animateImages(),
once: true,
});
function animateImages() {
gsap.to(".image-pop", {
...stackedSize,
stagger: 0.15,
duration: 0.75,
ease: "power2.out",
delay: 0.5,
onComplete: scatterAndShrink,
});
}
function scatterAndShrink() {
gsap.to(".image-pop:not([style*='display: none'])", {
top: (i) => positions[i].top,
left: (i) => positions[i].left,
transform: "none",
...scatteredSize,
stagger: 0.075,
duration: 0.75,
ease: "power2.out",
});
}
</script>
// NOT Mobile optimized code
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
<script>
gsap.registerPlugin(ScrollTrigger);
const positions = [
{ top: "0%", left: "0%" },
{ top: "0%", left: "10%" },
{ top: "0%", left: "60%" },
{ top: "16%", left: "30%" },
{ top: "16%", left: "40%" },
{ top: "16%", left: "90%" },
{ top: "32%", left: "65%" },
{ top: "32%", left: "75%" },
{ top: "48%", left: "0%" },
{ top: "64%", left: "30%" },
{ top: "64%", left: "50%" },
{ top: "64%", left: "90%" },
{ top: "80%", left: "20%" },
{ top: "80%", left: "70%" },
];
gsap.set(".image-pop", {
top: "50%",
left: "50%",
transform: "translate(-50%, -50%) scale(0)",
});
ScrollTrigger.create({
trigger: ".image-pop",
start: "top 90%",
onEnter: () => animateImages(),
once: true,
});
function animateImages() {
gsap.to(".image-pop", {
scale: 1.5,
width: "300px",
height: "400px",
stagger: 0.15,
duration: 0.75,
ease: "power2.out",
delay: 0.5,
onComplete: scatterAndShrink,
});
}
function scatterAndShrink() {
gsap.to(".image-pop", {
top: (i) => positions[i].top,
left: (i) => positions[i].left,
transform: "none",
width: "175px",
height: "200px",
stagger: 0.075,
duration: 0.75,
ease: "power2.out",
});
}
</script>
CSS
.gallery {
height: calc(100vh - 60px);
}