Jump to content
Search Community

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

granularclouds
Moderator Tag

Recommended Posts

granularclouds
Posted

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!

 

Posted

Hi,

 

I never used Lenis with GSAP and React so I couldn't really tell you about it. This does look odd to me TBH:

useEffect(() => {
  function update(time) {
    lenisRef.current?.lenis?.raf(time * 1000)
  }

  gsap.ticker.add(update)

  return () => {
    gsap.ticker.remove(update)
  }
})

That useEffect hook doesn't have any dependencies, so every time that component re-renders all that is called again, I would use an empty dependencies array, but they must have a reason for suggesting that.

 

Also Lenis is not a GSAP product so we can't really offer support for it, we have our own smooth scrolling solution in ScrollSmoother:

https://gsap.com/docs/v3/Plugins/ScrollSmoother/

 

Finally performance is a really deep topic and most likely this is tied to perhaps the Lenis react wrapper eating quite some resources and creating this rather than a GSAP specific problem and I don't recall other threads in the forums on this particular subject.

 

Sorry I can't be of more assistance, hopefully other users with more experience with lenis and react can chime in.

 

Happy Tweening!

granularclouds
Posted

Thank you - I know about ScrollSmoother but between not wishing to tie a feature for a site to a subscription package and also not wishing to work around the position: fixed limitation, for this project in particular I would go with Lenis.

 

I tried a bunch of stuff and the performance is just not ideal, so I think I'll stick with ScrollTrigger and scrub and ditch the smoothing for non-animation things. 

 

Thanks!

  • 9 months later...
devshinthant
Posted

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>
)
}
  • Thanks 1

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...