Rodrigo last won the day on July 26 2016

Rodrigo had the most liked content!

Rodrigo

Moderators
  • Content count

    1,579
  • Joined

  • Last visited

  • Days Won

    148

Everything posted by Rodrigo

  1. Rodrigo

    Draggable Loop with Vanilla JS and Greensock

    Mhh.. I see you're rehearsing your stand up comedy routine
  2. Rodrigo

    Draggable Loop with Vanilla JS and Greensock

    Sorry I didn't went through all the code. What's the idea here, have a horizontal stripe of images to drag/throw and snap I presume?. My knowledge of flex is quite limited (working on backend and PIXI for too long does that to you ) so I'd suggest to look how that can be done. Also consider that the element have an absolute position. Also how the progress is being calculated via the drag/throw update and being applied. I managed to get something barely useful with this css: .box { flex-grow:0; } .boxes { display: flex; position: relative; flex-direction: row; } This creates a horizontal stripe with the images, but there are some issues with the progress of the tween. Unfortunately I don't have a lot of time to go through the and get what the issue is, but my suggestion is to first get the animation to work, like this: https://codepen.io/rhernando/pen/RVLBGJ And when that's working like that go to the draggable part of it to calculate and apply the progress. Use this pen from the GreenSock collection to understand how the modifiers plugin works: https://greensock.com/modifiersPlugin Happy Tweening!!
  3. Rodrigo

    Draggable Loop with Vanilla JS and Greensock

    Hi, This code makes it work: var $overflow = document.getElementById("overflow"); var $viewport = document.getElementsByClassName("viewport"); var $wrapper = document.getElementsByClassName("wrapper"); var $boxes = document.getElementsByClassName("boxes"); var $proxy = document.getElementById("box1"); var numBoxes = 4; var boxWidth = 350; var boxHeight = 250; var imgWidth = boxWidth - 6; var imgHeight = boxHeight - 14; var viewWidth = $viewport[0].offsetWidth; var wrapWidth = numBoxes * boxWidth; var progress = 0; var xMin = 0; var xMax = 0; TweenLite.set([$wrapper, $viewport], { height: boxHeight, xPercent: -50 }); TweenLite.set($boxes, { left: -boxWidth }); var animation = TweenMax.to(".box", 1, { x: "+=" + wrapWidth, ease: Linear.easeNone, paused: true, repeat: -1, modifiers: { x: function(x, target) { x = x % wrapWidth; target.style.visibility = x - boxWidth > viewWidth ? "hidden" : "visible"; return x; } } }); Draggable.create($proxy, { type: "x", trigger: ".wrapper", throwProps: true, onDrag: updateProgress, onThrowUpdate: updateProgress, snap: { x: snapX } }); $overflow.onchange = applyOverflow; $(window).resize(resize); function snapX(x) { return Math.round(x / boxWidth) * boxWidth; } function updateProgress() { animation.progress(this.x / wrapWidth); } function resize() { console.log($viewport[0].offsetWidth); viewWidth = $viewport[0].offsetWidth; animation.render(animation.time(), false, true); } function applyOverflow() { if($overflow.checked){ TweenLite.set(".wrapper", {overflow:"visible"}); }else { TweenLite.set(".wrapper", {overflow:"hidden"}); } } Just needed to remove some jquery methods and use onchange instead of on in the checkbox. Happy Tweening!!
  4. Rodrigo

    Countdown with rolling numbers

    Hi and welcome to the GreenSock forums!! A looooooooong time ago I made this for another question in the forums. It uses the draggable tool to move the numbers but you could easily use it to get started with what you need: I made a fork of it and changed a bit to simulate what you need: Hopefully this helps. Happy Tweening!!!
  5. Hi, This seems to do what you're trying to achieve or what I understood at least. I'll go in as much detail as possible. First I got rid of almost all your code, nothing against your code, is just my unwillingness to go through a lot of code I didn't write, normally is faster to just rewrite things. I went as much as possible in the react way of doing things. So I removed the global scope timeline for the menu and added it to the component, like this: class App extends React.Component { constructor(props) { super(props); this.state = { menuExpanded: false, targetPanel: null }; // menu items this.menuItems = []; // menu tween this.menuTl = new TimelineLite({paused:true}); } componentDidMount() { this.createMenuTween(); } // bring the project to view// createMenuTween = () => { this.menuTl .staggerTo(this.menuItems, 1, { cycle: { x: ["56%","64%", "72%", "80%", "88%"] }, ease: Power2.easeInOut }) .reverse(); }; render() { return ( <div className="main"> <button onClick={this.toggleMenu} id='toggleMenu'> menu </button> <div className="projects" ref="projects"> {Data.projects.map((project, index) => { return ( <div className="project" ref={ project => this.menuItems.push(project)} key={index} id={`project-${index + 1}`} onClick={this.togglePreviewCase.bind(null, index)} > <span> {project.name} </span> </div> ); })} </div> </div> ); } } This is the basic setup to render the DOM elements. As you can see I attached two properties to the component, the menu timeline and a menu items array: // menu items this.menuItems = []; // menu tween this.menuTl = new TimelineLite({paused:true}); Also I added the menu items to the array using the ref method on each menu item: <div className="project" ref={ project => this.menuItems.push(project)} key={index} id={`project-${index + 1}`} onClick={this.togglePreviewCase.bind(null, index)} > <span> {project.name} </span> </div> The idea behind using an array is that GSAP can use them without any problems, weather is to create a stagger animation or a common animation for all the elements. Since you're using the cycle property for each element there's no need to use a stagger method, just a regular to() instance that animates all the elements at the same time. The rest is pretty much self explanatory and we've been through that in a different topic. The main idea, as mentioned in my previous post, was adding a property in the state to track if the menu is expanded and if there's a menu panel expanded as well (menuExpanded, targetPanel). Then is the toggle method, we'll go into the basic stuff of this and then get back to it: toggleMenu = e => { const { menuExpanded, targetPanel } = this.state; // if a panel is expanded, collapse any panel and then reverse // the menu timeline if ( targetPanel !== null ) { return TweenLite.to( this.menuItems.slice(0, targetPanel + 1), 1, { x: '+=50%', ease: Power2.easeInOut, onComplete: () =>{ // toggle the state property this.setState({ menuExpanded: !menuExpanded, targetPanel: null }); this.menuTl.reversed( menuExpanded ); }// on complete });// tweenlite instance } // toggle the state property this.setState({ menuExpanded: !menuExpanded }); this.menuTl.reversed( menuExpanded ); }; The basic stuff are the final two lines, basically the state property is being updated and we're using that to trigger the animation in order to keep consitency between react and the timeline reversed state. I'm using the reversed property because the timeline is paused in the constructor and when is created instead of playing it is reversed, with that we can toggle the reversed property of the timeline to toggle it's direction. Then the part of each panel animation: togglePreviewCase = (index, e) => { const { menuItems } = this; const { targetPanel } = this.state; // create two arrays with the elements that should be animated // one for the elements that will be expanded and the ones that // will be collapsed let expandArray, collapseArray; // if the current target is null means no element is expanded if ( targetPanel === null ) { // only create an array for the elements that will be expanded expandArray = menuItems.slice(0, index + 1); TweenLite.to( expandArray, 1, { x: '-=50%', ease: Power2.easeInOut }) } else if ( index < targetPanel ) { // the new target is already expanded, we have to collapse // the panels before that, there's no expand animation collapseArray = menuItems.slice(index + 1, targetPanel + 1); TweenLite.to( collapseArray, 1, { x: '+=50%', ease: Power2.easeInOut }); } else if ( index > targetPanel ) { // the new target is not expanded, we have to expand all the // elements between the previous target and the new one expandArray = menuItems.slice(targetPanel + 1, index + 1); TweenLite.to( expandArray, 1, { x: '-=50%', ease: Power2.easeInOut }); } else if ( index === targetPanel ) { // the current target element is being clicked, reverse that TweenLite.to( menuItems[index], 1, { x: '+=50%', ease: Power2.easeInOut }); // the new target index should be the previous element if the // index is bigger than 0 return this.setState({ targetPanel: targetPanel > 0 ? targetPanel - 1 : null }); } // set the current index as the target panel this.setState({ targetPanel: index }); }; The comments and the explanation for the use of arrays pretty much cover what's happening here. The idea is to keep track of the index of the element being clicked to create an array of elements that should be animated. Then depending on the index position of the target panel (where the user clicks) if those will be collapsed or expanded. Finally the other part of the toggle menu code. If the user clicks on the toggle menu button and there's a menu panel expanded, we create a tween for all the expanded panels and when that's complete, reverse the menu tween and set the target panel to null using an onComplete callback. Happy Tweening!!
  6. Hi @tomsah, Unfortunately I got caught up in a project now and haven't been able to follow up on your slide menu project. I'll try to whip something tomorrow to try to help you. For the moment what you should do is add to your menu component a property in the state in order to know if any panel is expanded or not and if the menu is being collapsed, ie, the user wants to hide the menu. The idea is this: when the menu button is clicked check that state and if a panel is open reverse the panels animation, then using a onReverseComplete instance check if the idea is to hide the menu or just collapse the panels. If the idea is to hide the menu then reverse the menu animation, if not do nothing. Happy Tweening!!!
  7. Rodrigo

    Animating a card game in React with GSAP

    Yep, the principle remains the framework changes, that's all. Is just about the "right moment" to update the DOM in cases like this. How to do that is up to whatever framework is being used.
  8. Rodrigo

    Animating a card game in React with GSAP

    @OSUblake has a great point regarding DOM reconciliation. Keep in mind that in react DOM manipulation is normally considered a no-no, anti-pattern, no-op and all those fancy terms that exists to politely tell you: "DON'T DO THAT!!!" I'd try to avoid changing the elements from one parent to another, but it this is a must-do situation using an onComplete callback (you see how shamelessly I'm re-using Blake's ideas?? ) to update the state, perhaps the index position of each child to match the order in which each parent is added to the DOM, to place them in the corresponding parent, would be a good way to do it. Perhaps using redux or mobx to store everything and access throughout the app. Happy Tweening!!
  9. Rodrigo

    Animated Page Transitions in React using GSAP

    Hi, Honestly going through all the motions to teach you how to work with redux is out of my capacities in terms of the required time and where exactly you are right now in terms of your coding knowledge. Redux basically let's you rescind, as much as possible, of using component's states in order to keep reference and track of different properties that might change during the app's life cycle. Before redux (and also is worth mentioning it's predecessors Flux and ReFlux) and mobx, a React app had references in the states of different components. The issue was that when you needed to access the state of a sibling component all hell was unleashed, because you needed a method from the parent component to be called in Child A in order to pass that property to the parent and then to Child B. Of course the deeper a component was in the app's tree, the bigger the unleashed hell. It was coding nightmare. Enter redux. Redux is just an object (store) with all the properties you want to keep track and it's accessible via the connect high order function from react redux in all the components of the app that you need. The point is that in order to add and update the property in the store you need actions and actions creators. Those are added to the component's props so they can be accessed and used easily. I strongly recommend you to go through the egghead course (I believe is free) and redux's official docs in order to get a better grasp of how to work with redux and react, is really not that complex. Regarding the error is fairly simple. You're trying to render something that comes as undefined. Similar to this: class Route extends Component { render(){ return <div> // some component here, most likely a function is returning undefined </div>; } } In the comment I mention a function, but also could be a ternary operator or some iteration of a map array helper. Without the entire code and the data structure is impossible to tell. A common scenario is that, since the render method can be called more than once as the state and props are updated (mostly during async code) an array could be empty or something else could be coming as undefined, therefore passing that to the render method which ultimately returns that error. You can use chrome's debugger to put a break point in that render method in order to check the call stack and the scope or use console.log() in the render method, before the return statement of course: class Route extends Component { render(){ console.log(/*check props and state here in order to track what could be returning as undefined*/); return <div> // some component here, most likely a function is returning undefined </div>; } } Happy tweening!!!
  10. Rodrigo

    Animated Page Transitions in React using GSAP

    Ahh, sorry missing closing parenthesis there. It should be: <button onClick={this.buttonClickHandler.bind(null, '/')}>Home</button> By the actions path I mean, that since you're using redux you have actions and action creators. Normally in small projects you put all your actions and action creators in one file and export each one as a named export. In larger projects people like to ether put each component, it's own reducer and actions in separate folders, or create a components folder, an actions folders and a reducers folder to place each components actions and reducers and export them via an index.js file. I'm assuming that you have some degree of experience working with redux and react-redux. If you're not very familiar with it you can check this resources: https://redux.js.org/faq/code-structure https://medium.com/front-end-hacking/the-three-pigs-how-to-structure-react-redux-application-67f5e3c68392 https://marmelab.com/blog/2015/12/17/react-directory-structure.html https://egghead.io/courses/getting-started-with-redux The egghead course is by Dan Abramov himself (the creator of redux). He goes kind of fast but the concepts are very clear though. Happy Tweening!!!
  11. Rodrigo

    Animated Page Transitions in React using GSAP

    Hi, Well since you're using react router and you have an overlay covering the entire content of the site perhaps using transition group with GSAP might not be necessary. The main question is this: Is the color overlay transparent in any degree or solid color?. Depending on the possible answers, these are the scenarios. Case: Solid Color Overlay The color overlay hides all the content, so there's actually no need to use transition group in order to animate each route's component's mount/unmount. Just use GSAP to animate the color overlay and using an onComplete callback, use history (a dependency used by react router and passed as a property to every component in a <Route /> wrapper) to push the next route. In this scenario the only catch is that the overlay component has to reside inside of a route (you can put it in the root route "/" or wrap it in a <Route /> component), which shouldn't be too complicated since I'm assuming it has a fixed position and a z-index that puts it on top of everything. It could be like this: Overlay component, as you can see the idea is to render it in every route, that's why there's no exact attribute in the Route component. Also the Route component is passing it's own history property to the overlay component class App extends Component { render(){ reuturn <div> <Route path="/" render={ props => <ColorOverlay history={props.history} /> } /> // the rest of the routes here </div>; } } Color overlay component import { connect } from "react-redux"; import { toggleOverlay } from "./actions"; // depending on your actions path class ColorOverlay extends Component { constructor(){ super(); this.overlayTween = null; this.overlayElement = null; this.overlayTweenComplete = this.overlayTweenComplete.bind(this); } overlayTweenComplete(){ const { history, targetRoute, toggleOverlay } = this.props; // here's all the magic // at this point the overlay is covering all the content // so we can change the route using history passed in the props history.push(targetRoute); // now the route has changed and we can reverse the animation // revealing the new rendered component toggleOverlay(false); } componentDidMount(){ this.overlayTween(this.overlayElement, 1, {/*config here*/, onComplete:this.overlayTweenComplete, paused:true}); } // we're updating the visibility via redux, so we look for // updates in the props componentDidUpdate(prevProps){ const { overlayVisible } = this.props; // check if the visible prop is the one that changed if ( prevProps.overlayVisible !== overlayVisible ) { overlayVisible ? this.overlayTween.play() : this.overlayTween.reverse(); } } render(){ return <div>/*your overlay html here*/</div>; } } const mapStateToProps = state => ({ overlayVisible: state.overlayVisible, targetRoute: state.targetRoute }); const overlayActions = { toggleOverlay }; export default connect( mapStateToProps, overlayActions )( ColorOverlay ); In the snippet I'm making some assumptions that is worth clearing up. First I'm assuming that since you want an overlay over the content using react router's <Link> or <NavLink> is not necessary for every button that should trigger that functionality. If you need a button, link or menu item to not use this overlay transition, then use the normal way of doing that with react router. Second you should have an action creator to toggle the overlay visible property in the store and the target route. The overlay visible is just a boolean and the target route is the string of the route to push into react router, so it should be in this format "/about", "/products", etc. So instead of using a link or navlink with the to="" attribute to change the route, just use an onClick event to update the target route first and then play the overlay animation: import { connect } from "react-redux"; import { toggleOverlay, updateTargetRoute } from "./actions"; // depending on your actions path class MainMenu extends Component { constructor(){ super(); this.buttonClickHandler = this.buttonClickHandler.bind(this); } buttonClickHandler(target){ const { toggleOverlay, updateTargetRoute } = this.props; // update the route in redux updateTargetRoute(target); // now that the target is updated toggle the visible state toggleOverlay(true); } render(){ return <div> <button onClick={this.buttonClickHandler.bind(null, '/')}>Home</button> <button onClick={this.buttonClickHandler.bind(null, '/about')}>About Us</button> <button onClick={this.buttonClickHandler.bind(null, '/products')}>Products</button> </div>; } } const menuActions = { toggleOverlay, updateTargetRoute }; export default connect( null, menuActions )( MainMenu ); (the syntax highlighter is messing up the color, but the code should work). The reason to control both the play state of the tween and route of the app via redux is that it could be a slight delay while setting the target route and also the fact that if you need the value elsewhere is already in the store. Something like this should be able to animate the overlay before changing the routes and then remove the overlay after. Case: Transparent Color Overlay In this case the content under the overlay will be visible so my guess is that you might need to actually animate one route being unmounted and the other route being mounted. If that's the case take a look at this sample (codepen doesn't have the features to import and mimic real work flows so I use codesandbox for more complex React & Gsap samples): https://codesandbox.io/s/mqy3mmznn Happy Tweening!!!
  12. Rodrigo

    multiple slide panel GSAP & React

    Hi, Does this comes close to what you're trying to do?
  13. Rodrigo

    multiple slide panel GSAP & React

    Hi, There are two things that I can spot in your code. First, everytime you click on a menu project you're adding instances to a timeline, therefore that instance gets longer and longer. Second you're not keeping track of the element that's currently expanded (if any). Normally when working with this type of projects, is a recommended pattern to keep track of the active element so you can unset that element as active when you either toggle that state on the element or select a new one as active. In your case if you click on project 1 that should be selected as active and start the animation. Then if you click on that element again by being the active element you can reverse the tween and unset it as the active element. Same thing if you click on another element: unset the active element, reverse the tween, set the another element as active and finally start that tween. Unfortunately you caught me with little time now to create a sample or play with your codepen sample. Later I'll be able to circle back to it. In the mean time use the component's state to keep track of the current active element (you're already passing a unique id in a method so you can use that) and the element being clicked. If there's an active element and the id's are the same then just reverse the animation, clear it ( https://greensock.com/docs/TimelineLite/clear() )and set the active property to null. If there's an active element and the id's don't match, set the active to null, reverse the timeline, clear it, set the new active, create the timeline and restart it. Happy Tweening!!!
  14. Rodrigo

    Importing TweenMax into React component

    Hi Paul, I don't know how you're bundling your code, so I'll assume babel and webpack or perhaps create react app. If you're using the HTML plugin for wepack, then webpack is placing the scripts tags in your index file for you, above the </body> closing tag, so you could add the CDN links between the <div> where the app is being placed and the closing body tag. <html> <head></head> <body> <!-- HERE REACT RENDERS THE APP --> <div id="app"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.4/TweenMax.min.js"></script> <script src="js/gsap/plugins/MorphSVGPlugin.min.js"></script> <!-- WEBPACK ADD THE SCRIPTS HERE --> </body> </html> If you're not using the HTML plugin then just add them manually before the tag where you're putting the bundled code: <html> <head></head> <body> <!-- HERE REACT RENDERS THE APP --> <div id="app"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.4/TweenMax.min.js"></script> <script src="js/gsap/plugins/MorphSVGPlugin.min.js"></script> <script src="js/app.js"></script> </body> </html> If you're using create react app, unfortunately I'm not at all familiar with it, I prefer to create my own startup code, but perhaps you can use this links: https://github.com/facebook/create-react-app/issues/3007 https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#using-the-public-folder Finally if you could provide more info about it, it'd be very helpful, such as what set up you're using and that particular part of your index.html file. Happy Tweening!!
  15. Rodrigo

    Animate out - React

    Yikes!!! there was a missing part in the code. This: paintOutTimerCreator(){ if ( this.painOutTimer ) { this.painOutTimer.restart(true); } else { this.painOutTimer( 4, () => this.paintOutTween.play(0) ) } } Should be this: paintOutTimerCreator(){ if ( this.painOutTimer ) { this.painOutTimer.restart(true); } else { this.painOutTimer = TweenLite.delayedCall( 4, () => this.paintOutTween.play(0) ); } } I forgot to add the instantiation of the delayed call, sorry about that. Happy Tweening!!
  16. Rodrigo

    Simple React/GSAP animation

    Hi, There were some issues with your code. In react inline styles are not applied like in regular HTML, take a look at the official React docs: https://reactjs.org/docs/dom-elements.html#style In that case is better to move the styles to the css file (SASS in the codepen). Then you're creating the TweenMax instance before the react class is even created, so when GSAP is instantiated there's no DOM element with the ID you're pointing to in the GSAP instance, so that throws an error as well. This code seems to do what you're expecting: class SvgReddit extends Component { constructor(){ super(); this.startPath = null; this.endPath = null; this.svgTween = null; } componentDidMount(){ this.svgTween = TweenMax.to(this.startPath, 1, { morphSVG:{shape: this.endPath, shapeIndex: 12}, fill: "#ff0000",ease:Bounce.easeOut }); } render(){ return ( <div> <svg id="Layer_6" data-name="Layer 6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 136.66 171.86"> <defs></defs> <title>redditlogo</title> <path ref={path => this.startPath = path} id="start" className="cls-1" d="..." /> <path ref={path => this.endPath = path} id="end" className="cls-2" d="..." /> </svg> <h1 onClick={ () => this.svgTween.play() }>Devil</h1> <h3>or</h3> <h1 onClick={ () => this.svgTween.reverse() }>Angel</h1> </div> ); } } Also this code should be compatible with the latest version of react and respects the deprecation of the strings refs by using the callback to assign the DOM nodes in a declarative way. Happy tweening!!
  17. Rodrigo

    Animate out - React

    Is the "paint out" animation happening after some time or an event handler, like click, hover, etc.? This is the most important part in order to choose the best course of action. I'm going to guess based on the code you added in the first post. If the animation happens some time after the "paint in" animation is completed then GSAP has you covered. All you have to do is create a delayedCall instance and execute a function to start the other animation. You can create a method in your react class in order to simplify the whole process. Something like this: class MyComponent extends Component { constructor(){ super(); // this method will create and restart the delayed call this.paintOutTimerCreator = this.paintOutTimerCreator.bind(this); this.paintInTween = null; this.paintOutTween = null; this.paintElement = null; // this is the DOM element being animated this.painOutTimer = null; } componentDidMount(){ const { paintOutTimerCreator } = this; this.paintInTween = TweenLite.to(this.paintElement, 1, { width: "110%", zIndex: "900", ease: Expo.easeInOut, delay: 2.1, onComplete: paintOutTimerCreator }); this.paintOutTween = TweenLite.to(this.paintElement, 1, { width: "0", right: "0", left: "initial", ease: Expo.easeInOut, paused: true}); } paintOutTimerCreator(){ if ( this.painOutTimer ) { this.painOutTimer.restart(true); } else { this.painOutTimer = TweenLite.delayedCall( 4, () => this.paintOutTween.play(0) ); } } // I'll assume that this part of your code is working render(){ return(...); } } As I said this code is assuming a lot of things, so it would be nice to have a better grasp of what you need. As always if you could provide a reduced sample in codepen, it would be very helpful. Happy Tweening!!!
  18. Rodrigo

    Animate out - React

    Hi, The thing here is that componentWillUnmount is not designed for that actually. From the React docs: Basically the lifecycle methods are there only as hooks or event emitters, soto speak, pretty much like GSAP's own callback system. The onComplete, for example instance is not going to wait for something else to happen before executing the provided callback. Like that componenWillUnmount is not going to wait for some animation or anything else that might take more time than it's own execution to complete and remove the component. So that's not a good place for any of that type of code. In fact this particular hook is not as used as others lifecycle hooks. A good thing would be to know exactly what you're trying to do.For your code I assume that you're still working on the loading overlay from the other day. If you're trying to simply animate out the component without removing it from the DOM then I'd recommend a simpler approach, such as a regular event handler like a button or something similar. This sample animates a DOM element with a delay (wait two secs) and then with the button you can toggle the animation Another option is a callback in the GSAP instance itself. As I mentioned it'll be best to know exactly what you're trying to achieve and take it from there. Happy Tweening!!!
  19. Rodrigo

    Simple React/GSAP animation

    Hi and welcome to the GreenSock forums!!! This always seems more daunting that it really is. All you have to do is create a reference to the element you want to animate, create a GSAP instance for the animation in the componentDidMount event and then play the animation when you need it. Here's a simple codepen that does exactly that: Now if you check the code you'll see two references set to null in the constructor method, the loader wrap and the reference that later will be used to hold the GSAP instance. These are useful if you later want to use them again so you can set the position of the loader wrap to make it visible again and then you want to play the animation to remove it. The loader wrap reference is set in this code: <div className="loader" ref={div => this.loaderWrap = div} > THIS IS THE LOADER WRAP, PUT YOUR SPINNER HERE!!! </div> Here the ref property is passed a callback that takes the React node being mounted as a parameter and sets it as the reference called loaderWrap. In this case the node is the actual div that will be rendered in the DOM, which is what GSAP uses to animate. Finally the GSAP instance is created in the componentDidMount method to ensure that the DOM nodes are actually mounted and rendered by that time. Happy Tweening!!
  20. Rodrigo

    GSAP + React.js The quest for best practices

    Hey Beau, There's absolutely nothing wrong with the React part of your code, that's working as expected. You're generating the refs for each DOM node and using them in the componentDidMount method. The issue has to do with the GSAP part. Right now you're creating an instance in the timeline for each SVG element but you're not passing a position parameter, so GSAP is putting each instance at the end of the timeline, therefore each element is animated in a sequential way. In order to achieve what's happening in the second codepen you can use two approaches: Option One Add every single instance at the start of the timeline using the position parameter at 0 and add random delays to each one in order to create the overlapping. const tl = new TimelineMax(); waves.forEach( item => { tl.to( item, duration, {opacity: random(), delay: randomDelay()}, 0 ); }); That zero at the end tells GSAP to add the instance at the very start of the timeline, ie, time zero. The random functions are for creating random opacity and delay numbers. Option Two Create a random position parameter for each instance based on the duration of the timeline when that particular instance is being added to the timeline or by forcing the timeline to have a specific duration using the (you guessed... the duration method). Now this might be a little more complicated because you'll have to access the duration of the timeline or set a fix duration for it. Check this from the API: https://greensock.com/docs/TimelineMax/duration() So I'd recommend using option one if you can. Finally check this from professor @Carl in order to get a great grasp of the position parameter. https://greensock.com/position-parameter Happy Tweening!!
  21. Rodrigo

    GSAP + REACT, play/reverse animation onClick issue

    Hey Tomsah, getting back to it now. Not at all, your GSAP instance will be available to every method in the global scope of your module or file. Some people like to attach the GSAP instance to the react component like this: import React, { Component } from "react"; import { TimelineLite } from "gsap"; class MyApp extends Component { constructor(){ super(); this.projectTl = new TimelineLite({paused: true, reversed: true}); } render(){ return(...); } } This allows to pass some of the components props or state properties to GSAP's callback system and stuff like that or if you need to reference a specific method from your component in a callback, or perhaps update the state using onComplete, onRevereseComplete or something like that. I don't see anything wrong with using stagger to do that. Keep in mind though that the stagger methods return a series of GSAP instances with different position parameter applied to them, based on the stagger time you provide. I recommend using React transition group only when the animation is related to the component being mounted/unmounted. For example if you want the element to be mounted before the animation starts and unmounted after the animation is complete, or if you're looking for some finer detail control over the lifecycle methods and associate them to the animation, you can use transition group. Other wise my advice is to stick with GSAP and use it in he usual way with react. Transition group might be a little too much if you're just starting with react and GSAP. If the animation only affects the elements in that component, then there's no reason to have it in a different component. If you want to target some specific callback from a parent component in the timeline, then you can pass it as a property. Finally if you need to access the GSAP instance in different components in your app, perhaps you should think about using redux, mobx or some other store solution in order to access the timeline in different components. Now I have never used redux to store a GSAP instance, but I can't think about a reason against though. Happy Tweening!!!
  22. Rodrigo

    GSAP + REACT, play/reverse animation onClick issue

    Hi Unfortunately I ran out of time to get a proper answer, I'll get back to it later tonight. In the mean time take a look at this: https://reactjs.org/docs/refs-and-the-dom.html This: And this: Hopefully this is enough for now. Happy Tweening!!
  23. Rodrigo

    GSAP + React.js The quest for best practices

    Hey Beau, I understand the frustration. The reality of things is that React works in a declarative way and the discourage to use string refs and find dom node has mostly to do with some specific issues and some excessively paternal approach by the React team. The paternal issue comes from the fact that the deprecation of string refs has to do with some edge cases and because of a few things that could go wrong the entire API is changed for everyone. Take GSAP for example, Jack does His best to ensure that GSAP serves as many scenarios as possible but never compromises performance, file size nor legacy support, and when those changes do come, believe me there is a lot of questions and samples going around before the decision is made. But that's how GSAP works and perhaps we've been a little spoiled by that, but that's how serious Jack and Carl are about how this works. IMHO there's nothing wrong with using string refs and fond dome node if you know what you're doing. At some point developers have to take responsibility for the code they write and not expect that the frameworks make all kind of loops and hoops to ensure that every blunder is supported. As I mentioned in another post yesterday this is some very specific issue, because you have to take in consideration the lifecycle of your app and it's components in order to make the best decision. There are some cases that you can safely reference the elements using the classic get element by id if, when you create the variable you're sure that the DOM node will be rendered in the app. In other cases if you're not sure is better to use refs. This simple codepen goes into the detail of how refs are accepted today: In the console you'll find an array with all the rendered elements that can be used with GSAP. This is the so called "React way" for referencing DOM nodes. Now the pain comes when you're using a third party component that doesn't expose the DOM node, like this case: import ThirdParty from "third-party"; const elements = ["label-1", "label-2", "label-3", "label-4"]; class MyApp extends Component{ constructor(){ super(); this.elements = []; } render(){ return <div> { elements.map( e => { <ThirdParty key={e} ref={ node => this.elements.push(node) } /> }) } </div>; } } In this case the array will have a collection of the four React components being rendered and not the nodes being added to the DOM. In this case find dom node comes in handy because it allows to reference the actual element in the screen. If the plan is indeed remove find dome node from ReactDOM then things will get more complicated, because developers will have to bake their own version of this component in order to expose the ref to the DOM node or the developers of third party components will have to come up with some way to find those references. One of the simple ways to do this could be for components to accept an id property in order to later reference that and get access to the DOM node. Finally don't get discouraged from using GSAP with React, there are ways to use it and the more is being used, the React team will become aware that to create complex animations and rich environments GSAP is the way to go and they'll have to come with some way to actually gain access to the dom elements. Right now we have to keep using this type of approaches with the hope that things will change and become simpler in the future. Also keep in mind that the article you mention was written when React was in version 15 and string refs and find dom node were not deprecated and that wasn't a long time ago, unfortunately this world moves way to fast and doesn't wait for anyone... Happy Tweening!!!
  24. Rodrigo

    GSAP + REACT, play/reverse animation onClick issue

    Hi Tomsah and welcome to the GreenSock forums. The issue here lies in the fact that you're creating a new instance of the timeline each time you click on the toggle button. The real kicker is that, since the timeline is on a global scope, everytime the slideMenu method is called, GSAP says: "ok, so your calling a method that adds this instances to the timeline and since you're not specifying where you want them, I'll add them to the end". So everytime you click on the toggle button the timeline gets longer and longer and longer and longer and... you see where this is going right?? React has components lifecycle methods to help with this. You can create the timeline just once in the componentDidMount method and the timeline will be created and you can be sure that your elements are in the DOM when it happens. Just remove the call to slide menu in the toggle button click handler and add the component did mount call in your component: // add component did mount to the class componentDidMount(){ this.slideMenu(); } // remove the slide menu call in the toggle click handler toggleMenu = (e) => { e.preventDefault(); menuTl.reversed() ? menuTl.play() : menuTl.reverse(); this.setState({ menuActive: !this.state.menuActive }) } On a related note, the more purist React developers will consider using selectors like this a big NO-NO: var projects = document.getElementsByClassName('project'); React is all about doing things in a declarative way, so for getting dom nodes the so called "React way" is to use refs which in some cases can be really painful, specially if you're working with components that you didn't wrote, which makes generating and accessing the refs a bit tricky. I've been using React with GSAP for some time now and this is just my opinion, but in the case of elements that are permanently rendered on the DOM and are always there, there's no issue in doing it like you did. If that's no the case, the one thing you have to keep in mind is if a state or props change could un-mount the component being animated or elements contained in that component that could be animated, because the component could be unmounted and the GSAP instance could be still be active and you'll get an error. In that case you can use componentWillUnmount to kill the animation and prevent an error. Happy tweening!!
  25. Rodrigo

    GSAP - React staggerFrom on props.children

    Hi and welcome to the GreenSock forums. The way I normally attack this situation is using React's lifecycle methods, most specific the componentDidMount method. With that you know for sure that all the child have been rendered before making the animation. This is a sample that uses a code similar to that. Unfortunately I don't have any samples around with the specific behavior you need but hopefully you'll get the gist of it: Basically when creating each child (which in this case come as a hardcoded array, but getting the data via props shouldn't be much different) you give it a unique ref attribute to each which then you can use to loop and create the timeline or the stagger instance if you like (to create a stagger instance just reach to the array in the props and that should do it), then create the timeline and finally play it. I'll try to whip something during the afternoon that matches what you need. This is another sample but it uses a different approach, since it uses Transition Group since the elements are mounted and unmounted depending on the user interaction. It doesn't use a timeline or stagger, just a simple delay. This relies in the fact that the code is executed very fast (for larger apps you could consider server side rendering or perhaps another approach, once all the elements are rendered, perhaps pagination to render a small chunk of elements) and that the components are rendered in the sequence they exist in the app's data (again hardcoded in an array). But considering the fact that in a real app React will render all those child components at once so we can rely on that delay to create the stagger effect. Pay no attention to the code that's commented out, that's there in case someone needs to use CSS Transitions. The only catch is that the duration const used in the <Transition> component has to be the same that the one used in the GSAP instance, otherwise the component mount/unmount will be out of sync with the animation. https://codesandbox.io/s/7Lp1Q8WBA Hope this helps. Happy Tweening!!!