Jump to content
Search Community

GSAP x React

Olav test
Moderator Tag

Recommended Posts

Hi everyone,

 

I've been fighting with gsap and react all day and finally have got it to work properly.

Now I'm in a very early stage of developing a website, and was wondering if my approach is 'correct', I've included a simple component using gsap which in functionality works fine, but I'd like to know where I can improve.

 

'use client'

import Image from 'next/image'
import { useEffect, useRef } from 'react'
import { gsap } from 'gsap/dist/gsap'

export default function Preloader() {
  const el = useRef<HTMLDivElement | null>(null)
  const tl = useRef()

  useEffect(() => {
    const ctx = gsap.context(() => {
      tl.current = gsap
        .timeline()
        .to(
          '#preloader-logo',
          { y: '160%', duration: 1, ease: 'power2.inOut' },
          1,
        )
        .to(
          '#preloader-bg',
          { y: '-100%', duration: 0.5, ease: 'power2.inOut' },
          '<+=.5',
        )
    }, el)
  }, [])
  return (
    <div ref={el} aria-busy="true" aria-label="Loading">
      <div
        className="fixed inset-0 flex h-full w-full items-center justify-center bg-crftd-purple"
        id="preloader-bg"
      >
        <div className="overflow-hidden">
          <Image
            priority
            src="/logos/crftd-letters-dark.svg"
            alt="CRFTD text logo"
            width={160}
            height={80}
            id="preloader-logo"
          />
        </div>
      </div>
    </div>
  )
}

Anyone have some tips? Live version is visible here btw; https://www.crftd.nl

Thanks in advance, I really appreciate your time.

 

Kind regards,

Link to comment
Share on other sites

Hi,

 

In general everything looks good in the snippet you're showing. The only missing part is that you're not doing proper clean up in your effect hook:

 

useLayoutEffect(() => {
  const ctx = gsap.context();
  return () => ctx.revert(); // <- clean up!!
}, []);

 

Take a good look at this resources

 

https://greensock.com/react

 

Also we recommend using the layout effect since is better for animations. Considering that you're using Next you could run into issues using the layout effect so it's better to use a custom hook for checking which effect hook should be used depending if the code is being run on the server or the client

https://greensock.com/react-advanced#useIsomorphicLayoutEffect

 

 

Hopefully this helps.

Happy Tweening!

Link to comment
Share on other sites

Hi Rodrigo,

 

Thanks for your quick reply! I've implemented your suggestions.

Another question, maybe a bit off topic but it's probably a very short answer;

 

In the installation guide I stumbled upon the following:

 

If you are using server side rendering, you may need to add GSAP to your transpile property in your configuration settings (like nuxt.config):

build: {
    ...
    transpile: ['gsap'],
},

 

Now I've also implemented that in my project, the next.config.js, but am getting warnings;

warn  - Invalid next.config.js options detected: 
warn  -     The root value has an unexpected property, build, which is not in the list of allowed properties (amp, analyticsId, assetPrefix, basePath, cleanDistDir, compiler, compress, configOrigin, crossOrigin, devIndicators, distDir, env, eslint, excludeDefaultMomentLocales, experimental, exportPathMap, generateBuildId, generateEtags, headers, httpAgentOptions, i18n, images, modularizeImports, onDemandEntries, optimizeFonts, output, outputFileTracing, pageExtensions, poweredByHeader, productionBrowserSourceMaps, publicRuntimeConfig, reactStrictMode, redirects, rewrites, sassOptions, serverRuntimeConfig, skipMiddlewareUrlNormalize, skipTrailingSlashRedirect, staticPageGenerationTimeout, swcMinify, target, trailingSlash, transpilePackages, typescript, useFileSystemPublicRoutes, webpack).
warn  - See more info here: https://nextjs.org/docs/messages/invalid-next-config

 

My next.config.js looks like this:
 

const nextConfig = {
  experimental: {
    appDir: true,
  },
  build: {
    transpile: ['gsap'],
  },
}

module.exports = nextConfig

 

Is this a known thing for next and do you know of any fix? Thanks in advance!

Link to comment
Share on other sites

  • 4 weeks later...

Hi! I took another look at my code, since I want the scrollbar to be locked while the animation is playing. I made the following:
 

'use client'

import Image from 'next/image'
import { useEffect, useLayoutEffect, useRef } from 'react'
import { gsap } from 'gsap/dist/gsap'

export default function Preloader() {
  const el = useRef<HTMLDivElement | null>(null)
  const tl = useRef()

  const useIsomorphicLayoutEffect =
    typeof window !== 'undefined' ? useLayoutEffect : useEffect

  useIsomorphicLayoutEffect(() => {
    document.body.style.overflow = 'hidden'
    const ctx = gsap.context(() => {
      tl.current = gsap
        .timeline()
        .set('body', { overflow: 'hidden' })
        .to(
          '#preloader-logo',
          { yPercent: 160, duration: 1, ease: 'power2.inOut' },
          1,
        )
        .to(
          '#preloader-bg',
          { yPercent: -100, duration: 0.25, ease: 'power2.inOut' },
          '<+=.5',
        )
    }, el)
    return () => {
      document.body.style.removeProperty('overflow')
      ctx.revert()
    }
  }, [])

  return (
    <div ref={el} aria-busy="true" aria-label="Loading">
      <div
        className="fixed inset-0 z-50 flex h-full w-full items-center justify-center bg-crftd-purple"
        id="preloader-bg"
      >
        <div className="overflow-hidden">
          <Image
            priority
            src="/logos/crftd-letters-dark.svg"
            alt="CRFTD text logo"
            width={160}
            height={80}
            id="preloader-logo"
          />
        </div>
      </div>
    </div>
  )
}

Just before the ctx, I add overflow: hidden to the body element, which works perfectly fine. Then just before the cleanup, I try to remove the overflow property, but this doesn't seem to work.

 

Also, as you can see I tried adding a .set to for the overflow hidden at the beginning of my timeline, but that doesn't work either. For now I'm looking at the document.body.style lines.

 

Then I tried adding a console.log to my return statement, which also never fires, which means that the 'cleanup' also never goes through. Can't seem to figure out what is going on here. Any clue's?

Link to comment
Share on other sites

Hi,

 

Unfortunately without a minimal demo there is not much we can do.

 

The one thing I can tell you is that your cleanup code is going to run only when that component is unmounted, whether you do that manually or when you navigate to another page using Next's routing. Right now the fact that your cleanup code is not running and the overflow is not removed from the body tag makes perfect sense based on the code you're posting here.

 

What you could try is using an onComplete callback in order to remove the overflow: hidden from the body tag:

useIsomorphicLayoutEffect(() => {
  document.body.style.overflow = 'hidden'
  const ctx = gsap.context(() => {
    tl.current = gsap
      .timeline({
        onComplete: () => gsap.set('body', { overflow: 'auto' }),
      })
      .set('body', { overflow: 'hidden' })
      .to(
      '#preloader-logo',
      { yPercent: 160, duration: 1, ease: 'power2.inOut' },
      1,
    )
      .to(
      '#preloader-bg',
      { yPercent: -100, duration: 0.25, ease: 'power2.inOut' },
      '<+=.5',
    )
  }, el)
  return () => ctx.revert();
}, [])

Also there is no need to create the isomorphic layout hook inside the component definition, with that approach you'll have to create the custom hook on every page/component that needs it and that could become quite difficult to track. Just create a helper file and export it from there:

// /helpers/isomorphicLayout.js
import { useEffect, useLayoutEffect } from "react";

export const useIsomorphicLayoutEffect = typeof window !== "undefined"
  ? useLayoutEffect
  : useEffect;


// /pages/index.js
import { useIsomorphicLayoutEffect } from "../helper/isomorphicEffect";

export default function Home () {
  useIsomorphicLayoutEffect(() => {
    // your code here
  }, []);
};

I created a simple example using Next and GSAP without any route transitions so you can see the approach and fork it if you need to create a minimal demo:

https://stackblitz.com/edit/nextjs-hwpi1b

 

Hopefully this helps.

Happy Tweening!

  • Thanks 1
Link to comment
Share on other sites

I changed my code to the following:
 

useIsomorphicLayoutEffect(() => {
    const ctx = gsap.context(() => {
      tl.current = gsap
        .timeline({
          onStart: () => {
            document.body.style.overflow = 'hidden'
            document.body.style.cursor = 'wait'
          },
        })
        .to(
          '#preloader-logo',
          { yPercent: 160, duration: 1, ease: 'power2.inOut' },
          1,
        )
        .to(
          '#preloader-bg',
          {
            yPercent: -100,
            duration: 0.25,
            ease: 'power2.inOut',
            onStart: () => {
              document.body.style.overflow = 'auto'
              document.body.style.cursor = 'auto'
            },
          },
          '<+=.5',
        )
    }, el)
    return () => ctx.revert()
  }, [])

Which seems to work perfectly fine! Thanks for your suggestion @Rodrigo

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