Jump to content
Search Community

How to apply gsap.context() with React?

Taun test
Moderator Tag

Recommended Posts

Hi. How should I apply gsapContext() in this React project?

I've read the docs - "

Let's say you've got a big block of GSAP code that's creating a bunch of different animations and you need to be able to revert() them all...

let ctx = gsap.context(() => {
 gsap.to(...);
 gsap.from(...);
 gsap.timeline().to(...).to(...);
 ...
});

// then later...
ctx.revert(); // BOOM! Every GSAP animation created in that function gets reverted!"

When I try wrap the animation code with ctx, I get syntax errors. I don't know what I'm doing. Any suggestions?

Codesandbox: https://codesandbox.io/p/github/taunhealy/Circa9_Website1/remote?layout=%7B%22sidebarPanel%22%3A%22EXPLORER%22%2C%22rootPanelGroup%22%3A%7B%22direction%22%3A%22horizontal%22%2C%22contentType%22%3A%22UNKNOWN%22%2C%22type%22%3A%22PANEL_GROUP%22%2C%22id%22%3A%22ROOT_LAYOUT%22%2C%22panels%22%3A%5B%7B%22type%22%3A%22PANEL_GROUP%22%2C%22contentType%22%3A%22UNKNOWN%22%2C%22direction%22%3A%22vertical%22%2C%22id%22%3A%22clpilwho400i7356he4lpemkn%22%2C%22sizes%22%3A%5B70%2C30%5D%2C%22panels%22%3A%5B%7B%22type%22%3A%22PANEL_GROUP%22%2C%22contentType%22%3A%22EDITOR%22%2C%22direction%22%3A%22horizontal%22%2C%22id%22%3A%22EDITOR%22%2C%22panels%22%3A%5B%7B%22type%22%3A%22PANEL%22%2C%22contentType%22%3A%22EDITOR%22%2C%22id%22%3A%22clpilwho400i2356h5bzguxv5%22%7D%5D%7D%2C%7B%22type%22%3A%22PANEL_GROUP%22%2C%22contentType%22%3A%22SHELLS%22%2C%22direction%22%3A%22horizontal%22%2C%22id%22%3A%22SHELLS%22%2C%22panels%22%3A%5B%7B%22type%22%3A%22PANEL%22%2C%22contentType%22%3A%22SHELLS%22%2C%22id%22%3A%22clpilwho400i4356hwsxjaz1b%22%7D%5D%2C%22sizes%22%3A%5B100%5D%7D%5D%7D%2C%7B%22type%22%3A%22PANEL_GROUP%22%2C%22contentType%22%3A%22DEVTOOLS%22%2C%22direction%22%3A%22vertical%22%2C%22id%22%3A%22DEVTOOLS%22%2C%22panels%22%3A%5B%7B%22type%22%3A%22PANEL%22%2C%22contentType%22%3A%22DEVTOOLS%22%2C%22id%22%3A%22clpilwho400i6356hy0h1wjva%22%7D%5D%2C%22sizes%22%3A%5B100%5D%7D%5D%2C%22sizes%22%3A%5B45.91051099661234%2C54.08948900338766%5D%7D%2C%22tabbedPanels%22%3A%7B%22clpilwho400i2356h5bzguxv5%22%3A%7B%22tabs%22%3A%5B%7B%22id%22%3A%22clpilwho400i1356hzmk7l9tz%22%2C%22mode%22%3A%22permanent%22%2C%22type%22%3A%22FILE%22%2C%22filepath%22%3A%22%2FREADME.md%22%2C%22state%22%3A%22IDLE%22%7D%5D%2C%22id%22%3A%22clpilwho400i2356h5bzguxv5%22%2C%22activeTabId%22%3A%22clpilwho400i1356hzmk7l9tz%22%7D%2C%22clpilwho400i6356hy0h1wjva%22%3A%7B%22tabs%22%3A%5B%7B%22id%22%3A%22clpilwho400i5356hmfd9pzie%22%2C%22mode%22%3A%22permanent%22%2C%22type%22%3A%22TASK_PORT%22%2C%22taskId%22%3A%22dev%22%2C%22port%22%3A3000%2C%22path%22%3A%22%2F%22%7D%5D%2C%22id%22%3A%22clpilwho400i6356hy0h1wjva%22%2C%22activeTabId%22%3A%22clpilwho400i5356hmfd9pzie%22%7D%2C%22clpilwho400i4356hwsxjaz1b%22%3A%7B%22tabs%22%3A%5B%7B%22id%22%3A%22clpilwho400i3356hrt4hf4ia%22%2C%22mode%22%3A%22permanent%22%2C%22type%22%3A%22TASK_LOG%22%2C%22taskId%22%3A%22dev%22%7D%5D%2C%22id%22%3A%22clpilwho400i4356hwsxjaz1b%22%2C%22activeTabId%22%3A%22clpilwho400i3356hrt4hf4ia%22%7D%7D%2C%22showDevtools%22%3Atrue%2C%22showShells%22%3Atrue%2C%22showSidebar%22%3Atrue%2C%22sidebarPanelSize%22%3A22.61421301650344%7D

 

 

Relevant code:

```

"use client";
 
import React, { useState, useMemo, useEffect, useRef } from "react";
import gsap from "gsap";
import "./sliderswiper.css";
interface Item {
  id: number;
  title: string;
  img: string;
  date: string;
  brand: string;
  desc?: string;
  director?: string;
  producer?: string;
  cinematographer?: string;
}
interface SliderProps {
  items: Item[];
}
const SliderSwiper: React.FC<SliderProps> = ({ items }) => {
  const [currentIndex, setCurrentIndex] = useState(0);
  const [selectedBrand, setSelectedBrand] = useState<string | null>("Recent");
  const brands = useMemo(() => {
    const uniqueBrands = Array.from(
      new Set(
        items.filter((item) => item.brand !== "all").map((item) => item.brand)
      )
    );
    return ["Recent", ...uniqueBrands];
  }, [items]);
  const filteredItems = useMemo(() => {
    if (selectedBrand === "Recent") {
      const validDateItems = items.filter((item) => item.date);
      return validDateItems
        .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
        .slice(0, 9);
    } else {
      return items.filter((item) => item.brand === selectedBrand);
    }
  }, [items, selectedBrand]);
  const itemTitlesRef = useRef<NodeListOf<Element> | null>(null);
  const itemImageRef = useRef<HTMLImageElement | null>(null);
  const buttonPrevRef = useRef<HTMLButtonElement | null>(null);
  const buttonNextRef = useRef<HTMLButtonElement | null>(null);
  const brandFilterButtonsRef = useRef<NodeListOf<Element> | null>(null);
  useEffect(() => {
    // Store refs to elements
    itemTitlesRef.current = document.querySelectorAll(".item-titles");
    itemImageRef.current = document.querySelector(".item-image");
    buttonPrevRef.current = document.querySelector(".button-prev");
    buttonNextRef.current = document.querySelector(".button-next");
    brandFilterButtonsRef.current = document.querySelectorAll(
      ".brand-filter-buttons"
    );
  }, []);
  const handleFilterChange = (brand: string | null): void => {
    gsap.to(
      [
        itemTitlesRef.current,
        itemImageRef.current,
        buttonPrevRef.current,
        buttonNextRef.current,
        brandFilterButtonsRef.current,
      ],
      {
        opacity: 0,
        duration: 0.3,
        ease: "power3.out",
        onComplete: () => {
          setSelectedBrand(brand);
          setCurrentIndex(0);
          animateIn();
        },
      }
    );
  };
  const animateIn = () => {
    const timeline = gsap.timeline({ defaults: { ease: "power3.out" } });
    timeline.fromTo(
      itemTitlesRef.current,
      { opacity: 0, y: 20 },
      { opacity: 1, y: 0, duration: 1 },
      "start"
    );
    timeline.fromTo(
      itemImageRef.current,
      { opacity: 0, y: 20 },
      { opacity: 1, y: 0, duration: 1 },
      "start+=0.2"
    );
    timeline.fromTo(
      buttonPrevRef.current,
      { opacity: 0, x: -20 },
      { opacity: 1, x: 0, duration: 0.3, stagger: 0.1 },
      "start+=0.5"
    );
    timeline.fromTo(
      buttonNextRef.current,
      { opacity: 0, x: -20 },
      { opacity: 1, x: 0, duration: 0.3, stagger: 0.1 },
      "start+=0.5"
    );
    timeline.fromTo(
      brandFilterButtonsRef.current,
      { opacity: 0, y: 20 },
      { opacity: 1, y: 0, duration: 0.5 },
      "start+=0.5"
    );
  };
  const handleNextItem = () => {
    gsap.to(
      [
        itemTitlesRef.current,
        itemImageRef.current,
        buttonPrevRef.current,
        buttonNextRef.current,
      ],
      {
        opacity: 0,
        duration: 0.3,
        ease: "power3.out",
        onComplete: () => {
          setCurrentIndex(
            (prevIndex) => (prevIndex + 1) % filteredItems.length
          );
          animateIn();
        },
      }
    );
  };
  const handlePrevItem = () => {
    gsap.to(
      [
        itemImageRef.current,
        itemTitlesRef.current,
        buttonPrevRef.current,
        buttonNextRef.current,
      ],
      {
        opacity: 0,
        duration: 0.3,
        ease: "power3.out",
        onComplete: () => {
          setCurrentIndex(
            (prevIndex) =>
              (prevIndex - 1 + filteredItems.length) % filteredItems.length
          );
          animateIn();
        },
      }
    );
  };
  return (
    <div className="item-background-container">
      {filteredItems.length > 0 && (
        <section className="item-titles">
          <div className="brand-title">{filteredItems[currentIndex].brand}</div>
          <div className="item-title">{filteredItems[currentIndex].title}</div>
        </section>
      )}
      <div className="brand-filter-sidebar">
        {brands.map((brand) => (
          <button
            key={brand}
            className={`brand-filter-buttons ${
              selectedBrand === brand ? "active" : ""
            }`}
            onClick={() => handleFilterChange(brand)}
          >
            {brand}
          </button>
        ))}
      </div>
      <div className="item-image-container">
        {filteredItems.length > 0 && (
          <img
            className="item-image"
            src={filteredItems[currentIndex].img}
            alt={filteredItems[currentIndex].title}
          />
        )}
      </div>
      <div className="nextprev-button-wrapper">
        <button type="button" className="button-prev" onClick={handlePrevItem}>
          <img src="/arrows/left-chevron-svgrepo-com.svg" alt="Previous" />
        </button>
        <button type="button" className="button-next" onClick={handleNextItem}>
          <img src="/arrows/right-chevron-svgrepo-com.svg" alt="Next" />
        </button>
      </div>
    </div>
  );
};
export default SliderSwiper;```

I'd like to prevent the flashing that sometimes happens when animations fire.
 
Link to comment
Share on other sites

I've gone through the examples and read the docs. My confusion lies in the wrapping of animations with useLayoutEffect.

The GSAP examples don't express the complexity of multiple React animations called inside and outside of useEffect.
I think this may be more of a React question, but if you can help that'll be great.

So I can't call the animateIn() function because it's defined inside of the useLayout. If I wrap all animations inside useLayout hooks, I can't call them outside of that hook, so I can't call it onClick that's defined in the return statement? Do I then have multiple gsap.context functions within each useLayout?

What do you suggest?

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