Jump to content
Search Community

How to manage animation in next js when working with dynamic content.

sagar_dev test
Moderator Tag

Recommended Posts

In my next.js v12 project I am using RTK Query's createAPI to fetch data and GSAP to animate the elements. However in some dynamic content the animation is not expected. For example in homepage where banner has fixed height doesn't seems to be have that problem but after the banner section all elements scroll trigger markers way above the elements. Meaning markers are coming before the element.

image.thumb.png.cebbbc2f11e5c5976bc7578cf2eaaf9c.png

 

In the above image the first start marker should be aligned with the step 1: Download the App but it's animation starts and end's even before it comes in the view. Heres the code:
 

import React, { useEffect, useRef } from 'react'
import Tiptap from 'components/Tiptap'
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/dist/ScrollTrigger'

const StepCard = ({ count, stepName, stepDetail }) => {
  const cardRef = useRef()

  useEffect(() => {
    gsap.registerPlugin(ScrollTrigger)

    const animation = gsap.context(() => {
      const tl = gsap.timeline({
        scrollTrigger: {
          trigger: cardRef.current,
          toggleActions: 'restart reset complete reset',
          start: 'top bottom',
          end: '100% top',
          markers: true
        }
      })

      tl.from(cardRef.current, {
        y: 30,
        autoAlpha: 0,
        ease: 'back.out',
        duration: 0.8
      })
    })

    return () => animation.revert()
  }, [])
  return (
    <>
      <div
        ref={cardRef}
        className='p-6 rounded-xl space-y-5 -card'>
        <p className='text-black/10 text-[32px] font-semibold'>{count}</p>
        <h3 className='text-[22px]'>{stepName}</h3>
        <div className='text-[14px]'>
          <Tiptap data={stepDetail} />
        </div>
      </div>
    </>
  )
}

export default StepCard

There are same problem across the website where almost all of the contents are dynamic.

Note: I tried re creating the issue in code sandbox but can't recreate the exact problem so sorry for that in advance.

Link to comment
Share on other sites

Hi,

 

Indeed is hard without a minimal demo. The only advice I can offer you right now is to run ScrollTrigger.refresh() after the server response is completed and the new data is reflected in the DOM. Use a layoutEffect hook in order to listen to either some props or state update that is tied to the server's response. Once you have the new data, create your ScrollTrigger instances and run ScrollTrigger.refresh(). I'll see if I can create a simple example that shows this and perhaps upload it to a repo.

 

Also remember to use GSAP Context in that layoutEffect too ;)

 

Hopefully this helps.

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

On 6/2/2023 at 7:58 PM, Rodrigo said:

The only advice I can offer you right now is to run ScrollTrigger.refresh() after the server response is completed and the new data is reflected in the DOM.

I tried using ScrollTrigger.refresh() but doesn't seems to be working. Or it might be just me who doesn't know how to use it properly, most likely. The thought when implementing this is, RTK provides isFetching method that returns boolean values based on if it is featching any data or not. After completing the fetch it returns false. And when it is false all the data should have been mapped on the UI. And I can refresh the ScrollTrigger and it should work just fine. But guess I was wrong, it is not working.

 

  gsap.registerPlugin(ScrollTrigger)
  useLayoutEffect(() => {
    const animation = gsap.context(() => {
      const tl = gsap.timeline({
        scrollTrigger: {
          trigger: '.title',
          toggleActions: 'restart reset complete reset',
          start: 'top bottom',
          end: '100% top',
          markers: true
        }
      })

      tl.from(['.title', '.detail'], {
        y: 20,
        autoAlpha: 0,
        duration: 0.8,
        stagger: 0.3,
        ease: 'back.out'
      })
    })

    // ScrollTrigger.refresh()
    return () => animation.revert()
  }, [])

  useLayoutEffect(() => {
    if (!isFetching) {
      ScrollTrigger.refresh()
    }
  }, [isFetching])

 

Link to comment
Share on other sites

Hi,

 

Based on a quick reading of the RTK Query docs I think your approach might not be the best. Right now you're checking the loading state but when that is updated, that doesn't mean that the new data is actually updated in the RTK store and that the DOM is updated as well.

 

I think a better approach would be something like this example:

https://redux-toolkit.js.org/rtk-query/overview#use-hooks-in-components

https://redux-toolkit.js.org/rtk-query/api/created-api/hooks#usequery

 

With that check the changes of the data property in your layout effect. Is worth noticing that if you just run ScrollTrigger refresh that might not have the effect you're looking for, since ScrollTrigger will update the start and end positions of the existing instances, it won't create new ones, otherwise the refresh method would be super expensive. You have to get the new elements and create the new ScrollTrigger instances just for the new elements so you'll have to come up with a way to identify just those elements in the DOM.

 

Also this might be a good case for using ScrollTrigger Batch:

https://greensock.com/docs/v3/Plugins/ScrollTrigger/static.batch()

 

Here is a simple example that is kind of similar to the situation you have in place:

See the Pen wvzzpEO by GreenSock (@GreenSock) on CodePen

 

Hopefully this helps.

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

13 hours ago, Rodrigo said:

Also this might be a good case for using ScrollTrigger Batch:

@Rodrigo Thank you for this tip. I used the ScrollTrigger Batch and it seems to be working. The final code is something like this

useEffect(() => {
    gsap.set(cardRef.current, {
      autoAlpha: 0,
      y: 60
    })
    const show = (batch) => {
      gsap.set(batch, {
        autoAlpha: 0,
        y: 60
      })

      batch.forEach((item, i) => {
        gsap.timeline().to(item, {
          autoAlpha: 1,
          y: 0,
          overwrite: true,
          duration: 0.9,
          stagger: 0.2,
          ease: 'back.out'
        })
      })
    }

    const hide = (batch) => {
      gsap.set(batch, {
        autoAlpha: 0,
        y: 60,
        overwrite: true
      })
    }

    const createBatch = (target) => {
      ScrollTrigger.refresh()

      ScrollTrigger.batch(target, {
        onEnter: show,
        onLeave: hide,
        onEnterBack: show,
        onLeaveBack: hide,
        markers: true,
        start: 'top 80%',
        end: '100% -10%'
      })
    }

    createBatch(cardRef.current)
  }, [isSuccess, cardRef])

  useEffect(() => {
    isSuccess && ScrollTrigger.refresh(true)
  }, [isSuccess])

In createBatch(), the ScrollTrigger.refresh() is required for it to work properly otherwise it is just like before. If you see any mistake in above code please tell me.

Thank You very much for you help.

Link to comment
Share on other sites

Hi,

 

IMHO I don't think all those calls to ScrollTrigger.refresh() are needed. You have two different effect hooks and both have isSuccess as a dependency, so both will run when that is updated. Also the new elements are added before or after the elements that were in the DOM before fetching new data? If the new elements are added after, then just call ScrollTrigger.refresh() after the new elements are added to the DOM in order to update the ScrollTrigger instances that are after those elements. Once again you seem to be using the flag for the fetching process and not checking for actual changes in the data. Also use useLayoutEffect because that will ensure that the DOM has actually been updated with the new data.

 

Finally you're not using GSAP Context:

 

I strongly recommend you to get a better grasp of React and it's hooks and RTK as well since you seem to have a few React/RTK conceptual issues more than GSAP ones.

 

Good luck with your project.

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

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...