Jump to content
Search Community

UnioninDesign

Members
  • Posts

    21
  • Joined

  • Last visited

Everything posted by UnioninDesign

  1. Follow up question...how do I capture the instance of the <li> element itself? What I'm trying to do...since these cards showcase quite a bit of data...is to add additional animations to the card or <li> itself if certain conditions are met. The data-attributes method works great for spotting a certain value etc, but then triggering another animation doesn't necessarily make it clear why a burger, or a whale in my case, is flying across the screen? From my research (and errors!) you can't tween a data-attribute, nor is it possible to render a data attribute (like take the value from the attribute and pass it to a <h2> tag or something?). I suspect that, because I had already used .map to go through and animate the entire array with staggerFromTo I may be complicating things? Perhaps a for each loop is in order? Any ideas are appreciated!
  2. HI friends - I noticed a bug and wanted to post a quick update in case anyone is working on a similar project. I never got any errors, but noticed that sometimes my animation would fire when it wasn't supposed to? I have two theories as to why this happened, and wanted to share my fix: From my research debugging this issue, it sounds like data-attributes may not be the most stable? As I am going through the data using .map(), and then checking data-attributes for a certain value....but also repeating my timeline until the next data fetch, I believe the function was being called again when it hit the same index or position in the array where it had been called previously, regardless of the value? My console logs did indeed confirm that this was from a previous booking! So here's what I did, with snippet below: After each time the 'whale' animation, or in this example burgerFlip, use setAttribute to add a new data-attribute in the conditional logic, add a check using the hasAttribute() method to see if the new attribute was present before calling the function. I haven't updated the codepen yet, but it would look something like this: burgerFlip(instance){ const dataValue = instance.target.getAttribute("data-value"); //check if the instance had called the burgerFlip previously... const prevBurger = instance.target.hasAttribute("data-burgerflipper"); //returns true or false if ( dataValue > 10 && prevBurger == false ){ TweenLite.fromTo(".burger", 3, {x: -250, autoAlpha:0, rotation: 0}, {autoAlpha:1, rotation:720, x:250}) //add a data-attribute to the instance that had called the burgerFlip function... instance.target.setAttribute("data-burgerflipper", "_self"); //per MDN docs you need to add "_self" to specify the instance in question or you get an error } else { console.log("sorry no burger"); } } Thanks for the help and support as always!
  3. As always - thanks @Rodrigo - works great! Adding the data-attribute to my list items totally makes sense - but glad I made this post, I don't think I would have figured out the 'instance.target.getAttribute' part on my own!
  4. Hi team! I've been struggling for a while to get some conditional logic in place with my animations. Some quick backstory to give my codepen some context...Anyone who's curious can also see my two other threads, all related to the same project, but I thought I would make a new one with a greatly reduced example. There are LOTS of other questions about animating list items, but not many with react? And none that I've seen that involve potentially large amounts of data, or conditional logic mixed into the timeline? Backstory: I've built an interactive USA map using D3/react-simple-maps, it shows all of my companies bookings on a 5 minute delay...usually close to 50 at a time...100-150 an hour, about 8,000 in a day! That's a lot dots to show on a map! Thanks GSAP for your awesome timeline and stagger methods to make it more digestible. So...enter the 'ticker'... I decided in addition to the map I would add a sidebar that has a card with data on each booking, like who it is, the dollar amount etc. These cards appear in the ticker at the same time dots appear on the map. Now that we're live, everyone wants new features. "Wouldn't it be cool if there was a booking over a certain dollar amount, we could show a whale fly across the screen?" How do I that???? A couple of things, then some code! - Because these ticker cards contain lots of data, I do want them to pause in place for long enough that someone could read them if they wanted...after digging through forums and lots of codepen examples, the best I could come up with was multiple staggerFromTos chained together! It's pretty choppy but it works...ish. The one that is live has better timing than the the codepen! Also - no map in this example - just list items...and a cheeseburger callback! - I've been playing around with .add and .call, or adding a label somewhere to my timeline - not quite there yet! - Nothing I've done actually considers the data! In my codepen, I've created an array with id, and values, and I'm hoping if a card reaches the top of the box (or anywhere in the box) and has a value greater than 10, it will call the cheesburger animation to do its thing? Any ideas? what am I doing wrong? Where should this conditional logic live - within the function, or outside of the timeline somewhere...possible in componentDidMount or DidUpdate? - I'm still getting the hang of react and its ways...but our best practice is to use keys when going through an array with the .map() method, and use createRefs to set targets for the animation. Is there a way I can access a particular card by it's key or ref etc? https://stackblitz.com/edit/react-iiesig?embed=1&amp;file=index.js Thanks!
  5. I have a follow-up question, and I've been trolling through the forums and stack overflow in search of a solution that still escapes me at the moment! So...quick recap...I am animating markers on a map of the US, and since there's over 500 an hour, roughly 8,000 a day the stagger methods and TimelineMax have made a very nice visualization - thanks GSAP! -- How to Toggle 'visibility' of tweens throughout the timeline -- The question is that, to avoid showing all of these map markers on render, I've set the CSS for the svg circle element to " visibility: hidden", which I then toggle in the animation using autoAlpha: 1. If I drag the slider to the left to go back in time, or click the reverse or restart buttons, and also when the animation repeats...all of the map markers have now been set to autoAlpha: 1, and every single one that has rendered so far is visible. Without a better codepen (Still working on the Globe...) and, it looks likes GSAP has many built-in methods to do this, but I haven't found the right one? I've tried chaining to my buttons something like this.tl.restart().pause(0)... as well as clear(), kill() - there's also remove(), Invalidate() - so many options! I must not be using any of them correctly! I've also attempted to use many of these in the onCompleteAll callback that fires at the end of the timeline, still no success! Any ideas? Additionally - and this relates more to react/javascript/dealing with async data fetching...but when the new data set comes in I'm having trouble making a smooth transition from one set of map markers to the next. I know...tough to guess without seeing what's going on, but if you have any tips on best practices or can point me towards examples of someone else doing this that would be huge help! I did do as Rodrigo suggests above and do a deep comparison of the two data sets using the loDash _.isEqual method, inside componentDidUpdate. I was hoping the 'this.forceUpdate() wouldn't be necessary but so far seems to be the only way to tell the D3 map library (react-simple-maps) that it's time to generate new markers? I'm also working on migrating this project into true D3, should be ready for iteration v2! Here's a snippet of that function in my child or map component where it is getting the data as state passed down as props: componentDidMount() { //Animations with GSAP this.animateBookings(); } componentDidUpdate(previousProps) { //console.log("DidUpdate child was called!"); if (!_.isEqual(previousProps.data, this.props.data)) { //console.log("DidUpdate child was called no. 2!"); this.setState({ data: this.props.data }); this.theWhale(); this.tl.kill(); this.animateBookings(); this.tl.play(); } } Thanks to everyone who has helped with this project! Big shout out to @Rodrigo !!!
  6. This was a tricky one for me that took a while to figure out...but Rodrigo was spot on in terms of what the problem was...when fetching data asynchronously, you need to make sure the fetch is complete before attempting to render other components. I was getting an error about 'Can’t call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.' In case anyone else is dealing with the same issue - this article here was a huge help! Although this is a React issue, and not GSAP - worth noting how to handle these kinds of things! https://www.robinwieruch.de/react-warning-cant-call-setstate-on-an-unmounted-component/
  7. Thanks Rodrigo! You've got the right idea...and it was getting late...I was missing (duh) the . in front of the className "animateMapMarker" which should have been ".animateMapMarker", like you said GSAP was getting a string for the target and not the element with a className of ".animateMapMarker". And yes, having added the . it kind of works...There's a method in the map library that will move the 'center' of the map and rotate the globe, so I do need to write some more code to get it to a point where, when I call the staggerFrom method, the map markers get bigger (which works) AND that marker would then be displayed front and center! If I could get a bezier line to animate and connect the dots, this could be cool app that would track movements around the globe with an interactive timeline...but it's not quite there yet. I'll see what I can do to use this example to recreate the problem I am having at work...I tried it again this morning, but how weird! Data that was hard-coded in an external file, then imported and passed to the 3rd party components would animate just fine using this same method of targetting the className of the <circle> element for the map marker. But then, the 'react-way' of fetching data higher up in the component tree, passing it down as props - I get markers but no animation? Puzzling and also frustrating! To whet your whistle, if you're curious I did reach out to the map library creators about a month ago concerning this issue, before I found the bandAid of adding the className as a target, which seemed like a good fix: https://github.com/zcreativelabs/react-simple-maps/issues/119 They did respond with this codesandbox: https://codesandbox.io/s/rzr73rjjq At the time my focus was a bit more on using something like React Transition group, and trying to wrap a new 3rd party component around the 3rd party Marker component was breaking its parent, the map projection itself. The codesandbox does exactly that with a different animation library, but the data is still hard-coded so I might still run into the same issue...I guess I'll find a way to hammer it into place? If you think of any other ideas let me know! I'll of course update the thread with any breakthroughs, and hopefully a better working example so you can see what's going on!
  8. Hello again! I took some time to make a test case to play around with...getting stuck on a few things... Can we talk about use of createRef, callback refs etc? I noticed in the example link you sent, your target looks like this: div className="box" ref={e => this.box = e}> <span> MOVE & ROTATE </span> </div> I tried to replicate something similar, but could be getting stuck with a 3rd party component library so I can play with maps (react-simple-maps, that is...) Here's kicker - I've had GSAP working for several weeks now with test data that was hard coded. When trying to make my app iterative, as discussed in the posts above, I'm trying to go through the best practices of fetching data in a parent component, passing state down as props to a child component - my data renders, but the animations are gone? Up until then, my animations were working successfully by adding a className to the html element (in this case, the svg circle tag inside the <Marker> 3rd party component) I've tried to recreate a similar use case, at the moment I'm not getting any animations to fire up? https://stackblitz.com/edit/react-globe-timeline?embed=1&amp;file=index.js Let me know if you have any suggestions...much appreciated!
  9. Thanks! For now I just added a simple function to log that the animation is complete, and the animation restarts as expected with no errors (because of the repeat: -1 passed in as params to this.tl = new TimelineMax(....). Always a nice when it's an easy fix! A quick note for posterity...depending on how you do this, especially with react, you may need to add 'this' in front of the onCompleteAll callback. Here's the WALL of code again: onCompleteAll() { console.log("Animation complete"); } updateSlider() { this.setState({ sliderValue: Math.round(this.tl.progress() * bookingData.length) }); } componentDidMount() { bookingInterval(); //animateBookings(); //Animations with GSAP this.tl = new TimelineMax({ onUpdate: this.updateSlider, repeat: -1 }); //controls time in seconds in between booking cycles var next = 3; //controls fadeout var nextOpacity = 5; var opacity0 = 10; var fadeOutDelay = 10; //how long markers and lines remain on screen prior to fadeout var fadeOutDuration = 3; //how long it takes bookings to fadeout this.tl .staggerFrom( ".animatedTick", 7, { ease: Power3.easeInOut, y: 1000, opacity: 1 }, next ) .staggerTo( ".custMarkers", 0.3, { autoAlpha: 1, ease: Elastic.easeOut.config(0.2, 1.5), opacity: 1, scale: 5, force3D: false, yoyo: true, repeat: 1 }, next, 1 ) .staggerTo( ".clientMarkers", 0.3, { autoAlpha: 1, ease: Elastic.easeOut.config(0.2, 1.5), opacity: 1, scale: 5, force3D: false, yoyo: true, repeat: 1 }, next, 1.5 ) .staggerTo( ".lineAmation", 1.5, { visibility: "visible", ease: Power3.easeOut, autoAlpha: 1, opacity: 1, strokeWidth: 2 }, next, 2 ) //FadeOut animations are chained to the original timeline, with a new set of animations for each target .staggerTo( ".animatedTick", fadeOutDuration, { delay: fadeOutDelay, autoAlpha: 0, y: -200, x: 100, scale: 0, rotationY: -360, display: "none", ease: SlowMo.easeOut }, nextOpacity, opacity0, this.onCompleteAll ) .staggerTo( ".custMarkers", fadeOutDuration, { delay: fadeOutDelay, autoAlpha: 0 }, nextOpacity, opacity0, this.onCompleteAll ) .staggerTo( ".clientMarkers", fadeOutDuration, { delay: fadeOutDelay, autoAlpha: 0 }, nextOpacity, opacity0, this.onCompleteAll ) .staggerTo( ".lineAmation", fadeOutDuration, { delay: fadeOutDelay, autoAlpha: 0 }, nextOpacity, opacity0, this.onCompleteAll ); }
  10. I'm having the same issue using create-react-app, not sure if I made the same/similar error? I tried to move some things around in my staggerTo methods but still getting the error...any ideas? Not sure if this is a react-thing, or a google chrome thing, but I do suspect a syntax error? Per the timelineMax staggerTo docs, is it necessary to add an onCompleteAll callback, or some way to let the animations know when the animation is complete? I get this error at the end of the timeline, and sometimes during if I scrub too quickly through the timeline. I am using this for a data visualization so the timeline will be up to 15 minutes long! The error: So the note about speed optimization could suggest a browser issue? But I get the same thing in Firefox too. Here is my animation code, which lives in react's componentDidMount method - any suggestions are appreciated! updateSlider() { this.setState({ sliderValue: Math.round(this.tl.progress() * bookingData.length) }); } componentDidMount() { //Animations with GSAP this.tl = new TimelineMax({ onUpdate: this.updateSlider, repeat: -1 }); //controls time in seconds in between booking cycles var next = 3; //controls fadeout var nextOpacity = 5; var opacity0 = 10; var fadeOutDelay = 10; //how long markers and lines remain on screen prior to fadeout var fadeOutDuration = 3; //how long it takes bookings to fadeout this.tl .staggerFrom( ".animatedTick", 7, { ease: Power3.easeInOut, y: 1000, opacity: 1 }, next ) .staggerTo( ".custMarkers", 0.3, { autoAlpha: 1, ease: Elastic.easeOut.config(0.2, 1.5), opacity: 1, scale: 5, force3D: false, yoyo: true, repeat: 1 }, next, 1 ) .staggerTo( ".clientMarkers", 0.3, { autoAlpha: 1, ease: Elastic.easeOut.config(0.2, 1.5), opacity: 1, scale: 5, force3D: false, yoyo: true, repeat: 1 }, next, 1.5 ) .staggerTo( ".lineAmation", 1.5, { visibility: "visible", ease: Power3.easeOut, autoAlpha: 1, opacity: 1, strokeWidth: 2 }, next, 2 ) //FadeOut animations are chained to the original timeline, with a new set of animations for each target .staggerTo( ".animatedTick", fadeOutDuration, { delay: fadeOutDelay, autoAlpha: 0, y: -200, x: 100, scale: 0, rotationY: -360, display: "none", ease: SlowMo.easeOut }, nextOpacity, opacity0 //2 ) .staggerTo( ".custMarkers", fadeOutDuration, { delay: fadeOutDelay, autoAlpha: 0 }, nextOpacity, opacity0, 1 ) .staggerTo( ".clientMarkers", fadeOutDuration, { delay: fadeOutDelay, autoAlpha: 0 }, nextOpacity, opacity0, 1.5 ) .staggerTo( ".lineAmation", fadeOutDuration, { delay: fadeOutDelay, autoAlpha: 0 }, nextOpacity, opacity0, 2 ); } To get an idea of the larger project, Here's two other threads I've initiated...sorry for being 'that guy' that does not post a codepen, but this is a large full stack application with a lot of moving parts. Reduced case codepen is coming soon, possibly this weekend! This one tells you a bit more what I'm up to - bigUps to Mikel! And in this one Rodrigo helped out big time getting the range slider working! Thanks all for the help!
  11. Just in case anyone reads this and wants to go a little deeper, I have a related question from a few weeks ago, similar use case:
  12. Ah ha - as we say in French - "le mot juste!" Rodrigo you sir are a Wizard! I got pretty close by the end of the day yesterday - I think it came down to missing a 'this' in front of my updateSlider callback! (it's a class...duh...). Now onto the next...I'm in awe of GSAP and the support from its community. There are some very exciting possibilities for making data visualizations here! My apologies again that I have not supplied any codepen or reduced case examples...since this is a worky-thing it's not very public...but I will try to replicate something with a more public dataset and will update this post...maybe this weekend? If anyone is interested in building interactive maps using D3 (in this case react-simple-maps) along with React, and a robust animation library like GSAP to create an interactive timeline, this is the place to be! Thanks again!
  13. Quick update...so here's a snip of the callback function: function updateSlider(context) { console.log("State? ", context.state); let currentProg = context.state.value; //context.setState({ value: currentProg + 1 }); context.tl.progress(currentProg / bookingData.length); } And I'm calling it at the timeline declaration at so, which I believe is correct: this.tl = new TimelineMax({ onUpdate: updateSlider, onUpdateParams: "{self}" }); But...because all of the animations live inside componentDidMount(){}, if I pass this in at after the timeline, inside componentDidMount...we are not getting passed the value that has been set here inside these two slider methods: onSliderChange = value => { this.setState({ value }); console.log("Value is: ", value); }; onAfterChange = value => { console.log(value); this.tl.progress(value / bookingData.length); this.tl.resume(); }; ...It will continue to update with no value and error out after a few seconds? Alternatively, passing in the updateSlider callback right after onSliderChange and onAfterChange, just above componentDidMount, will give you an undefined error (makes sense). I think this is not a GSAP problem! but something to do with the architecture or my app! Any ideas are appreciated! Sooooo close!
  14. Rodrigo - the man I was hoping to hear from! I've spent LOTS of time in this forum the past week or so, and your posts are always very helpful! Issue fixed per below! But the next one at the bottom of this post - how do we get the slider to update the position based on the current value of the animation? BTW - Good eye spotting an inconsistency in my code! Easy to do when you try things a bunch of different ways...however, when setting a range of 0 to 100, or 0 to bookingData.length (which indeed gives you a value for how long the dataset is...I'll stick with that!)....the same result was that it kills the animation, but not getting any errors in the console. You could continue to drag the slider around and it will keep logging the values, but the play, pause, resume don't function, and you need to refresh the page to get the animation to run again. After a few more minutes playing around...as I'm pretty sure I had tried what you had suggested, but with no luck...I noticed some code I had copied into to the top of the render method: render() { this.tl .kill() .clear() .pause(0); return ( The fix was to remove the .kill() and .clear() methods!!!!! Although I'm a little curious why these are here and what purpose they might serve, perhaps to pass in some kind of initial state to the playback methods at rendering...which is not best practice, correct? better to set the initial state in your constructor, or even to use a parent level component to manage the state and pass it down as props? Still have lots to learn! For posterity, scrolling on the timeline now brings you to the next value (or booking!), but it does help to pass in the .resume() method to get the timeline working automatically, if not it will be pause when onAfterChange talks to the slider: onAfterChange = value => { console.log(value); this.tl.progress(value / bookingData.length); this.tl.resume(); }; So the next challenge, which I hope to figure out soon...we need the range slider to update itself as the timeline progresses. It sounds like what we need is an onUpdate callback: onSliderChange = value => { this.setState({ value }); console.log("Value is: ", value); }; onAfterChange = value => { console.log(value); this.tl.progress(value / bookingData.length); this.tl.resume(); }; updateSlider = () => { let currentProg = this.state.value; this.tl.progress(currentProg * bookingData.length); console.log("running", currentProg); }; I keep getting an error that on updateSlider is undefined? I am trying to pass it in like so. Quick note that all of my animations are currently housed in componentDidMount, but I do need to move these to an external animation.js file... onSliderChange = value => { this.setState({ value }); console.log("Value is: ", value); }; onAfterChange = value => { console.log(value); this.tl.progress(value / bookingData.length); this.tl.resume(); }; updateSlider = () => { let currentProg = this.state.value; this.tl.progress(currentProg * bookingData.length); console.log("running", currentProg); }; componentDidMount() { bookingInterval(); //Animations with GSAP this.tl = new TimelineMax(); // parameter: { "onUpdate", updateSlider } var next = 3; this.tl .staggerFrom( ".animatedTick", 3, { rotationY: -180, ease: SlowMo.easeOut, y: 700, delay: 1, scale: 2, opacity: 0 }, 3, "nextBooking -=3" ) .staggerFrom( ".custMarkers", 1.5, { cycle: { x: [-1000, 1000], y: [-1000, 1000] }, bezier: { curviness: 1, values: [{ x: 0, y: 0 }, { x: 125, y: -80 }, { x: 250, y: 0 }] }, ease: Power1.easeOut, opacity: 0, scale: 10 }, next, 1 ) .staggerFrom( ".clientMarkers", 1.5, { cycle: { x: [-1000, 1000], y: [-1000, 1000] }, bezier: { curviness: 1, values: [{ x: 0, y: 0 }, { x: 125, y: -80 }, { x: 250, y: 0 }] }, ease: Power1.easeOut, opacity: 0, scale: 7 }, next, 1.5 ) .staggerFrom( ".lineAmation", 2.1, { ease: Sine.easeIn, opacity: 0 }, next, 2, "-=1" ); } render() { this.tl.pause(0); return (............ So up next, where do I pass in this callback? Thanks a ton!
  15. I really appreciate the help on this! I did get the sequence timed out the way I want, but had a follow up questions on callbacks in case either of you two gentleman would like to kick this can a little further down the road? Case: so we're building a map, and using this animation I'll get report every 15 minutes with 100 - 500 bookings to display! Now that I've got a nice stagger effect so you don't see all the bookings rendered simultaneously, I'm noticing that the map starts to get cluttered? I'd like to write some kind of callback (if necessary) to make the bookings fade out after 5-7 seconds ish? I think I'm close but haven't quite nailed it just yet Here's my animation code - with some parts adapted from your codepens. I also decided to add a sidebar that show text about in each booking so it's not just a bunch of dots on the screen - the target class ".animatedTick" leads off the timeline: this.tl = new TimelineMax(); // parameter: { onUpdate: updateSlider } var next = 3; var bookingDuration = 6; this.tl .staggerFrom( ".animatedTick", 3, { rotationY: -180, ease: SlowMo.easeOut, y: 700, delay: 1, scale: 2, opacity: 0, autoAlpha: 1, timeResolution: bookingDuration }, 3, "nextBooking -=3" ) .staggerFrom( ".custMarkers", 1.5, { cycle: { x: [-1000, 1000], y: [-1000, 1000] }, bezier: { curviness: 1, values: [{ x: 0, y: 0 }, { x: 125, y: -80 }, { x: 250, y: 0 }] }, ease: Power1.easeOut, opacity: 0, scale: 10, autoAlpha: 1, timeResolution: bookingDuration }, next, 1 ) .staggerFrom( ".clientMarkers", 1.5, { cycle: { x: [-1000, 1000], y: [-1000, 1000] }, bezier: { curviness: 1, values: [{ x: 0, y: 0 }, { x: 125, y: -80 }, { x: 250, y: 0 }] }, ease: Power1.easeOut, opacity: 0, scale: 7, autoAlpha: 1, timeResolution: bookingDuration }, next, 1.5 ) .staggerFrom( ".lineAmation", 2.1, { ease: Sine.easeIn, opacity: 0, autoAlpha: 1, timeResolution: bookingDuration }, next, 2, "-=1" ) I've tried chaining to the end of this with a few different methods, am I close? This one gives me an error 'delayed call is not a function' - should this be passed in as params to the timeline? I have this one chained right after the last staggerFrom... .delayedCall(bookingDuration, bookingFadeout); function bookingFadeout() { this.tl.to( [".animatedTick", ".custMarkers", ".clientMarkers", ".lineAmation"], bookingDuration, { autoAlpha: 0, opacity: 0 } ); } And i also tried to chain this to end of the sequence... .to( [".animatedTick", ".custMarkers", ".clientMarkers", ".lineAmation"], 5, { opacity: 0, ease: Power1.easeInOut, autoAlpha: 0 } Which does nothing? Another idea was to find a way to go by the number of repeats, but the onRepeat() method executes after each repeat, not say, every 6, 7 or 8 repeats? I have a few things going on here, like trying to set the timeResolution in each staggerFrom tween equal to a global variable called 'bookingDuration' - also not working... Any ideas are appreciated!
  16. Hello Community - my second post as I'm very stuck trying to integrate a range-slider in React that will be friends with the progress() method to move through the timeline! I can pass around the value I need to my slider, but getting undefined errors when I try to pass this over to this.tl.progress(this.state.value) etc? I've read through several posts, but none look very current...in fact most are going back 2-3 years and the react-way has changed! Looking through the 'getting started with react post from Rodrigo' there are some useful hints, but it seems overly complex, compared to how easy it was to plugin the play, pause, reverse and restart methods into a button with an onClick event handler. Do we really need a separate state management file to pass down the state as props to a child component just to get the slider to move through the timeline without breaking? I also got some very good support already (This gives you more of an idea what I am working on...sorry for the lack of a reduced codepen example as this is a large full-stack application! coming soon I promise!) Here's some code of how I got the play, pause, reverse, and restart methods working...these plugged right in! (FYI ButtonGroup and Button components are from React-Bootstrap): <Row> <Col md={12}> <ButtonGroup className="animationControls"> <Button bsStyle="primary" onClick={() => this.tl.play()}> <Glyphicon glyph="play" /> {"Play"} </Button> <Button bsStyle="primary" onClick={() => this.tl.pause()}> <Glyphicon glyph="pause" /> {"Pause"} </Button> <Button bsStyle="primary" onClick={() => this.tl.reverse()}> <Glyphicon glyph="backward" /> {" Reverse"} </Button> <Button bsStyle="primary" onClick={() => this.tl.restart()}> <Glyphicon glyph="step-backward" /> {"Restart"} </Button> </ButtonGroup> </Col> </Row> Until bootstrap-4 is up and running with react, you do not have range slider in your form components, so I had to look elsewhere. After trying a few different versions, the npm package rc-slider seems to be the most lightweight (little to no boilerplate required!) create your styles for the slider before your class function: const railStyle = { position: "relative", width: "90%", margin: "0% 0% 0% 3%", height: 10, borderRadius: 7, cursor: "pointer", backgroundColor: "#afafaf" }; const handleStyle = { height: 15, width: 15, backgroundColor: "white", borderTopLeftRadius: 10, borderTopRightRadius: 10, border: "3px solid #E5F1FC", top: -2, position: "absolute" }; AND be sure to set your starting value in the constructor....probably 0 since that would be the start of your timeline.... constructor(props) { super(props); this.tl = new TimelineMax(); this.state = { center: [46.8, 8.3], zoom: 1, value: 0 }; this.handleZoomIn = this.handleZoomIn.bind(this); this.handleZoomOut = this.handleZoomOut.bind(this); this.handleReset = this.handleReset.bind(this); this.handleSliderChange = this.handleSliderChange.bind(this); } Next, I have two functions...please note that onSliderChange and onAfterChange are pre-built methods for the react rc-slider component. these successfuly track and log the value as you drag along the timeline, but kill the animation! onSliderChange = value => { this.setState({ value }); console.log("Value is: ", value); }; onAfterChange = value => { console.log(value); this.tl.progress(value / 100); }; .....And lastly, the slider component itself, inside render() <Slider className="slider" style={railStyle} handleStyle={handleStyle} min={0} max={bookingData.length} value={this.state.value} onChange={this.onSliderChange} //onInput={this.handleSliderChange} onAfterChange={this.onAfterChange} /> I know this may be hard to digest without a working example. I'll try to make a reduced case, but here's the issue...inside the Slider component, if you drag the slider around, it will successfully log the value I need. Where can I pass the value to this.tl.progress(this.state.value / 100) etc to get the timeline to respond? I've tried a dozen different ways, and I either get that value is undefined, or when I try to pass this in to the onSliderChange I get my fav error about expected a function, but instead saw an expression, no unused expressions. dragging the slider around kills the timeline, or depending where I do it, will make the animated elements disappear from the screen. Grrrrr! React is very powerful, but the need to constantly update the state of components during their lifecycle make these kinds of things very frustrating! If anyone has solved this or can send a link to an example of how to do this it would be greatly appreciated! If I figure it out on my own I will update the post - I know I'm close! Thanks community!
  17. To chime in on my end, the react-simple-maps (using D3) component library will generate the lines/markers when passing in mercator coordinates for your locations in your data. I do have a few stumbling blocks to sort out with this, for example this library has a <ZoomableGroup> component that makes it easy to zoom in/out on your map...doing so kills the GSAP animations so I'll have to find another way to zoom! (edit: My guess is that the zoom button resets the state of the map...) I appreciate that Mikel took the time to hard code x and y values to create a working example, but at the moment I don't believe a relationship exists in between the coordinates to points on the map v. the x and y coordinates used by GSAP?
  18. Wow Mikel! Thanks so much, this works exactly like what I was looking for! Thanks Jack as well, and sorry I didn't give you guys a better code example to look at...this is turning into a large full stack application with a fairly complex component tree, but once I have it all working I will update this thread with a simpler codepen example or repo on github for anyone else that is working on data visualization with React and GSAP, and D3 maps! I've been all over the web looking for examples of something like this and haven't found anything, which is surprising to me! The timeline features in GSAP are great for animation, but make this platform a powerful tool for visualizing data over time! I also believed, like Jack, that this was a data structuring issue, and with the data split into three separate arrays, already sorted with .map, that there would be some extra steps involved to pass in the bookingId to the 'target' of your animations...maybe some kind of for loop function to give 'target' both the classname for the item you want to animate (or ref to expose the dom node where necessary to be react-y), along with the index of each booking or something like that. I'm thrilled that some kind of function to re-join the data was not at all necessary - this only speaks to the depth and flexibility of GSAP. I'm super impressed with both GSAP and the support from this community! Thank you both!
  19. Wow thanks! That does help, but not quite what I am looking for. To dig a little deeper, and this may be due to the third party component library I am using, to render two separate sets of map markers, my data is split up into three different arrays now - one for the customer data and map markers, one for the client, anda third array containing the coordinates of each to create the line connecting the two. So to recap...someone makes a booking, but they might live in a different part of the country? Wouldn't it be cool to generate a map marker for the customer, followed by an animated line to connect that customer to the client they booked with? And then - move on to the next booking via it's booking ID or key? What happens now is that, with my dummy data of 11 bookings - all 11 customer markers render, in staggered animation, followed by all 11 clients, and then after that 11 lines. The effect I am going for is (Customer of booking#1)------animatedLine-----(Client of booking #1), and then delay by a second or two, and then repeat that series for next bookingId (Customer of booking#2)-----animatedLine-----(Client of booking #2). Here's an example of how the map markers are generated from my dataset using react-simple-maps, starting with the customer who makes the booking: <Markers> {bookingData.map((data, iCust) => ( <Marker ref={this.custRef} key={iCust} marker={data.customer} style={{ default: { fill: "#1B9750" }, hover: { fill: "#FFFFFF" }, pressed: { fill: "#0A6ECE" } }}> <circle className="custMarkers" cx={0} cy={0} r={3} style={{ stroke: "#1B9750", strokeWidth: 3, opacity: 0.7 }} /> </Marker> ))} </Markers> Then you do this same thing again for the client, and then again for the lines! The result is that I have taken one array of data, but used the .map method three different times! While the animations look amazing, I think I'll need to restructure the way I'm generating these markers, or re-join them based on the bookingId in order to get the animation effect I am looking for? Any ideas are much appreciated!
  20. Hello community - my first post! I discovered GSAP on Friday, got it working a few days later...mind=blown! I'd be happy to supply a codepen but have more a general/best practices question. My stack: React, D3...or rather, react-simple-maps npm package to use D3's map projections...and of course GSAP! My project: I'm a new front-end developer, hired as a data visualization analyst, and my first project is to build an interactive map to show bookings. with hundreds of bookings to render, it would be 'data overload' and lousy UI with hundreds of markers across the US all at the same time... I started racking my brain to try and find a way to map these out over a timeline to stagger the bookings...enter GSAP! Question: The map shows (first) the location of the customer who made the booking, our client's location, and a line connecting the two. I have a data object with nested client and customer data, looks something like this below. In order to generate map markers for both the client and the customer, I have already used .map to go through each array and create the marker. I have staggered the animations, but at runtime, all customers are rendered one at a time...then all clients are rendered one at a time, followed by all lines. Surely there's a way to to use the key to go by each bookingId and render the customer unique to that ID, then the client, and then animate a line connecting the two before applying that same animation to the next booking? My janky code for the animations (first-timer over here, go easy on me! lol) - I know the best practice in React is to use createRef instead of classeNames - will migrate soon! Likely to move this to a separate animation.js file and call the functions later in componentDidMount(). Happy to send more code examples and thanks! componentDidMount() { bookingInterval(); //Animations with GSAP const tl = new TimelineLite(); tl.staggerFrom( ".custMarkers", 1, { y: 500, cycle: { x: [-500, 500] }, ease: Power1.easeOut, opacity: 0.1, scale: 10 }, 0.5 ) .staggerFrom( ".clientMarkers", 1, { cycle: { x: [-500, 500] }, y: 500, ease: Power1.easeOut, opacity: 0.1, scale: 7 }, 0.5 ) .staggerFrom( ".lineAmation", 2, { y: 500, x: -500, ease: Power1.easeOut, opacity: 0.1, scale: 7, rotation: 360 }, 0.5 ); } Data: { BookingId: 123456, client: { ZipCode: 55555, coordinates: [-80.6998, 28.3716], clientName: "name of the client", }, customer: { ZipCode: 11111, coordinates: [-122.3122, 47.5838], location: "city, state" }, CreatedAt: "timestamp string" },
  21. I was gonna suggest Heroku for your react apps? Works well with node.js and I think you can store up to 5 free apps. I did have one with a MySQL database that required additional plugins, but for practice apps that are not getting a lot of views or database requests Heroku is a great option, and easy to integrate to an existing git repo!
×
×
  • Create New...