Search the Community
Showing results for tags 'scrolltrigger'.
-
Hi, pretty new to GSAP and Scrolltrigger but really want to get familiar with it and use it going forward. I'm trying to get spltjs text animating in on scroll with Scrolltrigger (Yes I know SplitText plugin exists but testing out external library functions with ST) but not having much luck. Codepen:
-
Hi GSAP team, I’m running into an issue with ScrollTrigger pinning. I have a section with a .menu__image__wrapper that I want to pin while scrolling. On first page load, the pin doesn’t work (the image disappears). If I switch categories in my app (which destroys and re-inits the triggers), the pin suddenly works correctly. I also noticed earlier that I had a y transform (GSAP.set(..., { y: ... })) on the pinned element itself. Removing that transform fixed some glitching, but I still sometimes see the pin fail on first load. Video Of Issue:https://www.loom.com/share/f02251051e0d40a5b0595020b76bd8b7?sid=2a13507c-e9aa-4d87-9644-47d2f085eb01 Here’s a simplified version of my setup: Here is the animation class code for the menuPIN only: import GSAP from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; GSAP.registerPlugin(ScrollTrigger); export default class MenuPin { constructor() { this.mm = null; // matchMedia instance this.triggers = []; // ScrollTrigger instances this.clickHandlers = []; // mobile click handlers this.currentSection = null; } setupSection(section) { if (!section) return; const runSetup = () => { // destroy previous triggers/spacers this.destroy(); this.currentSection = section; this.mm = GSAP.matchMedia(); const initDesktop = () => { this.setupDesktop(section); }; // Desktop this.mm.add("(min-width:1024px)", () => { const firstImg = section.querySelector(".menu__image"); if (firstImg && !firstImg.complete) { firstImg.addEventListener("load", initDesktop, { once: true }); setTimeout(initDesktop, 300); } else { initDesktop(); } return () => { }; }); // Mobile this.mm.add("(max-width:1023px)", () => { requestAnimationFrame(() => this.setupMobile(section)); return () => { }; }); }; // Run immediately if DOM is ready, otherwise wait for DOMContentLoaded if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", runSetup); } else { runSetup(); } } setupDesktop(section) { const imageWrapper = section.querySelector(".menu__image__wrapper"); if (!imageWrapper) return; const images = Array.from(section.querySelectorAll(".menu__image")); const imageHeight = section.querySelector(".menu__image").getBoundingClientRect().height const items = Array.from(section.querySelectorAll(".menu__item")); const itemHeight = section.querySelector(".menu__item").getBoundingClientRect().height if (!items.length) return; // spacer at the end let spacer = section.querySelector(".menu__spacer"); if (!spacer) { spacer = document.createElement("div"); spacer.classList.add("menu__spacer"); section.appendChild(spacer); } spacer.style.height = `${imageHeight - itemHeight - 41}px`; const pinDuration = section.scrollHeight; // Pin image wrapper const pinTrigger = ScrollTrigger.create({ trigger: section, start: "top top", end: () => `+=${pinDuration}`, pin: imageWrapper, pinSpacing: false, markers: false, }); this.triggers.push(pinTrigger); // Set first image visible right away // GSAP.set(images[0], { autoAlpha: 1 }); // Then set up triggers items.forEach((item, i) => { const t = ScrollTrigger.create({ trigger: item, start: `top-=25 top`, end: `bottom-=25 top`, // onEnter: () => this.showImage(images, i), // onEnterBack: () => this.showImage(images, i), markers: true, }); this.triggers.push(t); }); } setupMobile(section) { const images = Array.from(section.querySelectorAll(".menu__image")); const items = Array.from(section.querySelectorAll(".menu__item")); // remove previous handlers items.forEach(item => { if (item._menuPinHandler) { item.removeEventListener("click", item._menuPinHandler); delete item._menuPinHandler; } }); // add click handlers items.forEach((item, i) => { const handler = () => this.showImage(images, i); item.addEventListener("click", handler); item._menuPinHandler = handler; this.clickHandlers.push({ el: item, handler }); }); // show first image // this.showImage(images, 0); } showImage(images, index) { images.forEach((img, i) => { GSAP.set(img, { autoAlpha: i === index ? 1 : 0, border: i === index ? "3px solid red" : "none" }); }); GSAP.to(images[index], { autoAlpha: 1, duration: 0.25, ease: "expo.out" }); } destroy() { // kill triggers this.triggers.forEach(t => t && t.kill()); this.triggers = []; // remove click handlers this.clickHandlers.forEach(({ el, handler }) => { el.removeEventListener("click", handler); if (el._menuPinHandler) delete el._menuPinHandler; }); this.clickHandlers = []; // remove spacer if (this.currentSection) { const sp = this.currentSection.querySelector(".menu__spacer"); if (sp) sp.remove(); } this.currentSection = null; } } import Page from '@classes/Page'; import MenuPin from '@animations/MenuPin'; import MobileCategorySwipe from '@animations/MobileCategorySwipe'; import GSAP from 'gsap'; import { ScrollTrigger } from "gsap/ScrollTrigger"; GSAP.registerPlugin(ScrollTrigger); export default class Menu extends Page { constructor() { super({ element: '.menu', elements: { wrapper: '.menu__wrapper', section: '.menu__section', button: '.menu__category__label', }, }); } create() { super.create(); this.menuPin = new MenuPin(); const categoryEl = document.querySelector(".menu__category"); if (categoryEl) { new MobileCategorySwipe({ element: categoryEl }); } const firstSection = document.querySelector(".menu__section.--active"); if (firstSection) { this.menuPin.setupSection(firstSection); } // bind buttons this.elements.button.forEach(btn => { btn.addEventListener("click", () => { const category = btn.dataset.category; this.showCategory(category); }); }); } showCategory(category) { // Hide all sections first this.elements.section.forEach(section => { const isActive = section.dataset.category === category; if (isActive) { section.classList.add('--active'); console.log(section); // Make section visible so ScrollTrigger can measure GSAP.set(section, { autoAlpha: 1 }); // Destroy old triggers before setting up the new one if (this.menuPin) this.menuPin.destroy(); // Setup the pin/animations for the new section this.menuPin.setupSection(section); // Refresh ScrollTrigger after layout changes requestAnimationFrame(() => ScrollTrigger.refresh()); } else { section.classList.remove('--active'); GSAP.set(section, { autoAlpha: 0 }); } }); // Update buttons this.elements.button.forEach(btn => { const isActive = btn.dataset.category === category; btn.classList.toggle('--active', isActive); GSAP.to(btn, { color: isActive ? "#000" : "#CBC4B1", duration: 0.5, ease: "expo.out" }); }); } }
- 3 replies
-
- scrolltrigger
- gsap
-
(and 1 more)
Tagged with:
-
pinspacer There's extra space below the section even though i have given the section a fixed height of 680px
Sid1509 posted a topic in GSAP
There's this extra space below the section even though i have given the section 680px of height.. and also at the end of this sectiona massive space is coming even though i never added it?.. heres the code "use client" import React, { useEffect, useRef } from 'react' import gsap from 'gsap' import { ScrollTrigger } from 'gsap/dist/ScrollTrigger' const steps = [ { id: 1, number: '1', title: 'Initial Discovery & Research', desc: 'We begin by aligning your goals with market research to validate and shape ideas.' }, { id: 2, number: '2', title: 'Brainstorming & Idea Generation', desc: 'We collaborate through brainstorming to spark innovation and explore diverse ideas.' }, { id: 3, number: '3', title: 'Idea Screening & Selection', desc: 'We filter ideas by feasibility, goals, and market fit to focus on those with the highest potential.' }, { id: 4, number: '4', title: 'User Persona Development', desc: 'We craft user personas to tailor concepts around real needs, behaviours, and pain points for user-centric solutions.' }, { id: 5, number: '5', title: 'Feasibility Study & Risk Assessment', desc: 'We assess feasibility and risks to build a strong, strategic foundation for product development.' }, { id: 6, number: '6', title: 'Prototyping & Concept Visualization', desc: 'We create prototypes and wireframes to visualize ideas, refine concepts, and gather stakeholder feedback.' }, { id: 7, number: '7', title: 'Feedback & Iteration', desc: 'We gather feedback to refine prototypes, ensuring concepts meet user needs and market expectations.' }, { id: 8, number: '8', title: 'Defining the Product Roadmap', desc: 'We design a product roadmap that defines timelines, milestones, and goals to guide development.' }, { id: 9, number: '9', title: 'Final Concept Approval', desc: 'The process ends with final concept approval, aligning all stakeholders for development.' } ] function ProductSuccess() { const sectionRef = useRef<HTMLDivElement | null>(null) const cardsContainerRef = useRef<HTMLDivElement | null>(null) const cardRefs = useRef<Array<HTMLDivElement | null>>([]) useEffect(() => { gsap.registerPlugin(ScrollTrigger) // Offset to leave space for fixed navbar (adjust if navbar height changes) const NAV_OFFSET_PX = -45 const section = sectionRef.current const cardsContainer = cardsContainerRef.current const cards = cardRefs.current if (!section || !cardsContainer || cards.length === 0) return // Calculate card height + gap const cardHeight = cards[0]?.offsetHeight || 0 const gap = 16 // mb-4 = 16px const totalCardHeight = cardHeight + gap // Set initial positions - all cards stacked below the viewport cards.forEach((card, index) => { if (index === 0) { gsap.set(card, { y: 0 }) } else { // Position each card below the previous one gsap.set(card, { y: totalCardHeight * index }) } }) // Create timeline for the animation const tl = gsap.timeline({ scrollTrigger: { trigger: section, // start when section hits just below navbar start: `top+=${NAV_OFFSET_PX} top`, end: () => `+=${totalCardHeight * (cards.length - 1) + window.innerHeight}`, scrub: 0.5, pin: true, anticipatePin: 1, } }) // Animate each card group cards.forEach((card, index) => { if (index > 0) { // Move all cards from current index onwards up by one card height const cardsToMove = cards.slice(index) tl.to(cardsToMove, { y: (cardIndex) => { const originalIndex = index + cardIndex const newPosition = totalCardHeight * (originalIndex - index) return newPosition }, duration: 1, ease: "none", stagger: 0 }, index === 1 ? 0 : ">") } }) return () => { ScrollTrigger.getAll().forEach(trigger => trigger.kill()) } }, []) return ( <section ref={sectionRef} className="w-full bg-white py-12 md:py-16 lg:py-20 h-[680px] overflow-hidden"> <div className="max-w-7xl mx-auto px-4 lg:px-0"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12 items-start"> {/* Left: Heading + paragraph (pinned) */} <div className="flex flex-col justify-start lg:sticky lg:top-0"> <h2 className="font-['Inter'] font-semibold text-[28px] md:text-[32px] lg:text-[36px] leading-[120%] text-[#000000] max-w-[550px]"> From Vision to Reality: Our Proven Path to Product Success </h2> <p className="mt-4 md:mt-8 font-['Inter'] font-normal text-[16px] leading-[24px] text-[#00000066] max-w-xl"> Our product ideation and conceptualization is a step-by-step journey from brainstorming and research to prototyping and refinement. We then craft a strategic roadmap to guide your concept into development, ensuring every decision aligns with your vision. </p> </div> {/* Right: Steps list with animation */} <div className="w-full relative"> <div ref={cardsContainerRef} className="relative h-[200px]"> <div className="absolute top-0 left-0 w-full"> {steps.map((s, index) => ( <div key={s.id} ref={el => { cardRefs.current[index] = el }} className="absolute top-0 left-0 w-full" > <div className="flex items-stretch gap-[36px]"> {/* Strip */} <div className="w-[66px] flex flex-col items-center flex-shrink-0 bg-white"> <div className="text-left whitespace-nowrap bg-white font-['Inter'] font-semibold text-[15px] leading-[20px] text-[#000000]"> {`STEP - 0${s.number}`} </div> <div className="flex-1 w-full flex items-start min-h-[120px]"> <div className="mx-auto w-0.5 h-full" style={{ background: 'repeating-linear-gradient(to bottom, #00000033 0px, #00000033 4px, transparent 4px, transparent 6px)' }} /> </div> </div> {/* Card */} <div className="flex-1 mb-4"> <div className="bg-[#FAFAFA] rounded-[12px] p-6"> <div className="flex flex-col items-start"> <div className="w-[48px] h-[48px] rounded-[8px] bg-[#3C82F6] flex items-center justify-center mb-3"> <span className="font-['Inter'] font-bold text-[24px] leading-[24px] text-white">{s.number}</span> </div> <div className="font-['Inter'] font-medium text-[18px] leading-[27px] text-[#0C0C0C]"> {s.title} </div> <div className="mt-1 font-['Inter'] font-normal text-[12px] leading-[20px] text-[#00000066]"> {s.desc} </div> </div> </div> </div> </div> </div> ))} </div> </div> </div> </div> </div> </section> ) } export default ProductSuccess- 4 replies
-
- pin
- scrolltriger
-
(and 1 more)
Tagged with:
-
Hi there! I'm a newbie - been struggling to get this working for a week now. I've created a function that animates the styling of the clicked nav-link. I also created a timeline with a scroll trigger that is supposed to call the same function when each section scrolls into view. (This is the part that's not working.) I think my mistake has something to do with scope? Sorry - I don't even exactly know what that means. The reason I think that, is because when I put the nav styling tweens directly into the Nav Animation timeline, then the animation works. If there is a more efficient way to achieve this please tell me. Thanks so much! Sonya. PS. I did manage to achieve the nav styling using scrollTrigger's toggleClass, but animating the style with css does not look as smooth.
-
Where can I find a tutorial or maybe docs or blogs where I can learn how to make a video be animated on scroll. Like every time I scroll the video runs 1 or 2 frames. I am also interested in animating movement of elements on scroll. Let's say I have 3 card and as I scroll the care apea one by one when I scroll from a position of X= -100 to X= 0 while the hole scrolled is sopped. In other wards when the uses get to a point the normal scroll stops and as they scroll the card move from right to left raveling themself and moving into their position and ten the normal scrolling behavior start again. An example would be https://zoox.com/ . Thank you!
-
Horizontally scrolling thumbnail gallery with parallax effect using gsap and next.js
sanindie posted a topic in GSAP
Hello there, I'm trying to develop a portfolio thumbnail gallery that is horizontally scrollable and have parallax effect with gsap scrollTrigger and with skew applied for animating them based on vertical scroll speed, all this I'm trying to achieve with next.js 15 framework. Dev environment: Next.js 15, GSAP(ScrollTrigger, useGSAP), Lenis Problem: i'm unable the scroll till the last thumbnail. I'm only providing the code of the particular component file where I've implemented the gallery. 'use client'; import { useRef, useLayoutEffect } from 'react'; import { gsap } from 'gsap/dist/gsap'; import { ScrollTrigger } from 'gsap/dist/ScrollTrigger'; import { useGSAP } from '@gsap/react'; import Image from 'next/image'; console.clear(); gsap.registerPlugin(ScrollTrigger, useGSAP); const images = [ '/images/1.jpg', '/images/2.jpg', '/images/3.jpg', '/images/4.jpg', '/images/5.jpg', '/images/6.jpg', '/images/7.jpg', '/images/8.jpg', '/images/9.jpg', '/images/10.jpg', ]; const Gallery = () => { const containerRef = useRef(null); const galleryRef = useRef(null); useGSAP( () => { // Ensure gallery is not empty before calculating dimensions if (!containerRef.current || !galleryRef.current) return; const images = gsap.utils.toArray('.gallery-image-wrapper'); const gallery = galleryRef.current; const totalWidth = images.reduce( (sum, item) => sum + item.offsetWidth, 0, ); // We need to calculate how far to scroll the gallery const scrollDistance = totalWidth - window.innerWidth; // Pin the section and horizontally scroll the gallery wrapper const scrollTween = gsap.to(gallery, { x: -scrollDistance, // Animate to the negative scroll distance ease: 'none', scrollTrigger: { trigger: containerRef.current, pin: true, scrub: 2, start: 'top top', end: `+=${scrollDistance}`, // The end position matches the scroll distance invalidateOnRefresh: true, // Recalculates on resize markers: true, }, }); // quickSetter for optimized velocity skew const skewProxy = { skew: 0 }; const skewSetter = gsap.quickSetter( '.gallery-image-wrapper', 'skewX', 'deg', ); const clamp = gsap.utils.clamp(-10, 10); // Max skew amount in degrees ScrollTrigger.create({ onUpdate: (self) => { let skew = clamp(self.getVelocity() / -100); // Only update the skew if the absolute value is greater than the current one if (Math.abs(skew) > Math.abs(skewProxy.skew)) { skewProxy.skew = skew; gsap.to(skewProxy, { skew: 0, duration: 0.8, ease: 'power3.in', overwrite: true, onUpdate: () => skewSetter(skewProxy.skew), }); } }, }); // Parallax for individual images images.forEach((wrapper) => { const image = wrapper.querySelector('img'); gsap.to(image, { y: '20%', // Adjust the parallax intensity ease: 'none', scrollTrigger: { trigger: wrapper, containerAnimation: scrollTween, // Tie the animation to the horizontal scroll start: 'left right', end: 'right left', scrub: true, }, }); }); // Ensure GSAP recalculates on image load completion // You may need a more robust solution if using a complex image lazy-loading library const imagesLoadedPromise = new Promise((resolve) => { let loadedCount = 0; const totalImages = images.length; if (totalImages === 0) { resolve(); } images.forEach((imgWrapper) => { const img = imgWrapper.querySelector('img'); img.onload = () => { loadedCount++; if (loadedCount === totalImages) { resolve(); } }; }); }); imagesLoadedPromise.then(() => { ScrollTrigger.refresh(); }); }, { scope: containerRef, dependencies: [], revertOnUpdate: true }, ); return ( <section ref={containerRef} className='gallery-container'> <div ref={galleryRef} className='gallery-wrapper'> {images.map((src, index) => ( <div key={index} className='gallery-image-wrapper'> <div className='gallery-image-content'> <Image src={src} alt={`Gallery Image ${index + 1}`} width={400} height={500} className='gallery-image' priority={index === 0} // Optional: Prioritize the first image for faster loading /> </div> </div> ))} </div> </section> ); }; export default Gallery; I'm fetching this Gallery.js inside page.js in the app folder and usinng Lenis wrapper for react in the layout.js file. Please guide me with a prfoper solution.Gallery.js layout.js page.js LenisProvider.js- 1 reply
-
- usegsap
- scrolltrigger
-
(and 2 more)
Tagged with:
-
I want the effect where you swipe and it focuses on the text, it starts to paint, and when the last letter is finished, it continues to go down again but waits until that moment. And the reverse effect, but I can't quite get it right. THANKSS!!
-
GSAP ScrollTrigger horizontal scroll pinSpacing problem when adding an top value for an early start
AbzoHQ posted a topic in GSAP
I’m trying to build a horizontal scroll effect with GSAP + ScrollTrigger. The idea is: when you scroll vertically, the text moves horizontally across the screen. It works fine when the scrollTrigger config is like that scrollTrigger: { trigger: trackSectionRef, start: "top top", scrub: true, pin: true, markers: true, anticipatePin: 1, invalidateOnRefresh: true, }, But whenever I change the start to be "-30% top", the pin spacer adds an annoying white space on the top, tried several solutions, but all of them messed up something in the animation, maybe the problem is that I'm new to GSAP. What I want to achieve, is for the horizontal scroll to start before the scroll section is pinned. And at the same time, I want the scroll section to be pinned correctly without any white spaces, would appreciate your take on the matter. Here is the code : https://codepen.io/abdelrahmanahmed04/pen/XJmxOXM- 4 replies
-
- scrolltrigger
- pin
-
(and 1 more)
Tagged with:
-
Hello, I've been working for a while on a homepage where the client asked me for a specific pinning effect: each section should start being pinned once its top reaches the bottom of the navbar (whose height is stored in the `--navbar-height` CSS variable), and stop being pinned after scrolling for about 150% of the section height. I've tried many variants of this, but the closest I've managed to get is this, which is still wrong since it starts pinning when the bottom of the section hits the bottom of the viewport: ``` sections.forEach((section, i) => { ScrollTrigger.create({ start: "bottom bottom", end: "+=90%", scrub: true, markers: true, trigger: section, pin: true, anticipatePin: 1, }); }); ```
-
Hello everyone, I'm running into some issues with ScrollTrigger and could use some guidance. I'm trying to create multiple scroll animations where images slide in from the side. I managed to get this working by pinning the container itself (https://codepen.io/zumpel96/pen/OPyBboy). However, this isn't the behavior I want, because I'd like all other content to remain pinned or "frozen". As you can see in my working pen, the other content continues to scroll. I've also tried using a single common pinned container, but that approach doesn't work either: https://codepen.io/zumpel96/pen/empPJzY Wrapping only the "visible" part in a container isn't an option either. In my pen, there's content between the animated scroll containers that's visible for both. This means that content would need to belong to both the first and second scroll containers, which isn't possible. Any advice or suggestions on how to achieve this effect would be greatly appreciated. Thank you so much!
-
Hello gsap-community, I have a problem with GSAP scrolltrigger consecutive flip. I created a loom here https://www.loom.com/share/592a2f60b84547d7912da965fae61440?sid=b8824d73-e552-4770-aec6-56824a5172e4 to explain the issue. And here are the links : - Unique test : https://client-sites-debug.webflow.io/ without additional code - Stagging site : https://hubcycle.webflow.io/expertise with additional scripts I am so lost, I tried many things and combinations but it is so random. I got my inspiration from that https://codepen.io/D_HB/pen/myejmZO a bit of ChatGPT and of me. Thank you for you help in advance!
-
Hi! I’ve built an animation where a headline gets pinned in the center while some images scroll by in the background. Then, a block of text scrolls up, hits the bottom of the headline, and at that point, the pin is removed and everything scrolls normally again. Now I want to take it a step further: After that sequence, I’d like to pin the parent container once it reaches the top, so that the next module can slide over the pinned section. But doing this breaks everything. Here’s a small demo – if you comment out the second part, you’ll see the problem clearly.
-
ScrollTrigger + ScrollSmoother: Grow an element to full-screen and keep it fixed as a background
Ak1tain posted a topic in GSAP
Hello guys I'm kinda new to GSAP. I’m working on the following effect: As the page scrolls, a black square grows until it fills the entire viewport. Once it reaches 100 vw × 100 vh, it should remain fixed, acting as a background while the next elements scroll over it. ( The problem ) I’m using GSAP 3, ScrollTrigger, and ScrollSmoother. Here’s a CodePen that reproduces the problem:- 5 replies
-
- scrolltrigger
- scrollsmoother
-
(and 1 more)
Tagged with:
-
scrolltrigger GSAP ScrollTrigger pin position is jumping on iOS due to its address bar
yeonhwan posted a topic in GSAP
KakaoTalk_Video_2024-04-07-21-26-01.mp4 Hello GSAP community! I am quite new to GSAP and here to get some help! I was using on GSAP for scroll animation on my web page and I met weird behavior with scrollTrigger on iOS environment. I tried to pin an element inside of a scrollable container and it worked smoothly in desktop and Chrome. But when I open the page using Safari on my iOS device, the pin position is broke and jumps around depending on browser's address bar appearance. You can see it's behavior on the video I recorded. Where I found this behavior for the first time is in much different code base, but it is still happening while I tried to reproduce it with minimal code example. Basic scrollTrigger settings with Vite, React, Tailwind Any advice to remove this behavior or any walk arounds for this situation? Thanks! This is the all I wrote for the example page the video shows import "./App.css"; import { useEffect } from "react"; import gsap from "gsap"; import ScrollTrigger from "gsap/ScrollTrigger"; function App() { useEffect(() => { gsap.registerPlugin(ScrollTrigger); const SC = ScrollTrigger.create({ trigger: ".container", start: "top top", end: "bottom top", scrub: true, pin: ".pin", markers: true, }); return () => { SC.kill(); }; }, []); return ( <div> <div className="w-full h-dvh bg-green-500"></div> <div className="container w-full h-[200dvh] bg-yellow-500"> <h1 className="pin text-4xl font-extrabold bg-blue-400">PIN ME</h1> </div> </div> ); } export default App; -
Scrolltrigger issues - Scrolltrigger + pin + snap not working as expected.
Akram Faiz posted a topic in GSAP
I need to create a react component that has scrolltrigger with pin which pins the media that comes behind it. The component will have multiple sections which have h1, p, span and a media background that has height of 150vh which displays different media (image or video) based on the index of array and its media type the component when comes into viewport the media div should have position fixed (using pin). The sections are not snapping properly. I am getting jitterring, bouncing effect and sometimes to for bouncing with a pause. const nextSection = sectionRefs.current[idx + 1]; const prevSection = sectionRefs.current[idx - 1]; if (nextSection) { triggers.push( ScrollTrigger.create({ trigger: section, start: 'bottom bottom-=50px', end: 'bottom top', onEnter: (self) => { if (self.direction === 1 && !isSnapping) { isSnapping = true; gsap.to(window, { scrollTo: { y: nextSection, autoKill: false, }, duration: 0.6, ease: 'power2.inOut', onComplete: () => { isSnapping = false; }, onInterrupt: () => { isSnapping = false; }, }); } }, }) ); } if (prevSection) { triggers.push( ScrollTrigger.create({ trigger: section, start: 'top-=50 top', end: 'top top', // markers: true, onLeaveBack: (self) => { if (self.direction === -1 && !isSnapping) { isSnapping = true; gsap.to(window, { scrollTo: { y: prevSection, autoKill: false, }, duration: 0.6, ease: 'power2.inOut', onComplete: () => { isSnapping = false; }, onInterrupt: () => { isSnapping = false; }, }); } }, }) ); } its not working as expected. I need smooth transition between section with a snap effect. Please guide me.- 4 replies
-
- scrolltrigger
- snap
-
(and 1 more)
Tagged with:
-
ScrollTrigger Animation not animating the entire marker positions
Levin Riegner posted a topic in GSAP
I recently created a CodePen for parallax image sections, but I’ve noticed the animation finishes early. Even though it’s set to run from the top of the element to the bottom, and from the bottom of the screen to the top, it pauses during the last 100px. I can’t figure out why this is happening, and it’s driving me crazy. Am I missing something obvious? I assume it’s probably an easy fix.- 1 reply
-
- scrolltrigger
- urgent
-
(and 3 more)
Tagged with:
-
scrolltrigger ScrollTrigger addEventListener "revert" not in Typescript
_ilizette posted a topic in GSAP
I am working with typescript and I'm using ScrollTrigger revert eventListener, but I get a typescript error : Argument of type '"revert"' is not assignable to parameter of type '"scrollStart" | "scrollEnd" | "refreshInit" | "refresh" | "matchMedia"'. Has revert been removed from the list of event listeners for scroll trigger? -
Hey, this is my first post so I hope I've done the codepen thing right - I have a question about accessing values from inside a map. My goal is that as you scroll past a specific HTML element, which has an animation applied to it, it will also change another HTML element to a specific value held inside an array of objects. The way I have it set up so far is that the HTML elements are all contained within that same object, and I would like the gsap animation to look at the key of the object it's just scrolled past (in this case: 'role') and get the 'skill1' value from that same object to change the width of the skill slider. Hopefully you can see from my codepen that the 'skill2' slider changes to the fixed value of 90% each time it scrolls past a new 'role'. But I would like it to change to the 'skill' value held within the object. My attempt at doing so with the 'skill1' slider has not been successful. Any suggestions? Cheers, Kitt UPDATE! I figured it out! If anyone else would benefit from knowing how I solved this, the updated codepen is below - I took the innerHTML from the DOM element that was created by the array of objects that I was referencing and used it to search that same array for the skill value I needed, which I then assigned to a variable that could be referenced when I needed that value later in the animation. I hope that makes sense. Happy times
- 2 replies
-
- scrolltrigger
- array
-
(and 3 more)
Tagged with:
-
ScrollTrigger refresh on resize slow my whole page to get rerendered and cause major performance lag
marco.giovanniello posted a topic in GSAP
Hi everyone, I'm experiencing a performance issue when resizing the browser window—specifically when the layout shifts from desktop to mobile (a layout-breaking resize). Whenever this happens, all my ScrollTrigger animations need to recalculate their start and end positions. This triggers a significant lag in style recalculation and layout rendering, as seen in the Chrome Performance tab (screenshot attached). However, if I temporarily call ScrollTrigger.disable() into my GSAPInit.tsx, the resize happens instantly and smoothly, without any noticeable frame drops. I’m currently using a GSAP setup where: GSAP is configured and registered globally via a GSAPInit component. ScrollTrigger refresh is throttled via a setTimeout on resize. "use client"; import { useGSAP } from "@gsap/react"; import gsap from "gsap"; import ScrollToPlugin from "gsap/ScrollToPlugin"; import { ScrollTrigger } from "gsap/ScrollTrigger"; import { memo } from "react"; function GSAPInit() { useGSAP(() => { if (typeof window !== "undefined") { gsap.registerPlugin( ScrollTrigger, ScrollToPlugin, useGSAP ); process.env.NODE_ENV === "development" && console.info( "%c%s", "color: green; opacity: 0.5", " GSAP PLUGINS REGISTERED " ); } gsap.config({ nullTargetWarn: process.env.NODE_ENV === "development", force3D: true, }); ScrollTrigger.config({ autoRefreshEvents: "visibilitychange,DOMContentLoaded,load", }); ScrollTrigger.defaults({ //markers: process.env.NODE_ENV === "development", }); let tid: ReturnType<typeof setTimeout> | null = null; const refreshScrollTrigger = () => { clearTimeout(tid!); tid = setTimeout(() => { ScrollTrigger.refresh(); }, 2500); }; window.addEventListener("resize", refreshScrollTrigger); return () => { window.removeEventListener( "resize", refreshScrollTrigger ); }; }, []); return null; } export default memo(GSAPInit); I'm synchronizing GSAP animations with Lenis using a custom Lenis component that hooks into gsap.ticker. "use client"; import { useRouter } from "@/i18n/navigation"; import { usePathSegment } from "@/lib/hooks/usePathSegment"; import gsap from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; import ReactLenis, { LenisRef, useLenis, } from "lenis/react"; /* eslint-disable-next-line */ import { memo, useContext, useEffect, useRef, useState, } from "react"; import { useCurrentPathSegment } from "../contexts/PathSegmentContext"; import { createContext } from "react"; import LenisClass from "lenis"; const GlobalLenisContext = createContext< LenisClass | undefined >(undefined); function Lenis({ children, }: { children: React.ReactNode; }) { const [isSafari, setIsSafari] = useState(false); const lenisRef = useRef<LenisRef>(null!); const pathname = useCurrentPathSegment(); const router = useRouter(); const lenis = useLenis(ScrollTrigger.update); const checkSafari = () => { const userAgent = navigator.userAgent; const isSafariBrowser = /^((?!chrome|android).)*safari/i.test(userAgent); return isSafariBrowser; }; useEffect(() => { setIsSafari(checkSafari()); }, []); useEffect(() => { function update(time: number) { lenisRef.current?.lenis?.raf(time * 1000); } gsap.ticker.add(update); return () => { gsap.ticker.remove(update); }; }, [lenis]); useEffect(() => { lenis?.scrollTo(0, { immediate: true, force: true, }); }, [pathname, lenis]); return ( <GlobalLenisContext.Provider value={lenisRef.current?.lenis} > <ReactLenis root options={{ anchors: true, allowNestedScroll: true, duration: 1.2, easing: (t) => 1 - Math.pow(1 - t, 3), prevent: (node) => node.id === "noLenis" || node.classList.contains("noLenis") || node.classList.contains("modal") || isSafari, autoRaf: false, touchMultiplier: 0, }} ref={lenisRef} > {children} </ReactLenis> </GlobalLenisContext.Provider> ); } export default memo(Lenis); export const useGlobalLenis = () => useContext(GlobalLenisContext); Unfortunately, the project is too large to isolate into a reproducible CodePen or demo. However, each page uses at least one ScrollTrigger with both pin and scrub enabled. Stack: Next.js Lenis (with autoRaf: false, manual raf via GSAP) GSAP (with ScrollTrigger and ScrollToPlugin) OverlayScrollbars What I'm trying to understand is: Why does ScrollTrigger.refresh() during resize cause such a noticeable slowdown in rendering? Is there any optimization pattern for managing many pinned + scrubbed ScrollTriggers during layout changes? Could this be an issue with how Lenis or GSAP handles the resize event in large DOM trees? Any help or insights would be greatly appreciated! Thanks in advance 🙏 -
Hey everyone, confused in one of my client's work. Would be grateful if somebody helps out. I have a two background images, one of 60vh and other of 40vh so that both appear in one page. On scroll I want to change the background x position of first image for example it should go right. Once the top of the viewport touches the top of the image, scroll should be locked and scroll should be continued when the animation completes. If i pin the image, the image is going down with my viewport but I don't want that. The image should be in its initial position, only lock the scroll. Thanks in advance.😁
- 1 reply
-
- scrolltrigger
- pin
-
(and 1 more)
Tagged with:
-
I'm experiencing a jumping/snapping issue at the end of my ScrollTrigger timeline animation. The animation works smoothly throughout the scroll, but when it reaches the end, there's an abrupt jump/snap back. Environment CMS: WordPress with Elementor pro GSAP Version: 3.12.2 (loaded via CDN) Site: https://signatureglobal.com/scrolltigger-animation/ HTML Generated-: <div class="experience"> <div class="experience_inner"><!-- First slide content --> <!-- Images and content --> </div> <div class=" experience_inner experience_inner02 amenity"> <!-- Second slide content --> <!-- Images and content --> </div> <div class="experience_inner experience_inner03 highlight"> <!-- Third slide content --> <!-- Images and content --> </div> </div> CSS: .experience{ height: 100vh; width: 100%; position: relative;overflow:hidden} .experience_inner{ width: 100%; height: 100%; position: absolute; top: 0; left: 0; } .experience_inner1 { position: absolute; top: 0; left: 0; width: 100%; height: 100vh; display: flex; align-items: center; flex-direction: column; justify-content: center; text-align: center; z-index: 9; padding: 0; } .experience_inner02 { z-index: 10; } .experience_inner03 { z-index: 11; } .amenity, .highlight { transform: translateY(100%); will-change: transform; backface-visibility: hidden; } JavaScript (Original): window.addEventListener("load", function () { gsap.registerPlugin(ScrollTrigger); var pinT2 = gsap.timeline({ scrollTrigger: { trigger: ".experience", pin: true, start: 'top top', markers: true, end: "+=" + (window.innerHeight * 3.5), scrub: 2, // ease: "slow", } }) .to(".amenity", { ease: "power1.inOut", y: 0, duration: 2,delay: 1}) .to(".highlight", {ease: "power1.inOut",y:0, duration: 2,delay: 1}); ScrollTrigger.refresh(); }); Animation works smoothly until the very end, where there's a noticeable jump/snap, suggesting the elements are being reset or repositioned unexpectedly. Any insights or alternative approaches would be greatly appreciated! Thanks in advance for your help. I can create a simplified CodePen if needed, though the issue might be specific to the Elementor environment.
- 2 replies
-
- scrolltrigger
- elementor scrolltrigger
- (and 4 more)
-
I am using scrollTrigger with pin option to pin down a section of my web app but, having it in a timeline, I see it creates a second scrollbar. I don't know if it's related but if scrolling really slowly, the animation and the overall page's layout breaks. How can I fix this? Link to demo: https://stackblitz.com/edit/vitejs-vite-qvotiuq8?file=src%2Fcomponents%2FHome.tsx,src%2Fcomponents%2FTech.tsx,src%2FApp.tsx&terminal=dev
- 1 reply
-
- scrolltrigger
- scrolltrigger scrub
-
(and 1 more)
Tagged with:
-
Scroll trigger pinned section with dynamic height based on inner content
Nick Lewis posted a topic in GSAP
Hi Everyone, Long time GSAP dabbler, but first time posting in the forum. Looking for some help and advice. I'm trying to create a scroll triggered and pinned section of a site that scrolls through content within a 'frame' inside the section. This is on desktop only. on mobile the animations are killed. What I'm struggling with managing is the height of the container and how long it is pinned for before the content below naturally appears and the scroll continues as normal. I have created an example of what I'm trying to achieve here. https://codepen.io/nickylew/pen/RNWWMop I feel as thought I may be over complicating things, trying to set the height of the section dynamically based on the number of 'boxes' there are to scroll through. These 'boxes' will be dynamic, pulled from a CMS, so there could be 5 of them, could be 10+. Which is why I was trying to get the height and uses that to dynamically set things up. Any help greatly appreciated! As I'm getting a little spun round in circles. Additionally, improving performance would be amazing to know as well. As this is janky! But, that might be because it is trying to work out heights.- 2 replies
-
- gsap
- scrolltrigger
-
(and 1 more)
Tagged with:
-
I'm quite a beginner in GSAP stuff so I followed a tutorial (I will call it "cards stack" for now) to incorporate it into my working-in-progress. When I ran the cards stack code separately in other files, it worked just fine, but when I incorporated it into my on-going project, it had a weird white space in the middle. I only found out that it related to the pinSpacing when inspecting elements, and have tried various ways to fix it as well as visiting the forum for related issues but still cannot find a solution. Here is the code: https://codesandbox.io/p/sandbox/s4g8sx Thank you so much for your help.
-
ScrollTrigger Video Scrubbing: Chrome Desktop Performance Issues
jovenfabuloso posted a topic in GSAP
Setup: Video hosted on Cloudinary FFmpeg encoded Chrome mobile = smooth, Safari = smooth, Chrome desktop = choppy What I've tried: Lower scrub values (0.05-0.1) preload="metadata" vs preload="auto Question: Is there a recommended approach for smooth video scrubbing in Chrome desktop? Any insights on why Chrome desktop handles this differently than Chrome mobile would be appreciated!- 1 reply
-
- chrome
- scrolltrigger
-
(and 2 more)
Tagged with: