Jump to content


  • Posts

  • Joined

  • Last visited

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

gabriel.ortiz's Achievements

  1. 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;
  2. 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!
  3. 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
  4. 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. 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.
  5. 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...
  6. 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
  7. 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?
  8. @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!
  9. 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 codesandbox: https://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: Why do we take a snapshot of here const progress = this.tl.progress(); Why do we reset the tl progress to 0? this.tl.progress(0, true); Is this intended to pause the timeline while we recalculate? 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`, InDraggable.onPressInit, we are killing the tweens gsap,killTweensOf(self.tl). Why are we killing the tween? Why are we also setting the proxy values here? gsap.set(self.proxy, {x:startProgress / -ratio} ) ? 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!
  10. 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!
  11. 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.
  12. 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 codesandbox: https://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!
  13. @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!
  14. Hi @Rodrigo Thank you for responding! here's a link of Codesandbox.. i hope it's editable: https://codesandbox.io/live/LEpRg I am using `useRef` to grab the element, but I might not be using it the right away. I've edited the code above to show the complete component. Sorry for the confusion.
  15. 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?