Jump to content
Search Community

gabriel.ortiz

Members
  • Posts

    27
  • Joined

  • Last visited

Posts posted by gabriel.ortiz

  1. Maybe it was hardware acceleration issues? This morning, I also changed something in my storybook that seemed to help - even though I can't explain why. 

     

    On initial load, I set the initial segment position with GSAP, and that seemed to make a huge difference. Any theories why setting GSAP styles on the initial item made the difference? The flickering i was seeing seemed to happen for the first transition between segments.

     

    After this change, it seems to be fluid. But I don't understand why. Is it best practice to set initial GSAP styles on all elements before animating? Thanks so much for your help @Rodrigo
     

    This is what i added:

      /**
       * Animation to play when the segment is the initial segment
       */
      setInitialSegment: () => gsap.core.Timeline;
    
    ....
    
    // For setting the initial animation state with gsap values
          ctx.add("setInitialSegment", () => {
            return gsap.set(sectionRef.current, {
              ...TRANSLATION_BASE,
              yPercent: 0,
              autoAlpha: 1,
              scale: 1,
            });
          });
    
    ....
    
        useEffect(() => {
          /**
           * If the previous segment is null, then this is the initial segment
           */
          if (previousSegment === null) {
            /**
             * Establish gsap styles on the initial segment
             */
            if (isActive) {
              if (ctx && "setInitialSegment" in ctx) {
                ctx?.setInitialSegment?.();
              }
            }
    
            return;
          }
    
    

     

  2. OMG @Rodrigo i'm sorry i realized my codesandbox was private: Here you can try again: https://codesandbox.io/p/sandbox/gsap-expedition-w96d45?file=%2Fsrc%2Fcomponents%2FExpedition%2FSegment.tsx Thank you for your time if you have any. I appreciate it

    Also, this is the core animation segment for reference:

    import React, { forwardRef, useEffect, useRef } from "react";
    import styles from "./styles.module.scss";
    import { useActiveSegment, BkExpeditionContextValues } from ".";
    import { mergeRefs } from "../util/mergeRefs";
    import clsx from "clsx";
    import { useGsapContext } from "../util/useGsapContext";
    import gsap from "gsap";
    
    /**
     * Registered animations for the context
     */
    export type RegisteredContextAnimations = {
      /**
       * Animation to play when the segment is active
       */
      segmentIn: () => gsap.core.Timeline;
      /**
       * Animation to play when the segment is inactive
       */
      segmentOut: () => gsap.core.Timeline;
    };
    
    /**
     * Segment component to be used within the BkExpedition component
     */
    export type SegmentProps = React.ComponentPropsWithoutRef<"section"> & {
      segmentKey: BkExpeditionContextValues["activeSegment"];
    };
    
    export const Segment = forwardRef<HTMLDivElement, SegmentProps>(
      (props, ref) => {
        const {
          segmentKey,
          "aria-label": ariaLabel,
          className,
          children,
          ...restOfHtmlAttrs
        } = props;
    
        /**
         * Get the active segment from the context
         */
        const { activeSegment, previousSegment } = useActiveSegment();
    
        /**
         * Check if the segment is active
         */
        const isActive = activeSegment === segmentKey;
    
        /**
         * Reference to the segment
         */
        const sectionRef = useRef<HTMLDivElement>(null);
    
        /**
         * Get the context for the animations
         */
        const ctx = useGsapContext<HTMLDivElement, RegisteredContextAnimations>(
          sectionRef
        );
    
        /**
         * Animation constants
         */
        const ANIMATION_DURATION = 1;
        const ANIMATION_SCALE = 0.9;
        const ANIMATION_EASING = "power1.inOut";
    
        const TRANSLATION_BASE = {
          y: 0,
          x: 0,
        };
    
        useEffect(() => {
          /**
           * Register the animations for the segment
           */
          ctx.add("segmentIn", () => {
            return gsap
              .timeline({
                paused: true,
                onComplete: () => {
                  sectionRef.current?.focus();
                },
              })
              .fromTo(
                sectionRef.current,
                {
                  ...TRANSLATION_BASE,
                  yPercent: 100,
                  autoAlpha: 0,
                },
                {
                  ...TRANSLATION_BASE,
                  yPercent: 0,
                  autoAlpha: 1,
                  scale: 1,
                  duration: ANIMATION_DURATION,
                  ease: ANIMATION_EASING,
                  immediateRender: false,
                }
              );
          });
    
          /**
           * Register the animations for the segment to animate out
           */
          ctx.add("segmentOut", () => {
            return gsap
              .timeline({
                paused: true,
                onComplete: () => {
                  gsap.set(sectionRef.current, {
                    ...TRANSLATION_BASE,
                    yPercent: 100,
                    autoAlpha: 0,
                    scale: 0.95,
                  });
                },
              })
              .fromTo(
                sectionRef.current,
                {
                  ...TRANSLATION_BASE,
                  yPercent: 0,
                  autoAlpha: 1,
                  scale: 1,
                },
                {
                  ...TRANSLATION_BASE,
                  yPercent: -100,
                  autoAlpha: 0,
                  scale: ANIMATION_SCALE,
                  duration: ANIMATION_DURATION,
                  ease: ANIMATION_EASING,
                  immediateRender: false,
                }
              );
          });
    
          return () => {
            ctx.revert();
          };
        }, []);
    
        useEffect(() => {
          if (previousSegment === null) {
            return;
          }
    
          if (activeSegment === segmentKey) {
            if (ctx && "segmentIn" in ctx) {
              ctx?.segmentIn?.().play();
            }
            return;
          }
    
          if (previousSegment === segmentKey) {
            if (ctx && "segmentOut" in ctx) {
              ctx?.segmentOut?.().play();
            }
    
            return;
          }
    
          return () => {
            ctx.revert();
          };
          // only run when the active segment changes
          // eslint-disable-next-line react-hooks/exhaustive-deps
        }, [activeSegment, previousSegment, segmentKey]);
    
        /**
         * Add error handling for when the segmentKey is not set
         */
        if (!segmentKey || segmentKey === "") {
          if (process.env.NODE_ENV === "development") {
            throw new Error(
              "The segmentKey prop must be passed to the BkExpedition.Segment component"
            );
          }
    
          return null;
        }
    
        const inlineStyles: React.CSSProperties = {
          ...(previousSegment === null && {
            transform: isActive ? "translateY(0%)" : "translateY(100%)",
            opacity: isActive ? 1 : 0,
          }),
        };
    
        return (
          <section
            ref={mergeRefs(ref, sectionRef)}
            {...restOfHtmlAttrs}
            aria-hidden={!isActive}
            tabIndex={-1}
            aria-label={ariaLabel || segmentKey}
            className={clsx(
              className,
              styles.segment,
              isActive ? styles.isActive : styles.isInactive,
              isActive && previousSegment === null && styles.isInitial
            )}
            style={{}}
            data-segment-key={segmentKey}
            data-segment-active={isActive}
            data-testid={`${segmentKey}-segment`}
          >
            {children}
          </section>
        );
      }
    );
    
    Segment.displayName = "Segment";

     

  3. Hi yall, I'm hoping I can get some help with some gsap animations in React. I have a UI i'm working on where an inactive segment animates by translating up, while a new active element translates to 0 from the bottom.. There is a noticeable flicker of no content before the animation starts. I don't know what I'm doing wrong to cause this. I first noticed it in my storybook.

     

    Here's a link to my code transferred to a codesandbox to demonstrate; this is the core of the logic where the animation happens: https://codesandbox.io/p/sandbox/gsap-expedition-w96d45?file=%2Fsrc%2Fcomponents%2FExpedition%2FSegment.tsx%3A189%2C28 

     

    You can recreate the animation sequence by clicking on the buttons at the top which should trigger the animations to change. Nothing will happen if you click the same button twice

     

     

     

    Caveat: I know I should be using useGsap hook - but I can't until we update our react version. So here i use a custom hook instead.


    Any help would really be appreciated!image.thumb.png.3cc5cc5c54c4be0d2391ac797753bbfa.png

  4. 35 minutes ago, Rodrigo said:

    Hi,

     

    I can't see anything wrong in the code you posted, plus the latest version of the hook is importing useRef as you can see here:

    https://github.com/greensock/react/blob/main/src/index.js

     

    Can you provide a minimal demo on Stackblitz that illustrates this? 

     

    https://stackblitz.com/

     

    Happy Tweening!

    Thank you @Rodrigo. The hook itself looks good. I think the issue could be a mismatch of React versions. Looking at the package.json. I'm using version ^18. and the hook is using ^16. I do think there are some breaking changes between these versions. I've ran into issues like this before.  The error message: 
    `Uncaught TypeError: Cannot read properties of null (reading 'useRef')`  I've read can be associated with version mismatches.

     

     


     

  5. I'm just starting out with React and `useGSAP` and I'm running into an issue with out what appears to be the hook registering:

     

    react.development.js:1630 Uncaught TypeError: Cannot read properties of null (reading 'useRef')
        at useRef (react.development.js:1630:1)
        at useGSAP (index.js:32:1)


    However, despite this error - I can console.log the `useGSAP` hook, and also the `gsap` package and both are successfully loaded. This error message happens when I try to use useGSAP.
     

    This is the error message i'm getting:

    Segment.tsx:39 Warning: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
    1. You might have mismatching versions of React and the renderer (such as React DOM)
    2. You might be breaking the Rules of Hooks
    3. You might have more than one copy of React in the same app
    See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
    printWarning @ react.development.js:209
    error @ react.development.js:183
    resolveDispatcher @ react.development.js:1592
    useRef @ react.development.js:1629
    useGSAP @ index.js:32
    (anonymous) @ Segment.tsx:39
    
    react.development.js:1630 Uncaught TypeError: Cannot read properties of null (reading 'useRef')
        at useRef (react.development.js:1630:1)
        at useGSAP (index.js:32:1)
        at Segment.tsx:39:1
        at renderWithHooks (react-dom.development.js:16305:1)
        at updateForwardRef (react-dom.development.js:19226:1)
        at beginWork (react-dom.development.js:21636:1)
        at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
        at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
        at invokeGuardedCallback (react-dom.development.js:4277:1)
        at beginWork$1 (react-dom.development.js:27451:1)

     

    here's what i have so far:

    import React, { forwardRef, useEffect, useCallback, useRef } from "react";
    import {
      useActiveSegment,
      BkExpeditionContextValues,
    } from "src/BkExpedition/index";
    import { mergeRefs } from "src/util/mergRefs";
    import clsx from "clsx";
    import gsap from "gsap/dist/gsap";
    import { useGSAP } from "@gsap/react";
    
    gsap.registerPlugin(useGSAP);
    
    export type SegmentProps = Omit<
      React.ComponentPropsWithoutRef<"section">,
      "onAnimationStart" | "onDragStart" | "onDragEnd" | "onDrag" | "onDragOver"
    > & {
      segmentKey: BkExpeditionContextValues["activeSegment"];
    };
    
    export const Segment = forwardRef<HTMLDivElement, SegmentProps>(
      (props, ref) => {
        const { segmentKey, className, children, ...restOfHtmlAttrs } = props;
    
        const { activeSegment, previousSegment } = useActiveSegment();
        const isActive = activeSegment === segmentKey;
    
        const sectionRef = useRef<HTMLDivElement>(null);
    
        const animateInTL = useRef<gsap.core.Timeline>();
        const animateOutTL = useRef<gsap.core.Timeline>();
    
        useGSAP(() => {
          // console.log("sectionRef", sectionRef.current);
          // if (sectionRef.current === null) return;
          //
          // animateInTL.current = gsap
          //   .timeline({ paused: true })
          //   .fromTo(sectionRef?.current, { x: "100%" }, { x: "0%" });
          // animateOutTL.current = gsap
          //   .timeline({ paused: true })
          //   .fromTo(sectionRef?.current, { x: "0%" }, { x: "-100%" });
        });
    
        useEffect(() => {
          if (previousSegment === null) {
            return;
          }
    
          if (activeSegment === segmentKey) {
            animateInTL?.current?.play();
            return;
          }
    
          if (previousSegment === segmentKey) {
            animateOutTL?.current?.play();
            return;
          }
        }, [activeSegment, previousSegment, segmentKey]);
    
        return (
          <section
            ref={mergeRefs(ref, sectionRef)}
            {...restOfHtmlAttrs}
            aria-hidden={!isActive}
            className={clsx(className, styles.segment, isActive && styles.isActive)}
            style={{
              transform: isActive ? "translateX(0%)" : "translateX(100%)",
            }}
          >
            {children}
          </section>
        );
      },
    );
    
    Segment.displayName = "Segment";

    Am i not registering the book in the right place? Should it be registered inside the function component? FWIW - I plan to use this component in Next.js.

    Any help would be really appreciated

     

    -Gabriel

    Screenshot 2024-04-26 at 12.18.48 PM.png

  6. Thanks so much @GreenSock! I appreciate your response!

     

    So I got something working for me in case others might encounter the same situation with context. I ended up modifying the react Gsap context hook to cast the return context types with an optional object that you can pass in through a generic

     

    /**
     * Wrapper hook initializes GSAP context object
     *
     * @param {RefObject}
     * @param {gsap.ContextFunc}
     * @returns {gsap.Context}
     */
    export function useGsapContext<
      T extends HTMLElement = HTMLDivElement,
      R extends Record<string, unknown> = Record<string, unknown>,
      // It's ok to allow an empty function since gsap.context() needs a function argument to initialize  
      // eslint-disable-next-line @typescript-eslint/no-empty-function
    >(scope?: RefObject<T>, context: gsap.ContextFunc = () => {}) {
      return useMemo(
        () => gsap.context(context, scope),
        // It's not necessary to list `context` as a dependency
        // eslint-disable-next-line react-hooks/exhaustive-deps
        [scope],
        /**
         * Normally its hacky to type assert - but since this context logic is
         * isolated in this hook there is little risk for these types causing issues
         * with other implementations of GSAP context
         */
      ) as Partial<R> & gsap.Context;
    }
    
    /**
     * These types will be added to the return of the gsap context so typescript
     * will know to expect these properties if referenced later
     */
    type ContextMembers = {
      newTab: (newTabKey: string, oldTabKey: string | null) => void;
    };
    
    const wrapperRef = useRef<HTMLDivElement>(null);
    const ctx = useGsapContext<HTMLDivElement, ContextMembers>(wrapperRef);
    
    useEffect(()=>{
    	ctx.add("newTab", (newTabKey: string, oldTabKey: string | null) => {....})
    }, []);
    
    useEffect(()=>{
    	ctx.newTab("foo", "bar");
    },[tabState]);

     

     

    Additionally, I noticed gsap.context() will return undefined if no function argument is passed in. Is this expected? The Context types have the callback function as optional argument, and but it appears to be required type argument or else nothing is initialized.

    // gsap-core.d.ts   
    function context(func?: ContextFunc, scope?: Element | string | object): Context;

    Correct me if i'm wrong - but it could possibly be defined like this:

    function context(func: ContextFunc, scope?: Element | string | object): Context;

     

    • Like 1
  7. On 1/6/2023 at 9:16 PM, GreenSock said:

    Love hearing that. 🥳

     

    I'm totally not a TypeScript expert but I'm pretty sure this is all related to TypeScript and/or linting settings. Maybe someone else has an idea for you, but as far as I can tell it's not technically invalid but perhaps you've got to change a setting somewhere on your end?: 

    https://typescript-eslint.io/rules/no-unsafe-call/

     

    I wish I was more of a TypeScript expert and could tell you exactly what to do. 🤷‍♂️

     

    I'm no Typescript rockstar either... I know enough to get myself in trouble though haha!

     

    Basically I think it would be beneficial if we could pass in an optional object type declarations to the gsap.context() method, telling the context typescript method that it can expect a custom property.  Something like this.. (and this is very rough and unpolished)

     

    type GSAPHandlers = {
      newTab: (activeTab:string, oldTab:string)=>void;
      otherMethods: (stuff:boolean)=>void;
      ...
    };
    
    // Pass in expected types
    // The generic passed in would likely need to be converted to a Partial<>, or else Typescript would complain if the additional method is not present when the function executes.
    const ctx = gsap.context<GSAPHandlers>(() => {
    });
    
    // Add the method to the context
    ctx.add("newTab", (activeTab:string, oldTab:string)=>{...});
                                                          
                                                          
    // Use the method!!
    ctx.newTab('foo', 'bar');                                                      
                                                          
      

     

    @GreenSock are there any other ways to add methods to GSAP context? Context is incredibly helpful because it also allows for concise organization of greensock timelines. I know there are other ways of organizing timelines into methods like this - but the x-factor with context is the access to the revert() method which is brilliant!

  8. Hi there!

     

    I'm really in love with the new GSAP Context! It's really cool with working with React!

     

    When i add timelines with `context.add()` I get a typescript error because the method does not exist as a property of the context object.

     

    So the question is: How do I declare a type in typescript (and GSAP) that I intend to add a specific timeline method to the GSAP context? 

     

    Here's an example:

    // Borrowing this context hook from the docs @see: https://greensock.com/react-advanced#useGsapContext
    export function useGsapContext<T extends HTMLElement = HTMLDivElement>(
      scope: RefObject<T>,
      // eslint-disable-next-line @typescript-eslint/no-empty-function
      context: gsap.ContextFunc = () => {},
    ) {
      // eslint-disable-next-line react-hooks/exhaustive-deps
      const ctx = useMemo(() => gsap.context(context, scope), [scope]);
      return ctx;
    }

     

    I see that the return types for context are:

      interface Context {
        [key: string]: any;
        selector?: Function;
        isReverted: boolean;
        conditions?: Conditions;
        queries?: object;
        add(methodName: string, func: Function, scope?: Element | string | object): Function;
        add(func: Function, scope?: Element | string | object): void;
        ignore(func: Function): void;
        kill(revert?: boolean): void;
        revert(config?: object): void;
        clear(): void;
      }

     

    Now I'm adding a method to the context like so:
     

    // Init the gsap context
    const ctx = useGsapContext(wrapperRef);
    
    
    // Adds the timeline method to the context. This useEffect runs only once after initial render
    useEffectOnce(() => {
      ctx.add("newTab", (newIdentifier: string, oldIdentifier: string) => {
    
        const { current: wrapperEl } = wrapperRef;
    
        if (!wrapperEl) return;
    
        const tl = gsap.timeline();
    
        if (oldIdentifier) {
          tl.to(`[data-tab="${oldIdentifier}"]`, {
            duration: 0.5,
            scale: 0.9,
            autoAlpha: 0,
          });
        }
    
        tl.fromTo(
          `[data-tab="${newIdentifier}"]`,
          {
            scale: 1.2,
            autoAlpha: 0,
          },
          {
            scale: 1,
            autoAlpha: 1,
          },
        );
      });
    });
    
    
    // on state update - uses the timeline we added to the context
    useUpdateEffect(() => {
    
      // Using the method added to context (this works!) But typescript complains this method doesn't exist
      if ("newTab" in ctx && typeof ctx["newTab"] === "function") {
        ctx.newTab(activeValue.active, activeValue.prev);
      }
    }, [activeValue]);

     

    So the timeline works as expected which is great... but I get the following error which is expected because typescript doesn't know there is a "newTab" property on the context object

    image.png.c1fe3497620586c85be2279502b84694.png

     

  9. Thank you this is brilliant @Carl and @GreenSock ! I never thought about tweening the z-index. I really like the approach of staggering two separate tweens. I think I have a good understanding of how it works.

     

    16 hours ago, Carl said:

    : ditch the staggers and build the timeline using a loop. this might add some more flexibility for having tweens that have callbacks that change the zIndex sorting. 

    As a general principle, when would you consider building an animation using a loop over trying to use staggering? The challenge i initially had with staggering if figuring out the sequencing was tricky to conceptualize.

  10. Thanks @Carl this is a stroke of genius!

     

    Sorry for the confusion in my description. Basically what i'm looking to do is very close to this example (in fact i already have a use-case for it). The only difference in my code example is I'm trying to overlap the incoming image on top of the outgoing image. (Gosh i can see how what i was describing was super weird and confusing)

     

    PS I really appreciate you taking the time to lend a hand... 

     

    image.thumb.png.d6ac70a171664d6e49d8fec64f190eb5.png

  11. As an exercise I've been attempting to modify this tween that creates an infinite loop of images fading in with the first image showing on initial render and then will animate out after a delay. The change I'm trying to make is I want to translate AND fade images in while overlapping the previous image.

    The sequence I'm looking for is:

    • After initial delay on first image
      • Move old image up AND out
      •  right after starting to move the new image up and in on top of the previous image
    • Rinse and repeat

     

    I'm having trouble with the timing of the stagger. I'm wondering if a label between the first and the last tween segment would help.  I'm also struggling with overlapping image 1 over image 3 when the loop starts all over again. I'm sure adding a z-index somewhere would help but I don't know where.

     

    I think I could do a lot of cool stuff if I can master this pattern. In many ways this expected behavior is very similar to @Carl 's stagger example in the "GSAP: Beyond the Basic course"  Greensock Staggers with Seamless Loops but The only difference I'm trying to merge the logic to have the first item visible on load and starts animating after a delay. Any help would be so so appreciated

     

     

    See the Pen XWEzNLQ by gortiz022 (@gortiz022) on CodePen

  12. 20 hours ago, GreenSock said:

    Happy to help. 

     

    Maybe consider joining Club GreenSock - that'd get you InertiaPlugin which could make the dragging functionality a lot more cool in the helper function. No pressure :) 

    haha i'm talking to my manager about that RIGHT NOW!

    At a minimum can I buy yall a cup of coffee or a beer somehow?

  13. @GreenSock I appreciate so much you making an exception to explain these rationales. This is really invaluable because I can leverage these strategies in the future. I of course will attribute this forum and the original horizontalLoop in my work. I really do appreciate your time.. thanks!

    • Like 1
    • Thanks 1
  14. HI! I always have to start off by saying how much I love Greensock. It changed my life (not joking).  So i've been working to adapt a typescript version of  the `horizontalLoop` method as described in the https://greensock.com/docs/v3/HelperFunctions#loop helper functions. I plan to use it in React. 

     

    I have a working version of this in a codesandboxhttps://codesandbox.io/s/gsap-marquee-loop-494fwh?file=/src/App.tsx

    Here is the marqueeLoop class: https://codesandbox.io/s/gsap-marquee-loop-494fwh?file=/src/lib/maequeeLoop.ts

     

    If i may make a humble ask.. I was hoping someone could explain to me the rationale for the logic behind the refresh() method on the window window.resize event. I would really appreciate the opportunity to learn. Here's the code:

     

      /**
       * Recalculates the the widths of the timeline, calculates the offsets
       *
       * @param deep {boolean}
       */
      refresh(deep?: boolean) {
        // Take a snapshot of the tl progress
        const progress = this.tl.progress();
        // Reset the tl to the beginning
        this.tl.progress(0, true);
        // repopulate the widths of the elements
        this.populateWidths();
        // rebuilds the timelines
        deep && this.populateTimeline();
        // populate the offsets between the timelines
        this.populateOffsets();
    
        deep && this.draggable?.[0]
           ? this.tl.time(this.times[this.curIndex], true)
           : this.tl.progress(progress, true);
      }

    So my questions are:

    1. Why do we take a snapshot of here const progress = this.tl.progress();
    2. Why do we reset the tl progress to 0? this.tl.progress(0, true); Is this intended to pause the timeline while we recalculate?
    3. What is the rationale for either using the tl.time or thetl.progress based on Draggable? I don't understand the difference between these.

     

    Likewise, in the `Draggable proxy`,

    1.  InDraggable.onPressInit, we are killing the tweens gsap,killTweensOf(self.tl). Why are we killing the tween? 
    2. Why are we also setting the proxy values here? gsap.set(self.proxy, {x:startProgress / -ratio} ) ?
    3. Also why does `Draggable` return an array?

     

        this.draggable = Draggable.create(this.proxy, {
          trigger: this.container,
          type: "x",
          onPressInit: () => {
            // Kills all the tweens actively in play
            gsap.killTweensOf(self.tl);
            // Take a snapshot of the tween progress
            startProgress = self.tl.progress();
            // Recalculate the tl spacing and width
            self.refresh();
    
            // Offset how much drag happens
            ratio = 1 / self.totalWidth;
            
            gsap.set(self.proxy, { x: startProgress / -ratio });
          },
          onDrag: function () {
            self.tl.progress(
              progressWrap(startProgress + (this.startX - this.x) * ratio),
            );
          },
          onThrowUpdate: function () {
            self.tl.progress(
              progressWrap(startProgress + (this.startX - this.x) * ratio),
            );
          },
          inertia: false,
          onRelease: syncIndex,
          onThrowComplete: syncIndex,
        });

     

    I hope it's not too much to ask - but i would love to understand how this all works. Thanks so much!

    • Thanks 1
  15. GAFF! you are totally right, that was the the ticket. Thank you for pointing it out I was basing my what i know from the "Animating Routes" example

     

    If you could spare a second @Rodrigo i was wondering if you could share insight into why we use a ternary to set the styles for the animations, ie: Does addEndListener run on both mount and unmount? 

     

    <Transition
      unmountOnExit
      in={props.show}
      timeout={1000}
      onEnter={node => TweenLite.set(node, startState)}
      addEndListener={ (node, done) => {
        TweenLite.to(node, 0.5, {
          autoAlpha: props.show ? 1 : 0,
          y: props.show ? 0 : 50,
          onComplete: done
        });
      }}
    >

    So does it work like this:

    onEnter => set styles before mount

    addEndListener (props.show is true) => entering mounting animation

    addEndListener (props.show is false) => existing unmounting animation 

     

    Thanks for the insight!

  16. Hi @ZachSaucier, Thanks for pointing out the legacy GSAP, I switched out the basics right away. And apologies about the confusing layout, it's not very good it's just for prototyping. It's not responsive at all. But on the left and right on the container there are arrows, and that triggered the image to animate. 

     

    What I'm noticing is the image transition will properly play when the new image is mounted, but it won't play the transition when the image is unmounted. I basically thought that I could control the entering image using the `onEntering` property, and use `addEndListener` to control the image existing. 

     

  17. I'm noodling around with React/TransitionGroup/GSAP and I'm attempting to make a carousel. 

     

    From an array of images, I'm setting the state to be one object. Using transition group, I want to leverage greensock to transition one image out, followed by one image in.  I'm using state to keep track of the "active" image, there is only one image in the state. So the rationale is, when this state update, the old state will transition out, and then replaced by the new node in the state. So there isn't a group of components per-se.

     

    I'm hoping that I can get Transition group to transition the old image out, while transitioning the new image in.. I've gotten some stuff to work thanks to posts from @Rodrigo. But I'm definitely stuck now. Here is the codesandboxhttps://codesandbox.io/s/carousel-gsap-transitiongroup-z9pd3?file=/src/ImgCard.js

     

    I feel like the issue has something to do with this:

        <Transition
          timeout={500}
          mountOnEnter
          unmountOnExit
          appear
          in={show}
          onEnter={(node) => TweenLite.set(node, startState)}
          addEndListener={(node, done) => {
            TweenLite.to(node, 1, {
              autoAlpha: show ? 1 : 0,
              y: show ? 0 : 50,
              onComplete: done
            });
          }}
        >
          <li>
            <img src={cardData.image} alt={cardData.name} />
          </li>
        </Transition>

     

     Any advice would be throughly appreciated!

  18. @Rodrigo You were 100% right! Yaahooooooo!!! I never would have figured that out. Storing the tween in `useRef` was the key.

     

    I didn't realize that all variables would be overwritten when the component re-renders. I suppose that in the future if we need to save variables in between renders then we should store it in `state` or `useRef`

     

    Thank you so much for your help. I believe this example with give me a solid structure for using React and GSAP. 

     

    Thanks again!

    • Like 1
  19. I've been trying to master using React and Greensock. But I'm having major issues with actually getting the animation to run. To be fair I'm new to React. To practice, I've been trying to recreate @Rodrigo's modal toggle example ( Rodrio's State Controlled Modal ) with React Hooks. 

     

    I'm successful in passing all the props to the stateless component - but no matter what I try, changing the boolean value in the `props.visible` does not actually play the timeline. I'm not sure if my issue is React, or Greensock. I've logged a series of messages to the console, and I can see that all my functions are working correctly, I can also see that my `useEffect` is firing when `props.visible` changes.

     

    https://codesandbox.io/s/quirky-bush-gjn3v?file=/src/modal-component.js

    import React, { useRef, useEffect } from "react";
    import { TimelineLite, CSSPlugin } from "gsap/all";
    
    const ModalComponent = props => {
      const modalTween = new TimelineLite({ paused: true });
      let modalWrap = useRef(null);
      let modalDialog = useRef(null);
    
      console.log("props", props);
    
      //useEffect for creating the animation and assigning it to the refs
      useEffect(() => {
        modalTween
          .to(modalWrap, 0.01, { autoAlpha: 1 })
          .to(modalDialog, 0.25, { y: 50, autoAlpha: 1 }, 0)
          .reverse();
      }, []);
    
      //on props.visible change, we are playing the animation
      //this only fires when props.visivle changes
      useEffect(() => {
        console.log("inside useEffect", props.visible);
    
        modalTween.reversed(!props.visible);
      }, [props.visible]);
    
      return (
        <>
          <div
            className="modal"
            ref={div => (modalWrap = div)}
            onClick={props.handleClose}
          >
            <div className="modal-dialog" ref={div => (modalDialog = div)}>
              <div className="modal-content">
                <div className="modal-header">
                  <h4>A Simple Modal Tween</h4>
                </div>
                <div className="modal-body">
                  <p>
                    Lorem ipsum, dolor sit amet consectetur adipisicing elit. Totam
                    velit provident sunt iusto ratione dolore veritatis deserunt
                    ullam vel doloribus.
                  </p>
                </div>
                <div className="modal-footer">
                  <button className="btn btn-secondary" onClick={props.handleClose}>
                    Close
                  </button>
                </div>
              </div>
            </div>
          </div>
        </>
      );
    };
    
    export default ModalComponent;

     

    Does anyone had any advice on how I can get this thing working? It's driving me crazy! Is there a better approach to this all together? 

    See the Pen modal-component.js by s (@s) on CodePen

  20. @Sahil hey i was wondering, since this animation is based on scroll, do you have any performance optimization recommendations? For starters, I put a throttle on the window scroll event of 150ms. So the scroll function has a slight delay to reduce throttling

     

    Sometimes i notice a lag and a delay in animation when i'm interacting with the background. I suppose i could reduce the FPS... do you have any other thoughts?

     

    Thanks so much, i really appreciate your expertise!

     

    -Gabriel

  21. @ZachSaucier Thank you so much for your help! You are 100% correct, the issue is that the beginning of the SVG needs to overlap with the end in such a way that makes it seem seamless. It's really difficult to do, and what i found from the modifiers demo was that the re-positioning of the box happened outside the overflow of the parent container --- so that's why it has a seamless effect.

     

    Can you suggest any way to achieve an infinite seamless effect? Thank you again for your help!

     

  22. I'm working on a project to infinite scroll a set of diagonal lines in an SVG. I had some massive help from @Sahil  (thank you :)) who added easing effect on mouse scroll. He also suggested that I incorporate an infinite seamless loop of these shapes using the `modifers` plugin. So i pulled what I could from  https://cdpn.io/QEdpLe

     

    I'm basically have 2 issues:

    • There's a noticeable jump between the end of one loop and the beginning of the next (See attached video: https://drive.google.com/uc?id=1GfIHd98vs85SOCRdOa45OmzsvjCFanJG). It's not quite right
    • `repeat : -1` doesn't work. I think it has to do with the proxyTimeline. When i try infinite repeat, the diagonals fly through the viewBox and it does not look good.

     

    Here's what I've figured out:

    • Based on the "ModifiersPlugin:Seamless Loop" demo, We need to force each child item to advance simultaneously toward the distance of it's parent container, in this case it would be the `viewBox`
    • To position the next item back at the beginning, we use the `modifiers` callback and modulus to fix the next diagonal to the bottom of the viewport, using the height value of the `viewBox`.
    • The "ModifiersPlugin:Seamless Loop" demo also using an overflow to hide each element moving the left position. Somehow I might need to move the transition diagonal to happen outside of the `viewBox` but i'm not sure how to do that

     

    Does anyone have suggestions on how I can make the loop more seamless?

    See the Pen PowKLVQ by gortiz022 (@gortiz022) on CodePen

×
×
  • Create New...