Jump to content
Search Community

Search the Community

Showing results for 'lenis ScrollTrigger' in content posted in GSAP.

  • Search By Tags

    Type tags separated by commas.
  • Search By Author

Content Type


Forums

  • GreenSock Forums
    • GSAP
    • Banner Animation
    • Jobs & Freelance
  • Flash / ActionScript Archive
    • GSAP (Flash)
    • Loading (Flash)
    • TransformManager (Flash)

Product Groups

  • Club GreenSock
  • TransformManager
  • Supercharge

Categories

There are no results to display.


Find results in...

Find results that contain...


Date Created

  • Start

    End


Last Updated

  • Start

    End


Filter by number of...

Found 153 results

  1. Hi, I'm facing an issue while using GSAP ScrollTrigger with the following options: once: true, pin: true, and scrub: true, combined with Lenis for smooth scrolling. Everything works correctly, but after the animation with once: true, a blank space appears — as if some padding is left behind by the pin-spacer that ScrollTrigger uses. It feels like the scroll is still reserving space for the pinned element, even though the animation runs only once. Here's what I'm using: GSAP ScrollTrigger with pin, scrub, and once: true Lenis for smooth scrolling No JavaScript errors in the console Has anyone encountered this issue or found a way to fix it? Thanks in advance! CodePen : https://codepen.io/IdealDev12/pen/PwPYQYJ
  2. @Rodrigo How would your solution need to be amended when not using Lenis? I'm having pretty much exactly the same issue which you can see in this Codepen: https://codepen.io/bluegg/pen/pvbRjOw You can see that the orange block is the scrolltrigger element that gets pinned and the red block inside it animates as you scroll. Once the animation completes and then you scroll back up you have a large white gap above it that wasn't there previously.
  3. 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 🙏
  4. Hi guys, hope you're doing well. Trying to achieve a gsap scrollTriggers animations with Lenis and using scroller proxy to avoid performance issues on the main project. Here my code : <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>HTML + CSS</title> <link rel="stylesheet" href="styles.css" /> </head> <style> html, body { height: 100%; overflow: hidden; font-family: sans-serif; } .scrollWrapper { height: 100vh; overflow: hidden; position: absolute; top: 0; } .scrollContent { height: 100%; overflow-y: scroll; -webkit-overflow-scrolling: touch; } section { width: 100vw; max-width: 100%; height: 100vh; display: flex; } section h2 { align-self: center; } </style> <body> <div class="scrollWrapper"> <div class="scrollContent"> <section><h2>My Title 0</h2></section> <section style="background: red"><h2>My Title 1</h2></section> <section style="background: blue"><h2>My Title 2</h2></section> <section><h2>My Title 3</h2></section> <section><h2>My Title 4</h2></section> <section><h2>My Title 5</h2></section> <section><h2>My Title 6</h2></section> <section><h2>My Title 7</h2></section> <section><h2>My Title 8</h2></section> <section><h2>My Title 9</h2></section> <section><h2>My Title 10</h2></section> </div> </div> </body> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js" integrity="sha512-7eHRwcbYkK4d9g/6tD/mhkf++eoTHwpNM9woBxtPUBWm67zeAfFC+HrdoE2GanKeocly/VxeLvIqwvCdk7qScg==" crossorigin="anonymous" referrerpolicy="no-referrer" ></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js" integrity="sha512-onMTRKJBKz8M1TnqqDuGBlowlH0ohFzMXYRNebz+yOcc5TQr/zAKsthzhuv0hiyUKEiQEQXEynnXCvNTOk50dg==" crossorigin="anonymous" referrerpolicy="no-referrer" ></script> <script src="https://cdn.jsdelivr.net/npm/@studio-freight/[email protected]/dist/lenis.min.js"></script> <script> gsap.registerPlugin(ScrollTrigger); const wrapper = document.querySelector(".scrollWrapper"); const content = document.querySelector(".scrollContent"); const lenis = new Lenis({ wrapper: wrapper, content: content, duration: 2, easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), eventsTarget: document.documentElement, }); function raf(time) { lenis.raf(time); ScrollTrigger.update(); requestAnimationFrame(raf); } requestAnimationFrame(raf); ScrollTrigger.defaults({ scroller: wrapper }); ScrollTrigger.scrollerProxy(wrapper, { scrollTop(value) { return arguments.length ? lenis.scrollTo(value, { duration: 0, immediate: true }) : lenis.scroll; }, getBoundingClientRect() { return { top: 0, left: 0, width: window.innerWidth, height: window.innerHeight, }; }, }); const sections = gsap.utils.toArray("section"); sections.map((section, index) => { let tl = gsap.timeline({ duration: 1, scrollTrigger: { trigger: section, markers: true, scrub: true, start: "top bottom", end: "top top", }, }); tl.from(section, { opacity: 0, duration: 1 }); }); </script> </html> And here the live demo : https://codesandbox.io/p/sandbox/m87qzq?file=%2Findex.html%3A1%2C1-127%2C1, as you can see , when I open the console I see the opacity on sections moving, I also see them ( blue overlay) moving, but they dont appear on the screen. And on mobile the markers dont move, I guess I did something wrong in the setup and gsap dont refresh well, or dont detect the scroll on the right element. My goal is to use this idea to avoid using gsap.normalizeScroll and Lenis.syncTouch, these two options dont fit well with webgl background, create some delay and jitterings. So my idea was to use a scroller proxy as you see in my code, and update my webgl at the same place where I update the ScrollTrigger.update(), and lenis.raf(), I guess it's a potential good way to achieve fine and smooth animations and preserving good fps. Thanks you guys!
  5. hey, i want to create a stacking effect where page 5 will come on top of page 4 after the page 4 text animation will end but a, stuck for 2 days if someone would help me it willl be really helpful import './App.css' import gsap from 'gsap' import { useGSAP } from '@gsap/react' import { useRef, useEffect } from 'react' import { ScrollTrigger } from 'gsap/ScrollTrigger' import { SplitText } from 'gsap/SplitText' import ScrollSmoother from 'gsap/ScrollSmoother' import Lenis from 'lenis' gsap.registerPlugin(useGSAP, ScrollTrigger, SplitText, ScrollSmoother) const App = () => { const h1Ref = useRef(null) const h1Ref2 = useRef(null) //lenis setup useEffect(() => { const lenis = new Lenis({ duration: 1.2, easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), smooth: true, }) const raf = (time) => { lenis.raf(time) requestAnimationFrame(raf) } requestAnimationFrame(raf) return () => lenis.destroy() }, []) //page 1 h1 effect // Page 1 pin effect ->stacking effect need to use zindex useGSAP(() => { ScrollTrigger.create({ trigger: '#page1', start: 'top top', end: 'bottom 20%', // Adjust for how long you want it pinned pin: true, pinSpacing: false, }) }, []) //page 2 effect useGSAP(() => { const split = new SplitText(h1Ref.current, { type: 'chars', }) gsap.from(split.chars, { scrollTrigger: { trigger: h1Ref.current, scroller: 'body', start: 'top 80%', end: 'bottom 30%', scrub: 1, }, x: 100, opacity: 0, stagger: 0.05, ease: 'power3.out', }) }, []) useGSAP(() => { const split = new SplitText(h1Ref2.current, { type: 'lines', linesClass: 'animated', // ✅ this adds the CSS class to each line }) split.lines.forEach((line) => { gsap.to(line, { backgroundPositionX: 0, ease: 'none', scrollTrigger: { trigger: line, scroller: 'body', start: 'top 70%', //keep both value at same end: 'bottom 72%', scrub: 0.5, }, }) }) }, []) //parallax effect for hello text useGSAP(() => { gsap.to('.hello', { scrollTrigger: { trigger: '.parallax', scroller: 'body', start: 'top 30%', end: 'bottom -65%', scrub: 1, pin: true, }, }) }) //animating page 4 useGSAP(() => { // Pin the wrapper ScrollTrigger.create({ trigger: '#page4', scroller: 'body', start: 'top 0%', end: '+=1000', // longer scroll = slower animation pin: true, }) // Animate .explore gsap.fromTo( '.explore', { x: '70%' }, { x: '-100%', scrollTrigger: { trigger: '#page4', scroller: 'body', start: 'top 0%', end: '+=2000', scrub: 2, // smooth, slower motion }, } ) // Animate .endless gsap.fromTo( '.endless', { x: '-70%' }, { x: '30%', scrollTrigger: { trigger: '#page4', scroller: 'body', start: 'top 10%', end: '+=1000', scrub: 2, }, } ) }, []) return ( <div id="container"> <div id="page1"> <h1>Welcome</h1> </div> <div id="page2"> <div className="hello"> <h2 className="parallax">Hello</h2> </div> <div className="lorem"> <h1 ref={h1Ref}> Lorem ipsum dolor sit amet consectetur adipisicing elit. Molestiae eos iusto repellat esse aspernatur atque fugiat magnam obcaecati ea accusamus! Deleniti veritatis quidem iure sit alias, earum non ab quo. Lorem, ipsum dolor sit amet consectetur adipisicing elit. Recusandae aliquam ad culpa eum atque voluptatem quod repellat eius corrupti laudantium natus ipsa, iste exercitationem commodi porro aut facere amet deleniti. </h1> </div> </div> <div id="page3"> <div className="hello"></div> <div className="lorem"> <h1 ref={h1Ref2} className="animated"> Lorem ipsum dolor sit amet consectetur adipisicing elit. Molestiae eos iusto repellat esse aspernatur atque fugiat magnam obcaecati ea accusamus! Deleniti veritatis quidem iure sit alias, earum non ab quo. </h1> </div> </div> <div id="page4"> <h1 className="explore">We set the bar high</h1> <h1 className="endless">For our Projects</h1> </div> <div id="page5"></div> </div> ) } export default App
  6. mvaneijgen

    Help! Can't get GSAP code to work.

    Hi @artyengy123 welcome to the forum! The best thing to do when working with ScrollTrigger is to remove it! This seems counter intuitive, but ScrollTrigger is just animating something on scroll, so just focus on the animation at first and only when you're happy with the animation add ScrollTrigger back in. This way you can focus on one part at a time and it will save a lot of headache when debugging. Most of the ScrollTrigger effects you see are just moving things on the y-axis instead of scrolling them, then hooking that animation to ScrollTrigger will give the illusion of scrolling, but they are really just animating! Getting to grips with this knowledge will make it so much easier to create all kinds of effects, I've written a whole post about it, check it out: I find the way this tutorial is setup a bit complicated and also note that GSAP has its own smooth scroll library called ScrollSmoother https://gsap.com/docs/v3/Plugins/ScrollSmoother/ which works out of the box with all the GSAP tools, so I don't see why you would use lenis (but to each their own) How I would set it up is just build the animation you want (in a timeline) and then hook that to ScrollTrigger when you think it is done to see how that works on scroll. Below your pen with ScrollTrigger removed as mentioned above and GSDevTools installed so that you can easily debug your animation. https://codepen.io/mvaneijgen/pen/QwNaRdr?editors=1010 If you're new to GSAP check out this awesome getting started guide https://gsap.com/resources/get-started/ Again how the tutorials is made is not how I personally would set it up, not that it is bad, but different preferences. If it must work like the tutorials I would ask the original creator, seems like they are also selling the source code, so that is the route I would go down. Hope it helps and happy tweening!
  7. Hello! I started working on a project in which I'm using both Lenis (ReactLenis) and Scrolltrigger on a different, slower machine and am noticing very poor framerates on scroll (whereas on the beefier machine, the scroll animations were fluid, fast). Commenting out ReactLenis restores performance. My question is: how to make scrolltrigger and lenis play nice in React/Next. This is not really a "help me achieve outcome X" in this sandbox/codepen question, moreso a higher-level, best practices "what is the right/recommended way to do Y" question For example, in the React Lenis docs, they suggest synchronizing Lenis and GSAP like this: function Component() { const lenisRef = useRef() useEffect(() => { function update(time) { lenisRef.current?.lenis?.raf(time * 1000) } gsap.ticker.add(update) return () => { gsap.ticker.remove(update) } }) return ( <ReactLenis ref={lenisRef} autoRaf={false}> { /* content */ } </ReactLenis> ) } While in their Next.js starter, synchronization appears to be spread across two files: this more scrollTrigger specific one; and this more generic GSAP one (in the combined setup featured these two files, there is no wrapping ReactLenis element, also). So, again. Just wondering if any GSAP people have a recommended/best practice approach for doing this. As I keep seeing different patterns. Totally fine if this question is ineligible because this isn't a Lenis Q&A board, but I feel like these two tools are used together enough that someone on here might have some clarity. Thanks!
  8. Hi there, I'm trying to use Scrolltrigger with Lenis smooth scroll inside a Next js project. My problem is when I resize the window when it has been scrolled, the scrolltriggered animations are shifted. I've made a codesandbox reproducing my issue : here. I have setup a simple animation, triggered by the first .section, it's just moving the blue .box by 100vh. If you scroll a little bit and resize the window, the blue .box will shift vertically. It look like it's shifting proportionally to the distance scrolled. If you scroll back to the top and resize the window, the animation will get back to it's normal position. I've already setup scrollerProxy on others custom smooth scrolls, and never had this issue. The problem is hapenning on all animations in my project, on pinned scrolltriger stuff to, (the pin position is shifting by something that looks like the scrolled distance). I didn't find any ressources with a Lenis smooth scroll setup with scrollerProxy (wich is mandatory to keep perfect animations sync). Can someone see what i'm missing or doing wrong ? thanks.
  9. Hey, I have a problem with split text on mobile. If I resize my desktop screen to mobile size the problem does not apply, possibly because the height of the website does not change and on my mobile browser it does. Attached a video for demonstration, the live link and also the separate js / css. Has anyone experienced this before? Link: https://olafs-dapper-site-9ca308.webflow.io/ [data-split] > span { display: inline-block; white-space: normal; vertical-align: baseline; } .u-text-style-h1 > span { transition: all 0.6s cubic-bezier(0.25, 0.1, 0.25, 1); } .u-text-style-h1.loaded-class > span { transform: translateY(100%); opacity: 0; } let lenis; function debounce(func, delay) { let timeout; return function () { clearTimeout(timeout); timeout = setTimeout(func, delay); }; } function initLenis() { lenis = new Lenis(); // Synchronize Lenis scrolling with GSAP's ScrollTrigger plugin lenis.on('scroll', ScrollTrigger.update); // Add Lenis's requestAnimationFrame (raf) method to GSAP's ticker // This ensures Lenis's smooth scroll animation updates on each GSAP tick gsap.ticker.add((time) => { lenis.raf(time * 1000); // Convert time from seconds to milliseconds }); // Disable lag smoothing in GSAP to prevent any delay in scroll animations gsap.ticker.lagSmoothing(0); } function initSplit() { return new Promise(resolve => { const wrapWithMask = item => { const mask = document.createElement('span'); mask.setAttribute('data-split-mask-el', ''); item.parentNode.insertBefore(mask, item); mask.appendChild(item); }; const split = () => { document.querySelectorAll('[data-split]').forEach(el => { if (el._splitTextInstance) { el._splitTextInstance.revert(); delete el._splitTextInstance; } el.querySelectorAll('[data-split-mask-el]').forEach(mask => mask.remove()); const type = el.getAttribute('data-split'); const splitType = type === 'chars' ? 'words,chars' : type; const splitInstance = new SplitText(el, { type: splitType, tag: 'span', wordDelimiter: ' ' }); el._splitTextInstance = splitInstance; if (el.hasAttribute('data-split-mask')) { const key = type === 'chars' ? 'chars' : type; (splitInstance[key] || []).forEach(wrapWithMask); } }); resolve(); }; const debouncedSplit = debounce(split, 200); window.addEventListener('resize', debouncedSplit); if (document.fonts && document.fonts.ready) { document.fonts.ready.then(split); } else { window.addEventListener('load', split); } }); } function initTransitionDelay() { function transitionDelay() { document.querySelectorAll('[data-td]').forEach(element => { const delay = (parseFloat(element.getAttribute('data-td')) || 100) / 1000; const startDelay = (parseFloat(element.getAttribute('data-td-start')) || 0) / 1000; const splitType = element.getAttribute('data-split'); let spans = []; if (element.querySelector('[data-split-mask-el]')) { spans = element.querySelectorAll('[data-split-mask-el] > span'); } else { spans = element.querySelectorAll('span'); } spans.forEach((span, index) => { const currentDelay = index * delay + startDelay; span.style.transitionDelay = `${currentDelay}s`; }); }); } const debouncedTransitionDelay = debounce(transitionDelay, 200); window.addEventListener('resize', debouncedTransitionDelay); transitionDelay(); } function initFading() { const elements = [ { selector: "[data-fade-in]", init: el => gsap.set(el, { autoAlpha: 0, y: 60 }), action: el => { gsap.to(el, { autoAlpha: 1, y: 0, duration: 0.5, ease: "cubic-bezier(0.5, 0, 0, 1)" }); } }, { selector: "[data-loaded-class]", init: null, action: el => el.classList.remove( "loaded-class") }]; elements.forEach(({ selector, init, action }) => { document.querySelectorAll(selector).forEach(el => { if (init) init(el); ScrollTrigger.create({ trigger: el, start: "top 80%", onEnter: () => action(el), onEnterBack: () => action(el) }); }); }); ScrollTrigger.refresh(); } initLenis(); initSplit().then(() => { initTransitionDelay(); initFading(); }); Screen Recording Jun 3 2025.mov
  10. Hi guys, I am totally newbie here, : i am building my website with several javascript and GSAp animations, they are all working fine, but I want to stop the site from scrolling until the preloader animation (or should I call it function)? finishes. so when the preloader finishes you see the hero, I even would like to prevent scrolling for a bit( a delay of 1 second or such) so user can't scroll until white bars fully go out of sight. this is the piece of code in charge of the animation. the structure of the preloader is quite simple: a parent (.preloader): width: 100vw, height: 100vh. inside 10 divs each (.bar) width: 10vw. height:100vh. And an h1 div with the class counter containing a text element, this is the number that goes up to 100. the 10 divs go up at the end of the counter animation showing the rest of the site. here is a link to the live site: https://www.notbranding.com/ I have tried adding the body overflow hidden solution but it is not working. The site is built using webflow if it is relevant. Any help is greatly appreciated, Thanks! //this is for the preloader function startLoader() { let counterElement = document.querySelector(".counter"); let currentValue = 0; function updateCounter() { if (currentValue === 100) { } currentValue += Math.floor(Math.random() * 10) + 1; if (currentValue > 100) { currentValue = 100; } counterElement.textContent = currentValue; let delay = Math.floor(Math.random() * 100) + 50; setTimeout(updateCounter, delay); } updateCounter(); } startLoader(); gsap.to(".counter", 0.25, { delay: 2.5, opacity: 0, }); gsap.to(".bar", 1.5, { //how long the bar takes to move up delay: 2.5, height: 0, stagger: { amount: 0.5, }, ease: "power4.inout", }); and here is the full code I currently have (in case something might be generating a conflict, or if you see something that could be done better). i am adding the code to weblfow using slater. // Load SplitType script if i find issues it might be the async line.not sure if this improves performance or not really. let splitTypeScript = document.createElement("script"); splitTypeScript.src = "https://unpkg.com/split-type"; splitTypeScript.async = true; splitTypeScript.onload = checkScriptsLoaded; document.body.appendChild(splitTypeScript); // Load GSAP script let gsapScript = document.createElement("script"); gsapScript.src = "https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.3/gsap.min.js"; gsapScript.async = true; gsapScript.onload = checkScriptsLoaded; document.body.appendChild(gsapScript); // Load ScrollTrigger script let scrollTriggerScript = document.createElement("script"); scrollTriggerScript.src = "https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.3/ScrollTrigger.min.js"; scrollTriggerScript.async = true; scrollTriggerScript.onload = checkScriptsLoaded; document.body.appendChild(scrollTriggerScript); // Load Lenis script let lenisScript = document.createElement("script"); lenisScript.src = "https://cdn.jsdelivr.net/gh/studio-freight/[email protected]/bundled/lenis.min.js"; lenisScript.async = true; lenisScript.onload = checkScriptsLoaded; document.body.appendChild(lenisScript); let scriptsLoaded = 0; function checkScriptsLoaded() { scriptsLoaded++; if (scriptsLoaded === 4) { // All scripts have loaded, execute the main code mainCode(); } } // Main code that depends on SplitType, GSAP, and ScrollTrigger function mainCode() { // //smooth scrolller code // let lenis = new Lenis({ lerp: 0.1, //initial value is 0.1 wheelMultiplier: 0.8, //this slows or makes the interaction faster gestureOrientation: "vertical", normalizeWheel: false, smoothTouch: false, }); function raf(time) { lenis.raf(time); requestAnimationFrame(raf); } requestAnimationFrame(raf); $("[data-lenis-start]").on("click", function () { lenis.start(); }); $("[data-lenis-stop]").on("click", function () { lenis.stop(); }); $("[data-lenis-toggle]").on("click", function () { $(this).toggleClass("stop-scroll"); if ($(this).hasClass("stop-scroll")) { lenis.stop(); } else { lenis.start(); } }); // //thisis for the preloader // function startLoader() { let counterElement = document.querySelector(".counter"); let currentValue = 0; function updateCounter() { if (currentValue === 100) { } currentValue += Math.floor(Math.random() * 10) + 1; if (currentValue > 100) { currentValue = 100; } counterElement.textContent = currentValue; let delay = Math.floor(Math.random() * 100) + 50; setTimeout(updateCounter, delay); } updateCounter(); } startLoader(); gsap.to(".counter", 0.25, { delay: 2.5, opacity: 0, }); gsap.to(".bar", 1.5, { //how long the bar takes to move up delay: 2.5, height: 0, stagger: { amount: 0.5, }, ease: "power4.inout", }); // //This is the text animations, IF IT DOESNT WORKS it is because you are applying the custom attribute: text- split and then the type of animation For example not-who-slide-up as the value and not as a second attribute, i lost 3 HS with this each time. // // split text into spans let typeSplit = new SplitType("[text-split]", { types: "lines, words, chars", tagName: "span", }); // Apply animations to each element individually document.querySelectorAll("[not-who-slide-up]").forEach(function (element) { let tl = gsap.timeline({ paused: true }); tl.from(element.querySelectorAll(".word"), { opacity: 0.0001, yPercent: 80, duration: 2, ease: "back.out(2)", stagger: { each: 0.035 } // stagger: { amount: 0.6 } //this is a different stagger option }); ScrollTrigger.create({ trigger: element, start: "top bottom", onLeaveBack: () => { tl.progress(0); tl.pause(); } }); ScrollTrigger.create({ trigger: element, start: "top 50%", markers: false, scrub: 1, onEnter: () => tl.play(), }); }); document.querySelectorAll("[not-text-slide-left]").forEach(function (element) { let tl = gsap.timeline({ paused: true }); tl.from(element.querySelectorAll(".word"), { opacity: 0.0001, xPercent: -6, duration: 2, delay: 0.5, ease: "back.out(2)", //stagger: { each: 0.035 } stagger: { amount: 0.2 } //this is a different stagger option }); ScrollTrigger.create({ trigger: element, start: "top bottom", onLeaveBack: () => { tl.progress(0); tl.pause(); } }); ScrollTrigger.create({ trigger: element, start: "top 50%", markers: false, scrub: 1, onEnter: () => tl.play(), }); }); document.querySelectorAll("[not-hero-slide-up]").forEach(function (element) { let tl = gsap.timeline({ paused: true }); tl.from(element.querySelectorAll(".char"), { opacity: 0.0001, yPercent: 100, filter: "blur(12px)", duration: 0.9, ease: "power2.inOut", stagger: { amount: 0.5, from: "start" } }); ScrollTrigger.create({ trigger: element, start: "top top", end: "bottom top", markers: false, onToggle: (self) => { if (self.isActive) { tl.play(); } else { tl.progress(0).pause(); } }, toggleActions: "play pause reset none" }); }); document.querySelectorAll("[not-sectiontitle-slide-up]").forEach(function (element) { let tl = gsap.timeline({ paused: true }); tl.from(element.querySelectorAll(".char"), { opacity: 0.0001, yPercent: 100, filter: "blur(12px)", duration: 0.9, ease: "power2.inOut", stagger: { amount: 0.5, from: "start" } }); ScrollTrigger.create({ trigger: element, start: "top bottom", onLeaveBack: () => { tl.progress(0); tl.pause(); } }); ScrollTrigger.create({ trigger: element, start: "top bottom", markers: true, scrub: 1, onEnter: () => tl.play(), }); }); // Avoid flash of unstyled content document.querySelectorAll("[text-split]").forEach(function (element) { gsap.set(element, { opacity: 1 }); }); // //THEME BUTTON OVERLAY // // Set initial position of the button outside the viewport gsap.set("#hiddenButton", { visibility: "hidden", opacity: 0, yPercent: 500, }); // Define initial animation const initialAnimation = gsap.to("#hiddenButton", { visibility: "visible", opacity: 1, filter: "blur(0px)", duration: 0.9, yPercent: 0, ease: "power2.inOut", paused: true, // Pause the animation initially }); ScrollTrigger.create({ trigger: "#triggerSection", // Corrected trigger definition start: "bottom bottom", end: "bottom", onLeave: () => { // Play initial animation when leaving the section initialAnimation.play(); // Animate the button to slide up smoothly gsap.to("#hiddenButton", { duration: 0.5, ease: "power2.inOut", }); }, onEnterBack: () => { // Reverse the initial animation when re-entering the section initialAnimation.reverse(); // Animate the button to slide down smoothly gsap.to("#hiddenButton", { duration: 0.5, ease: "power2.inOut", }); } }); //.....this is for the stacked services cards......0 // Check if the viewport width is within the specified breakpoints if (window.matchMedia("(min-width: 0px)").matches && window.matchMedia("(max-width: 479px)") .matches) { // Initialize the stacking cards animation for small breakpoints stackcards(1); } else if (window.matchMedia("(min-width: 1024px)").matches) { // Initialize the stacking cards animation for large breakpoints stackcards(1.3); } function stackcards(scaleValue) { let tl = gsap.timeline({ scrollTrigger: { trigger: '.services_main_wrap', end: 'bottom bottom', scrub: 0.3, toggleActions: 'restart none reverse', }, }); tl.from('.card_wrapper', { opacity: 100, yPercent: 350, // Adjusted for a more pronounced vertical movement scale: scaleValue, // Start from scale 0 to scale 1 for a scaling effect duration: 1.2, stagger: { each: 0.5, from: 'end' }, // Adjusted stagger for a quicker succession }); } }
  11. I can't speak on how to use lenis, seen that GSAP has its own smooth scroll library, aptly named ScrollSmoother (https://gsap.com/docs/v3/Plugins/ScrollSmoother/), seems to me like an easier tool to use if you want it to play nice with all the GSAP tools. Next to that the best thing to do when working with ScrollTrigger is to remove it! This seems counter intuitive, but ScrollTrigger is just animating something on scroll, so just focus on the animation at first and only when you're happy with the animation add ScrollTrigger back in. This way you can focus on one part at a time and it will save a lot of headache when debugging. Most of the ScrollTrigger effects you see are just moving things on the y-axis instead of scrolling them, then hooking that animation to ScrollTrigger will give the illusion of scrolling, but they are really just animating! Getting to grips with this knowledge will make it so much easier to create all kinds of effects, I've written a whole post about it, check it out: Hope it helps and happy tweening!
  12. 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
  13. Hi, i have working in cargo collective website then i've added smooth scroll lenis.js and my requirement also one section use animation like card scrolling pin effect horizontally then i have implement gsap scrollTrigger but i have scrolling then smooth scroll lenis.js working perfectly but when enter the section they are hold card's not see only white background showing so please check it and help me.
  14. Hi there, i'm sharing you a part of my animation i want to achieve. It's working fine if i'm scrolling gradually. I need to fire some timelines depending on scrollTrigger progress one by one. For that i add onUpdate callback on scrollTrigger and depending on the ranges i have defined i want to fire the concerned timelines. Is there a solution to write my callbacks more correctly to be sure that i stop lenis scrolling as a timeline is fire and restart lenis scroll when the timeline is complete or reverseComplete to let lenis scroll to next or previous progressRange ? I try a lots of different writing but i never achieve to make the animation perfect user friendly. If you want to get what i mean about my script issue, you can test scrolling hard up and down until you see a timeline fire even if previous timeLine is not complete and you will se some text on top of the other. Not sure if i get clear on my explanations. Thank you in advance for your help or your opinion on the subject.
  15. mixpofo

    ScrollTrigger and Lenis

    Hello, I am encountering an issue with ScrollTrigger and Lenis on the mobile version of my project. The scrolling experience is laggy and not smooth, whereas everything operates seamlessly on the desktop version. This is my code : useEffect(() => { const lenis = new Lenis({ duration: 2, easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), infinite: false, }); lenis.on("scroll", ScrollTrigger.update); gsap.ticker.add((time) => { lenis.raf(time * 1000); }); gsap.ticker.lagSmoothing(0); ScrollTrigger.normalizeScroll(true); // Nettoyage lors du démontage return () => { lenis.off("scroll", ScrollTrigger.update); }; }, []); Thanks !!
  16. Hey everyone, I'm new to GSAP and just started using it in a project for the first time. I'm trying to create an animated navbar that's supposed to stick to the top of the window once the user starts scrolling. After fiddling around a bit, I got the effect I was going for (using ScrollTrigger), but for some reason, it seems to be jittering throughout the animation (especially when scrolling up). At first, I was using Lenis, but since that library has given me a lot of trouble in the past, I tried removing it. That seemed to make the animation less jittery (though it didn't go away completely). Then, I decided to switch to GSAP's ScrollSmoother, but after setting it up, the jittering became noticeable again, which is really confusing me... I've created a minimal demo here: https://codepen.io/ChengCheng_0v0/pen/jEqEVBm It kind of reproduces the issue, but it's not as bad as in my actual project. I've tested this in both Firefox and Chrome, and the issue is present in both. It seems to be a bit worse in Chrome. Thanks in advance for any help! I've been stuck on this for two days now, and it's really giving me a headache 😵‍💫
  17. devshinthant

    Pattern(s) for synchronizing ScrollTrigger and Lenis in React/Next

    I just use like this, I don't wrap my content in ReactLenis Wrapper, cause it makes too laggy on mobile,especially on ios devices import { useLayoutEffect } from 'react' import { useLenisStore } from '@/store/lenis-store' import { useGSAP } from '@gsap/react' import gsap from 'gsap' import Flip from 'gsap/dist/Flip' import ScrollTrigger from 'gsap/dist/ScrollTrigger' import TextPlugin from 'gsap/dist/TextPlugin' import Lenis from 'lenis' /* Plugins */ gsap.registerPlugin(ScrollTrigger) gsap.registerPlugin(useGSAP) gsap.registerPlugin(Flip) gsap.registerPlugin(TextPlugin) ScrollTrigger.normalizeScroll(true) gsap.config({ force3D: true }) ScrollTrigger.config({ ignoreMobileResize: true }) export default function mountLenis() { // eslint-disable-next-line react-hooks/rules-of-hooks const lenisStore = useLenisStore((state) => state) // eslint-disable-next-line react-hooks/rules-of-hooks useLayoutEffect(() => { const lenis = new Lenis({ duration: 1.2, syncTouch: true, smoothWheel: false, }) lenisStore.setLenis(lenis) lenis.on('scroll', ScrollTrigger.update) gsap.registerPlugin(ScrollTrigger) gsap.ticker.add((time) => { lenis.raf(time * 600) }) gsap.ticker.lagSmoothing(0) return () => { ScrollTrigger.getAll().forEach((trigger) => trigger.kill()) lenis.destroy() } // eslint-disable-next-line react-hooks/exhaustive-deps }, []) return null } import { Outlet } from 'react-router-dom' import mountLenis from '@/hooks/mountLenis' import Footer from './footer' export default function Layout() { mountLenis() return ( <div className='relative overflow-x-hidden bg-[#0F0F0F]'> <Outlet /> <Footer /> </div> ) }
  18. Context / What I’m building I have a section .factory with a fixed/pinned background container .factory__bg. Inside it, .factory__bg-inner is taller and scrolls upward while the background stays pinned until the inner content is fully revealed. At the same time, a .factory__main panel is pinned with a slight blur change. Near the end, a .testimony block transitions. Issues When the .factory__bg starts pinning, it “snaps”/pins very abruptly. I’d like a smoother/less perceptible pin-in. When the pin ends, there’s extra whitespace below .factory__bg. Instead, I need the following section to overlap (no gap), i.e., a seamless handoff where the next section sits right under/over the pinned area. Minimal reproducible markup (simplified) – https://codepen.io/vzmsk1/pen/EaPqbYy https://codepen.io/vzmsk1/full/EaPqbYy What I’ve tried Tuning anticipatePin (helps a bit but still feels snappy). Slightly offsetting the start (start: "top top+=1"). pinReparent: true to reduce layout shift. Checked for transforms on ancestors (to avoid transform-based pinning surprises). Questions What’s the recommended way to make the pin-in feel smoother/less jumpy for this layout? How should I remove the extra space at the bottom so the next section overlaps cleanly? Environment GSAP + ScrollTrigger 3.12.x Lenis ^1.3.13 Reproducible in latest Chrome/Firefox
  19. Arshadali

    Cargo Collective Lenis & Scrolltrigger White background Bug

    <style> [id="X4136400666"] bodycopy { } [id="X4136400666"] bodycopy{ } .page-content{ padding: 0; } .full-page { width: 100%; height: 100vh; display: flex; align-items: center; justify-content: center; font-size: 2rem; } /* Custom CSS */ section.section-wrapper { height: 100%; overflow: hidden; width: 300vw; background-color: #242424; } .container { height: 100vh; display: flex; /* justify-content: space-between; */ align-items: center; gap: 50px; margin: 50px 0; transform: translateX(500px); } .card { width: 500px; height: 400px; border: 1px solid #fff; color: #fff; display: flex; justify-content: center; align-items: center; } </style> <div class="page-wrapper"><section class="full-page" style="background-color:#ffefba"><h1>Section 1</h1></section><section class="full-page" style="background-color:#aedff7"><h1>Section 2</h1></section><section class="section-wrapper"><div class="container"><div class="card one"><h3>Module One</h3></div><div class="card two"><h3>Module 2</h3></div><div class="card three"><h3>Module 3</h3></div><div class="card three"><h3>Module 3</h3></div><div class="card three"><h3>Module 3</h3></div><div class="card three"><h3>Module 3</h3></div></div></section><section class="full-page" style="background-color:#d3c4e3"><h1>Section 4</h1></section></div> <!-- gsap js --> <script src="https://freight.cargo.site/m/U2055329218988265169812608288936/gsap.min.js"></script> <!-- scrollTrigger --> <script src="https://freight.cargo.site/m/O2064440389866436514677162081968/ScrollTrigger.min.js" defer></script> <script src="https://unpkg.com/[email protected]/dist/lenis.min.js"></script> <script> // Initialize Lenis Smooth Scroll const lenis = new Lenis({ duration: 3, smooth: true, }); function raf(time) { lenis.raf(time); requestAnimationFrame(raf); } requestAnimationFrame(raf); // Register GSAP's ScrollTrigger plugin gsap.registerPlugin(ScrollTrigger); // Horizontal Scroll Animation const horizontalContainer = document.querySelector('.container'); const totalScrollWidth = horizontalContainer.scrollWidth - window.innerWidth; // Pin and Horizontal Animation gsap.to(horizontalContainer, { x: -totalScrollWidth, // Move horizontally ease: 'none', scrollTrigger: { trigger: '.section-wrapper', start: 'top top', // Pin starts at the top end: `+=${totalScrollWidth}`, // The horizontal scroll distance scrub: 1, // Smooth animation during scroll pin: true, // Pin the section //markers: true }, }); // Sync Lenis with GSAP's ScrollTrigger lenis.on('scroll', ScrollTrigger.update); </script>
  20. vamsi12

    Heading is Jumping on intial sroll

    <html> <head> <link rel="stylesheet" href="./index.css"/> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap" rel="stylesheet"> <script src="https://unpkg.com/@dotlottie/player-component@latest/dist/dotlottie-player.mjs" type="module"></script> <style> *{ scroll-behavior:auto !important; } #text-container { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; color: #000000; font-size: 60px; display: flex; flex-direction: column; justify-content: center; align-items: center; } .container11 { text-align: center; position: absolute; top: 50%; left: 50%; transform: translate(-50%, 0%); text-align: center; color: #000000; font-size: 60px; display: flex; flex-direction: column; justify-content: center; align-items: center; } canvas { image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; -ms-interpolation-mode: nearest-neighbor; } .dunbox{ width: 84px !important; } .text-data{ font-family: "Plus Jakarta Sans", sans-serif; } .badge11 { display: inline-flex; align-items: center; background-color: #F1EDFF; border-radius: 8px; padding: 5px 15px; font-size: 14px; color: #431DC2; font-weight: bold; } #scroll-text{ font-size: 64px; font-family: "Plus Jakarta Sans", sans-serif; font-weight: 600; margin-top: 20px; transform: translate(0%, -180%); } #scroll-text11{ transform: translate(0%, -180%); } .heading-unboxed{ font-size: 40px; font-weight: bold; color: #000; margin-top: 20px; margin-bottom: 20px; font-family: "Plus Jakarta Sans", sans-serif; } #page2{ display: flex; flex-direction: column; justify-content: center; align-items: center; margin-top: 90px; } .badge11 .icon { margin-right: 5px; font-size: 16px; color: #431DC2; } .data-heading{ color: #805AFF; } .cta-button { background-color: #000; color: #fff; padding: 3px; border-radius: 58px; width: 197px; height: 48px; font-size: 16px; display: flex; justify-content: space-between; align-items: center; text-decoration: none; font-weight: bold; transition: background-color 0.3s ease; } #scroll-text11{ margin-bottom: 30px; outline: none; border-width: 0; } .arrow11{ background-color: #F8F75E; width: 44px; height: 44px; display: flex; justify-content: center; align-items: center; border-radius: 46px; } .text-11{ text-decoration: none; color: #FFFFFF; margin-left: 20px; font-size: 13px; display: flex; justify-content: center; font-family: "Plus Jakarta Sans", sans-serif; } @media (min-width:320px) and (max-width:480px){ #image-1{ aspect-ratio: 1000/1000 !important; } } </style> </head> <body> <div id="main"> <div id="main2"> <div id="page2"> <canvas id="image-1" ></canvas> <div id="text-container"> <video width="64" height="64" autoplay muted loop id="scroll-text11" style="opacity: 1;"> <source src="https://dunboxed.com/wp-content/uploads/2024/10/Comp-2.mp4" type="video/mp4"> </video> <p id="scroll-text" style="opacity: 1;">Data Is Powerful</p> </div> <div class="container11" id="container11" style="opacity: 0;"> <div class="badge11"> <span class="icon"><img src="https://dunboxed.com/wp-content/uploads/2024/10/dunboxed-logo.svg" class="img11"/></span> <span class="text-data">Data Is Powerful</span> </div> <h1 class="heading-unboxed">Unbox Your <span class="data-heading">Data Potential</span><br/> with Dunboxed</h1> <div class="cta-button"> <a href="https://dunboxed.com/contact-us/" class="text-11">Get Started Today</a> <div class="arrow11"> <img src="https://dunboxed.com/wp-content/uploads/2024/10/arrow-dunboxed.svg"/> </div> </div> </div> </div> </div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/gsap.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/Timeline.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/ScrollTrigger.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@studio-freight/lenis@latest/bundled/lenis.min.js"></script> <script> const lenis = new Lenis({ duration: 0.8, // Adjust the smoothness of the scroll easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), // Custom easing function smooth: true, }); function raf(time) { lenis.raf(time); requestAnimationFrame(raf); } requestAnimationFrame(raf); ScrollTrigger.scrollerProxy(document.body, { scrollTop(value) { return arguments.length ? lenis.scrollTo(value) : lenis.animatedScroll; }, getBoundingClientRect() { return { top: 0, left: 0, width: window.innerWidth, height: window.innerHeight }; }, pinType: document.body.style.transform ? "transform" : "fixed", }); // Update on animation frame lenis.on("scroll", ScrollTrigger.update); const preloadLottie = () => { const lottiePlayer = document.querySelector("dotlottie-player"); return new Promise((resolve, reject) => { lottiePlayer.addEventListener('load', resolve, { once: true }); lottiePlayer.load(); }); }; gsap.registerPlugin(ScrollTrigger); ScrollTrigger.defaults({ }); const canvas = document.querySelector("#page2>canvas"); const context = canvas.getContext("2d"); const canvasWidth = window.innerWidth ; const canvasHeight = window.innerHeight * 0.9; canvas.width = canvasWidth; canvas.height = canvasHeight; window.addEventListener("resize", function () { const canvasWidth = window.innerWidth; const canvasHeight = window.innerHeight * 0.9; canvas.width = canvasWidth; canvas.height = canvasHeight; render(); }); const frameCount = 155; const baseUrl = "https://dunboxed.com/wp-content/uploads/2024/11/final_animation2_"; const images = []; const sequence = { frame: 2 }; for (let i = 0; i < frameCount; i++) { const img = new Image(); img.src = `${baseUrl}${(i + 1).toString().padStart(5, "0")}.jpg`; images.push(img); } gsap.to(sequence, { frame: frameCount - 1, snap: "frame", ease: "none", scrollTrigger: { scrub: 0.4, start: `10% top`, end: `+=5000`, trigger: "#page2", onUpdate: function() { render(); const text = document.getElementById("scroll-text"); const text11 = document.getElementById("scroll-text11"); // Text Opacity and Positioning Logic if (sequence.frame <= 80) { text.style.opacity = 1; text11.style.opacity = 1; } else if (sequence.frame > 80 && sequence.frame <= 90) { const fadeOutProgress = (sequence.frame - 80) / 10; // Calculate fade out progress text.style.opacity = 1 - fadeOutProgress; text.style.transform = `translate(0%, ${-180 - fadeOutProgress * 40}%)`; // Smooth transition upwards text11.style.opacity = 1 - fadeOutProgress; text11.style.transform = `translate(0%, ${-180 - fadeOutProgress * 40}%)`; // Smooth transition upwards } else { text.style.opacity = 0; // Fully faded out after frame 90 text.style.transform = `translate(0%, -220%)`; text11.style.opacity = 0; // Fully faded out after frame 90 text11.style.transform = `translate(0%, -220%)`; } const container1 = document.getElementById("container11"); let textOpacity1 = 0; let textY = 0; if (sequence.frame >= 108 && sequence.frame < 130) { textOpacity1 = (sequence.frame - 108) / 8; textY = -120 + (sequence.frame - 108); } else if (sequence.frame >= 130 && sequence.frame <= 155) { textOpacity1 = 1; textY = -120 + (130 - 108); } else if (sequence.frame < 108) { textOpacity1 = 0; } container1.style.opacity = textOpacity1; container1.style.transform = `translate(-50%, ${textY}%)`; } } }); images[2].onload = render; function render() { scaleImage(images[sequence.frame], context); } function scaleImage(img, ctx) { var canvas = ctx.canvas; var hRatio = canvas.width / img.width; var vRatio = canvas.height / img.height; var ratio = Math.min(hRatio, vRatio); var centerShift_x = (canvas.width - img.width * ratio) / 2; var centerShift_y = (canvas.height - img.height * ratio) / 2; context.clearRect(0, 0, canvas.width, canvas.height); context.drawImage( img, 0, 0, img.width, img.height, centerShift_x, centerShift_y, img.width * ratio, img.height * ratio ); } ScrollTrigger.create({ trigger: "#main", pin: true, start: `top top`, end: `+=5000` }); </script> </body> </html> I am working in wordpress elementor one section i used custom code in that i written a custom code as mention above i am working on image sequence in wordpress can you fix the "data is powerfull" is jumping on intiall scroll and please check the video it should fixed without any jumping and scroll smoothly https://www.loom.com/share/dcffc00fba024515b096428522120775?sid=a54fb642-d31f-4138-9b4e-a95cfbc0722e
  21. We are using Lenis smooth scroll but this wasnt causing the problems. After a few more tries I got a working solution now. Is there anything that can be improved with the code below? window.addEventListener('load', function() { let hasUserScrolled = false; window.addEventListener('scroll', () => { hasUserScrolled = true; }, { once: true }); gsap.registerPlugin(ScrollTrigger); const elementsToAnimate = ".fs-fade-in"; const allElements = document.querySelectorAll(elementsToAnimate); if (allElements.length === 0) { return; } gsap.set(allElements, { autoAlpha: 0, y: 50 }); function animateBatch(batch) { if (!hasUserScrolled) return; const elementsToPlay = batch.filter(el => !el.isAnimated); if (elementsToPlay.length > 0) { elementsToPlay.forEach(el => el.isAnimated = true); gsap.to(elementsToPlay, { autoAlpha: 1, y: 0, filter: 'blur(0px)', duration: 0.3, stagger: 0.2, ease: "power3.out" }); } } ScrollTrigger.batch(allElements, { start: "top 80%", onEnter: animateBatch }); ScrollTrigger.batch(allElements, { start: "bottom 20%", onEnterBack: animateBatch }); });
  22. rantsa jonathan

    Invalid property scrollTrigger set to

    Hi everyone, I’m encountering the following error when deploying my project on Netlify: Invalid property scrollTrigger set to {trigger: section#vertical, start: 'top top', end: 'bottom center', scrub: true}. This works perfectly fine in my local environment, but the error appears on Netlify. Here is what I’ve tried so far: I made sure to register the plugin using gsap.registerPlugin(ScrollTrigger), but the issue persists. I double-checked that all the DOM elements (#vertical and .col_left) exist before initializing ScrollTrigger. I tested the values passed to scrollTrigger using console.log, and they seem correct. Here’s a simplified example of my code: import React, { useEffect } from "react"; import "../styles/SecondScreen1.css"; import { gsap } from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; import orange from "../assets/images/Orange.webp"; import mapnazava from "../assets/images/Mpanazava.webp"; import gate from "../assets/images/Gate.webp"; import Lenis from "@studio-freight/lenis"; gsap.registerPlugin(ScrollTrigger); const SecondScreen1 = () => { useEffect(() => { const lenis = new Lenis({ duration: 1.2, easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), }); function raf(time) { lenis.raf(time); ScrollTrigger.update(); requestAnimationFrame(raf); } requestAnimationFrame(raf); const section1 = document.getElementById("vertical"); const colLeft = document.querySelector(".col_left"); // Gestion responsive avec matchMedia const mm = gsap.matchMedia(); mm.add("(max-width: 449px)", () => { gsap.fromTo( colLeft, { y: 0 }, { y: "90vh", duration: 1, ease: "tick", scrollTrigger: { trigger: section1, start: "top top", end: "bottom center", scrub: true, }, } ); }); mm.add("(min-width: 450px) and (max-width: 950px)", () => { gsap.fromTo( colLeft, { y: 0 }, { y: "115vh", duration: 1, ease: "tick", scrollTrigger: { trigger: section1, start: "top top", end: "bottom center", scrub: true, }, } ); }); mm.add("(min-width: 951px)", () => { gsap.fromTo( colLeft, { y: 0 }, { y: "32vh", duration: 1, ease: "tick", scrollTrigger: { trigger: section1, start: "top top", end: "bottom center", scrub: true, }, } ); }); return () => { ScrollTrigger.getAll().forEach((trigger) => trigger.kill()); mm.revert(); // Supprimer les animations lors du démontage }; }, []); return ( <div className="secondscreen1"> <section id="vertical"> <div className="container"> <div className="vertical__content"> <div className="col col_left"> <h2 className="vertical__heading custom-h2"> <span>Experience</span> <span>That you</span> <span>Can Trust</span> </h2> <h4 className="custom-h4"> MY PROFESSIONAL PATH </h4> <p className="custom-p"> Over my career, I've had the opportunity to consolidate my skills while working with outstanding organizations across various industries. Here are some of the projects and companies I’ve had the pleasure of contributing to. </p> </div> <div className="col col_right"> <div className="vertical__item"> <div className="header-container"> <img src={orange} alt="Logo" className="custom-image" /> <div className="test-xp"> <h3 className="custom-h3">Orange Digital Center</h3> <h3 className="custom-h3">Développeur Mobile et Web</h3> <h3 className="custom-h3 tech"> Flutter - React Js - Node Js </h3> </div> </div> <p> July 2024 - October 2024 ( 4-month internship ) <br /> As part of the Orange Summer Challenge, I created the Mobile application of the SPARE project with Flutter and the website with React Js. </p> </div> <div className="vertical__item"> <div className="header-container"> <img src={mapnazava} alt="Logo" className="custom-image" /> <div className="test-xp"> <h3 className="custom-h3">Mpanazava eto Madagascar</h3> <h3 className="custom-h3"> Développeur d’application Mobile </h3> <h3 className="custom-h3 tech">Flutter- Node Js</h3> </div> </div> <p> February 2024 - June 2024 <br /> Development of a mobile payment application payment form mobile application with Flutter and Node Js with transaction tracking. </p> </div> <div className="vertical__item"> <div className="header-container"> <img src={gate} alt="Logo" className="custom-image" /> <div className="test-xp"> <h3 className="custom-h3">Gate Company Group</h3> <h3 className="custom-h3">Développeur Mobile et Web</h3> <h3 className="custom-h3 tech"> React Native - React Js - Node Js </h3> </div> </div> <p> November 2023 - January 2024 ( 3-months internship ) <br /> Development of GateAfri, GateNews and AfriMuz with React Native. Optimization of the graphical (UI) and technical aspects of the GateAfri and GateNews websites with React Js. </p> </div> </div> </div> </div> </section> </div> ); }; export default SecondScreen1;
  23. Hi, Just call the refresh method before setting the new scroll position: onLeave(self) { let scroll = lenis.scroll, pinDistance = self.end - self.start; self.kill(true, true); scrollTextSecond.progress(1); // Call the refresh method ScrollTrigger.refresh(); lenis.scrollTo(scroll - pinDistance, { immediate: true }); } https://gsap.com/docs/v3/Plugins/ScrollTrigger/static.refresh() That will update the calculations ScrollTrigger makes and set the start/end points of the remaining ScrollTrigger instances according to the updated height of the DOM. The issue here is that when you remove the first ScrollTrigger instance the height of the document is updated because the pin space generated is removed, but the next ScrollTrigger instances don't know about that, so the calculations made, when the pin space was present in the document, are no longer accurate. Hopefully this clear things up Happy Tweening!
  24. Guest

    ScrollTrigger + Lenis problem

    My goal: Fixed header that fades out when scrolling down and fades in when scrolling up, very simple My problem: I have integrated Lenis in my project for smooth scroll. Following the guidelines provided here https://github.com/studio-freight/lenis Like suggested on github I've added this code for GSAP integration: lenis.on('scroll', ScrollTrigger.update) gsap.ticker.add((time)=>{ lenis.raf(time * 1000) }) gsap.ticker.lagSmoothing(0) The problem is that this piece of code causes the header to partially fade In or Out until the scroll animation is done (check codepen) I don't have the same problem if I completely remove this code, but that does not seem the right solution since this is provided as the code for GSAP integration What can I do? I want the header to fade as soon as the scroll movement starts, not to stop in between waiting for the scroll to end. Also, what does exactly this code do anyways? Is it fundamental for the perfect GSAP + Lenis setup?
  25. Hey Cassie, Thanks for the help - upon some further inspection I realized the issue wasn't with my code setup as much as with a confliction with lenis smooth scroll. For some reason when I scroll back to the top of the page the logo is jumping by the amount I scrolled down the page (above the viewport) before animating back into the hero, but when I turn off lenis it works fine. I already have the below code to try to keep lenis and gsap in sync but it seems to not be working. Any chance you've run into this issue before or know any other workarounds? (No worries if not, I know external libraries can be their own beast to work with). You can see the current iteration with the lenis issue here: (https://lvly-tv.webflow.io/). Glad to know I can just remove lenis if need be but would prefer to not have to do that. Do you know if I used the GSAP scrollsmoother instead, would prevent this issue from happening? // Keep lenis and scrolltrigger in sync lenis.on('scroll', () => { if (!ScrollTrigger) return; ScrollTrigger.update(); }); On a separate note - do you have any tips on how to handle code cleanup and re-instating on browser resize? I couldn't find any sample code on the flip page and have been unsure on the best way to approach this. Would something like this be sufficient? //kill flips on browser resize let windowWidth = window.innerWidth; window.addEventListener('resize', function () { if (window.innerWidth !== windowWidth) { windowWidth = window.innerWidth; //input code you want run after the browser width is changed Flip.killFlipsOf(logo); } });
×
×
  • Create New...