Jump to content

Rodrigo last won the day on March 17 2019

Rodrigo had the most liked content!


  • Posts

  • Joined

  • Last visited

  • Days Won


Everything posted by Rodrigo

  1. Fun!!! However I love bees, granted here in Chile we don't have those nasty killer ones, in fact you have to pester a bee quite a bit in order to get a sting, but still. Perhaps some trouble-making-looking wasps?
  2. Hi and welcome to the GreenSock forums. When starting up, using GSAP in a React app consist mostly in getting the DOM elements available for GSAP. The approach that React gives is using the ref callback to keep an instance of the DOM element available: https://reactjs.org/docs/refs-and-the-dom.html Here's a very simple example of that way to access an arrow and move it depending on the click target: The key is in this code: <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/33073/arrow.png" className="arrow" ref={ e => this.arrow = e } /> The ref to the DOM element is stored in the component's constructor so it can be accessed to any method binded to the class instance, that's why we can use it in the click event handler of each box. Happy tweening!!!
  3. You're welcome. Keep in mind that using React Router will show only one image at the time unless you mimic this code: https://reacttraining.com/react-router/web/example/modal-gallery At that point you'll have to update all the images positions and start mounting and unmounting components before starting the animation and it could get a little messy. My advice would be to create the gallery similar to the demo I made and wrap that in a <Route> tag, like that you'll be able to check the location object from the API and update the image based on that. That will also allow users to bookmark the URL of a specific image. Happy Tweening!!
  4. Hi, This is the simplest and laziest possible solution for this. Is missing the click handler for the image cards, but the main idea is to show how to animate this in React using GSAP and to confirm what I said in the previous response that neither Transition Group or React Router are needed for that particular part of an app: https://stackblitz.com/edit/gsap-forums-react-wyz64a?file=index.js Here's a full screen view which is better than the smaller split in the editor view: https://gsap-forums-react-wyz64a.stackblitz.io/ Now keep in mind that I'm using flex in order to reduce the complexity of the code (again, simple and lazy). But this could be done using elements with an absolute position, bottom and right values of 0, run a loop for the initial positions and use some similar logic to advance the elements. Also I didn't include the handler if the user clicks on an image card, but that's basically playing with the index of the element being clicked (that's why every element has a data-target attribute with the index position of it). Hopefully this helps to clarify how to achieve this. Happy Tweening!!!
  5. Hi, I'm actually trying to get a demo similar to the GIF you posted. In the mean time there are certain things that don't add up in your question, that in any case had to do with how the question is formulated, is just some React concepts in it that don't match the animation in the GIF file. First, IMHO, I see no need to use either Transition Group or React Router for this demo. Keep in mind that router works with the url in the browser's address bar and a bunch of objects and methods in order to change the url based on user interaction and keep a history object to go back and forth just like regular navigation. Router normally mounts and unmounts a specific component as a result of the url change. I don't see that happening in the GIF. Second, Transition Group is normally used for CSS animations (forbidden language in this lands ) and create animations in a declarative way that's very similar to the way you actually write a React app/component. When used with GSAP, transition group normally is good only for animations that result in the animated component being mounted before the animation starts (when you animate a component in) and unmounted after the animation is completed (when you animate a component out). This is an extremely simple example of using transition group with GSAP: https://codesandbox.io/s/yvye9nnw As you can see when the animation hasn't started the component is unmounted, you click the button and the component is mounted before being animated, then the in animation (to put a name to it) is completed. Then when you click the button again the out animation starts and when is completed, the component is unmounted. I think this approach is far more simple and best suited for what you're trying to achieve, since the images are always present in the DOM so there's no need to mount/unmount them. It basically uses the ref callback to get access to the DOM node and then use that in a GSAP instance, at least that's the approach for the demo I'll create a little later: I hope that in the mean time, this helps to clarify some concepts and give you a better concept of how to work with GSAP and React. Happy Tweening!!
  6. You're setting as the target of the Draggable instance the element with the id="box". It should be the patch element. var line = document.querySelector("#line"); var patch = document.getElementById("patch_218_454"); var x1 = line.x1.baseVal.value || 0; var y1 = line.y1.baseVal.value || 0; var x2 = line.x2.baseVal.value || 0; var y2 = line.y2.baseVal.value || 0; var C = x2 - x1; var D = y2 - y1; var distSq = C * C + D * D; /* TweenLite.set(patch, { xPercent: -50, yPercent: -50 }); */ var draggable = new Draggable(patch, { allowContextMenu: true, liveSnap: { points: getClosestPoint } }); /* TweenLite.set(patch, getClosestPoint({ x: draggable.x, y: draggable.y })); */ draggable.update(true) That allows the patch to be dragged, but it throws the live snapping far from the line. I'm not sure why is that, perhaps the fact that you have an <svg> tag inside another <svg> tag. The code is making the calculations in the context of the outer SVG tag and then those are being applied in the inner SVG tag, which causes that issue. I haven't seen something like that before; any particular reason for that? Also you don't need throwProps for this, so you can remove it from the Draggable instance. Happy Tweening!! PS: Well... basically what Blake said , He beat me to it
  7. @OSUblake How geometrically elegant, a true heir of Pythagoras
  8. Hi, I had to deal with something like this about a month ago. First you forgot to include jQuery in the pen's resources. Then in order to enforce the right bounds, you need to find the dimensions of the path you want to use as track using getBBox() and use those in the Draggable instance, but for that you have to line up the patch and the track correctly, otherwise it looks a bit funny: var bounds = document.getElementById("line2").getBBox(); var R = Draggable.create( "#patch_218_454", { type: "x,y", bounds:{ minX: 0, maxX:bounds.width, minY:-5, maxY:bounds.height - 5 }, onDrag: function (e) { console.log(e.offsetX); console.log(R); } }); Then in order to limit the patch movement to the path you want to use, don't use the Draggable instance on the patch but in a dummy object, then simply trigger the instance using the patch and update an independent GSAP instance using the onDrag callback. This sample from one of our heroes @Diaco shows how to do that: Here in the docs, more specifically in the config object properties you'll find the trigger property. https://greensock.com/docs/Utilities/Draggable Hope this helps. Happy Tweening!!!
  9. Ever heard of conditioning?? this guy has all the answers https://en.wikipedia.org/wiki/Ivan_Pavlov EDIT: I didn't saw the Bugs Bunny shape there!!!! You deserve like 500 likes for that!!!
  10. Hi Gilbert and welcome to the GreenSock forums. First try to avoid string refs, they are deprecated since React 15.x. Use the ref callback instead: https://reactjs.org/docs/refs-and-the-dom.html In the constructor create an empty array to add the elements as you call the render title method: // this is inside the component's class constructor(){ super(); // just an empty array, we'll use the ref callback to add the DOM elements this.titles = []; // the rest of your constructor code here } Then, IMHO I don't see the need for two methods to render each title, you can run all your logic in just one and use the ref callback to add the element to the array, like that you'll have a predictable way to add your elements to the timeline. renderTitle(e) { const ATitle = titles[e.component]; return( <div className="box" key={e.component} ref={e => this.titles.push(e)} > <ATitle size={e.size} text1={e.title} text2={e.subTitle} open="false" style={{ position: "absolute", x: " +e.x+" , y: " +e.x+", "fill": "orange" }} onComplete={this.handleComplete} /> </div> ) } The key is the ref callback which has the DOM node as the argument, then the DOM node is added to the titles array. Then you can access the titles array and create your timeline: componentDidMount(){ const { tl, titles } = this; titles.forEach( (e,i) => { tl.to( this.refs[e.component], 1, {x: e.size, y: e.size/3 , scale: 0.5, opacity: 0.5}, .5 * i ); }); tl.reverse(); } This is a better and more actual approach to write your app. Now to the sequencing part of your app. I'm a bit puzzled, because react-titles does creates either a GSAP instance or uses react-motion for the animations. If you want to control those animations, you'll have to fork the repo and create an API that exposes some way to do what you need, because react-titles starts as soon as the component is mounted. For the initial position, what I saw in the repo is that the code uses fromTo instances, which create an immediate render, which sets the initial position of the elements being animated, so no matter what you do react-title will enforce those initial values passed in the props of the component's instance. Such is the nature of re-usable React components, you have to work with whatever the creator of the component is giving you. Also keep in mind that the open prop passed to the component is a boolean and you're passing a string "false". Try using a boolean instead: <ATitle size={e.size} text1={e.title} text2={e.subTitle} open={false} style={{ position: "absolute", x: " +e.x+" , y: " +e.x+", "fill": "orange" }} onComplete={this.handleComplete} /> Another option is hack into the code of react-titles and create a way to control the animations. Honestly if I was you, I'd take the animations being created by react-titles and bake my own solution for the particular needs of the project. The GSAP code in react-titles is good and there are no issues there and unfortunately we can't spend a lot of time figuring out how GSAP based tools work and how they're used. I saw that you started an issue in the repo, hopefully the creator(s) of the tool can help you more than I can and they can add a way to keep the tweens paused when mounting and give more control over them, as well as creating more complex sequences. Happy Tweening!!!
  11. Hi and welcome to the GreenSock forums. Unfortunately, as far as I know, GSAP can't be used in React Native, due to the way RN actually works. Keep in mind that RN works in a different way than Cordova, PhoneGap and other tools. What I know is that RN uses the Animated API, which jumps directly on the native way for creating animatios. GSAP uses the browser's requestAnimationFrame event to create the animations. Now take this with a grain of salt, because my knowledge lays on the web side of React and not in RN. Although I made a google search for RN animations with JS libraries and there weren't a lot of results. Perhaps you could try going to Reactiflux and go into their discord channel and ask around for animations using an external framework: https://www.reactiflux.com/ Sorry that I can't be more helpful, but it seems that for the foreseeable future just the existent APIs are the ones you'll have to work with. Other options involve using C, JAVA or Swift, like Flutter. Then there's Lottie that allows to export After Effects animations but that's a completely different scenario. Happy Tweening!!!
  12. Ohh... how swiftly you rose... and now thou has fallen....
  13. Well... aren't you a big box of surprises!!!
  14. Hi, TimelineMax by itself is not going to work, because GSAP's core is not available. Add TweenLite before TimelineMax and it should work: <!DOCTYPE html> <html> <head> <title>TimelineMax test</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.1/TweenLite.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.1/TimelineMax.min.js"></script> </head> <body> <script> var tl = new TimelineMax({}); tl.to("body", .3, {backgroundColor:"blue"}); </script> </body> </html> Happy Tweening!!!
  15. Rodrigo

    React support

    Hi and welcome to the GreenSock forums. First I love the avatar!!!! Second, yep there's a lack of documentation for GSAP + React / React Native out there. Unfortunately tools like Pose and Transition Group help only with simple tasks, when the time comes to make complex animations things get a bit complicated. There is (actually was) a tool for that (https://github.com/azazdeaz/react-gsap-enhancer) but is no longer maintained so not much to do there. Also in this scenarios we run into React's declarative nature, which is quite unique and the fact that the React team doesn't help too much in getting access to the DOM elements. In fact I've read some comments and Tweets from Dan Abramov (creator of Redux and part of the React and React Create App teams), where He declares that accessing the DOM nodes should be a no-no and that is anti-pattern. Obviously Dan hasn't done a single complex animation in His life on a React app ;). Back to the issue the most robust tool I use (and that's just one guy's opinion) is React Transition Group (RTG), because it takes care of the DOM operations and reconciliation when the components get mounted, unmounted, updated, etc., so with it we can take care of only creating our GSAP code. The caveat (there's always one of those, right?) is to create the transition component to use GSAP in it, but once you get the hang of it you'll see is not too complicated because RTG's API is quite simple. Here are some samples using GASP with RTG: Simple list of elements (this has some commented out code, that's the code to use... [sigh] CSS Transitions, but the sample actually uses GSAP) https://codesandbox.io/s/7lp1q8wba Mount / UnMount a component https://codesandbox.io/s/yvye9nnw Draggable Rotation https://codesandbox.io/s/jrkbkxeqy Now the first two need a major update because they're using React 15.x, when you could still use string refs for the dom elements. Now a days you need a different approach for it, like this: But updating it shouldn't be too complex. If you still find some issues on the road, please don't hesitate to create a codepen or sandbox to get a look at it. The idea is to encourage people to keep using GSAP and spread it as the standard for web animations. Happy Tweening!!!
  16. Mhh.. I see you're rehearsing your stand up comedy routine
  17. 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!!
  18. 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!!
  19. 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!!!
  20. 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!!
  21. 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!!!
  22. 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.
  23. @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!!
  24. 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!!!
  25. 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!!!