GSAP ScrollTrigger on Mobile — Every Mistake I Made
I spent a week making my portfolio responsive. The desktop version was perfect. Then I opened it on a phone.
Here's every ScrollTrigger problem I hit and how I fixed each one.
The 100vh Trap
On mobile Safari, 100vh includes the address bar. When the bar collapses on scroll, your "full height" section suddenly has empty space at the bottom.
/* Bad */
.hero { min-height: 100vh; }
/* Better */
.hero { min-height: 100dvh; } /* dynamic viewport height */
/* Best for JS */
const vh = window.innerHeight; /* actual visible height */
For ScrollTrigger pins, I stopped using 100vh entirely below 1025px. Auto height with padding works better.
Invisible Content
My scroll-triggered reveals used start: "top 80%". On desktop, this means "trigger when the element's top edge reaches 80% from the top of the viewport." Works great.
On a phone in portrait, the viewport is much taller relative to content width. Elements that were comfortably in view at 80% on desktop are still offscreen on mobile.
// The fix: more aggressive trigger on portrait
const triggerStart = isPortrait ? "top 95%" : "top 80%";
ScrollTrigger.create({
trigger: sectionRef,
start: triggerStart,
onEnter: () => gsap.to(sectionRef, { opacity: 1, y: 0 }),
});
Pins Create Gaps
Pinned sections on desktop feel great — the content sticks while you scroll through it. On mobile, pins create weird gaps because the scroll distance calculation assumes a fixed viewport height.
My solution: disable all pins below 1025px. Replace pinned scroll effects with simple vertical stacking with generous spacing.
if (w >= 1025) {
ScrollTrigger.create({
trigger: section,
pin: true,
start: "top top",
end: "+=500",
});
}
// Below 1025px: no pin, just fade-in on scroll
Portrait Detection
"Mobile" isn't just about screen width. An iPad in landscape is 1024px wide — bigger than many laptops. But in portrait, it's 768px wide with a very tall viewport.
The real question is: is the viewport taller than it is wide?
const isPortrait = window.innerWidth < 1025 && window.innerHeight > window.innerWidth;
This catches phones, tablets in portrait, and small laptop windows. The layout switches fundamentally — horizontal project scroll instead of grid, stacked sections instead of side-by-side.
Touch Events
ScrollTrigger works with scroll, not touch. But on mobile, you want swipe gestures and touch interactions that feel native.
The fix: add touch event listeners separately from ScrollTrigger. Don't try to make ScrollTrigger handle touch interactions — it's not designed for that.
Refresh on Orientation Change
When a user rotates their device, every ScrollTrigger calculation is wrong. The viewport dimensions changed, pin distances changed, trigger positions changed.
window.addEventListener("orientationchange", () => {
setTimeout(() => ScrollTrigger.refresh(), 300);
});
The 300ms delay matters. The viewport dimensions don't update instantly after rotation — the browser needs a moment to settle.
Building a site with scroll animations? We build responsive, animated websites at 4UGUSTA Systems.