Jump to content


  • Posts

  • Joined

  • Last visited

UncommonDave's Achievements

  • Week One Done
  • One Month Later
  • One Year In

Recent Badges



  1. Thought I should post what we've ended up with so far. This has been running for a couple weeks and seems to be covering our needs for now, although I know there are still some holes especially if we needed to test any timelines with more sophisticated construction processes or out-of-sequence methods appended (using position offsets). I should also note that this decreased the overall running time of our front-end test suite by well over 30%, so this has been a pretty successful addition. Timeline is still missing most of the handlers (most notably on my radar are the "progress" and "yoyo" methods) and we didn't fully patch the fromTo tween (just zero'd out the duration) but this is still an early V1 to get something in place. function getPatchedTween() { let tween = ($el, dur, opts = {}) => { isDefinedExecute(opts.onStart); isDefinedExecute(opts.onComplete); return tween; }; tween.delay = () => tween; tween.kill = () => tween; return tween; }; function muteDuration(func) { return function() { if (arguments[1]) { arguments[1] = 0; } return func.apply(this, arguments); }; }; function isDefinedExecute(callback, context = null, args = []) { let isDefined = (val) => { return val !== undefined && val !== null; }, isFunction = (obj) => { return !!(obj && obj.constructor && obj.call && obj.apply); }; if (isDefined(callback) && isFunction(callback)) { return callback.apply(context, args); } else { return false; } }; TweenMax.from = getPatchedTween(); TweenMax.to = getPatchedTween(); TweenMax.fromTo = muteDuration(TweenMax.fromTo); TweenLite.from = getPatchedTween(); TweenLite.to = getPatchedTween(); TweenLite.fromTo = muteDuration(TweenLite.fromTo); class TimelinePatched { constructor(options = {}) { this.callbacks = []; this.options = options; this.paused = this.options.paused || this.options.autoplay === false; this.from = this.callbackTween.bind(this); this.to = this.callbackTween.bind(this); this.add = this.callbackAdd.bind(this); this.fromTo = ($el, dur, opts, opts2, position) => this.callbackTween($el, dur, opts, position); if (!this.paused) { setTimeout(this.play.bind(this)); } } callbackTween($el, dur, opts, position) { return this.callbackAdd(() => getPatchedTween()($el, dur, opts), position); } callbackAdd(func, position) { this.callbacks.push({position, func: func}); this.callbacks.sort((a, b) => { return a.position - b.position; }); if (!this.paused) { func(); } return this; } play() { this.paused = false; isDefinedExecute(this.options.onStart, this); for (let cb of this.callbacks) { cb.func(); } isDefinedExecute(this.options.onComplete, this); } pause() { this.paused = true; } isActive() { return false; } reverse() { for (let i = this.callbacks.length - 1; i > -1; i--) { this.callbacks[i].func(); } isDefinedExecute(this.options.onReverseComplete); } } TimelineMax = TimelineLite = TimelinePatched; I should also add that I had to remove a little bit of embedded utility methods and other options that are very specific to our use of this. I'm hoping I didn't break something during translation.
  2. @Rodrigo So yea, as Jack was saying, we've already patched the duration args for all the tween and timeline instances to 0. So the next thing we're trying is to squeeze additional speed out by circumventing the internal optimizations that GSAP performs, even for 0 duration animations. This GSAP overhead usually ends up manifesting in just a handful of ms (something between 10-30ms seems pretty common for us right now), but because we already have several hundred tests (and that number is growing quickly) that involve a part of the interface that does some sort of animating, the cumulative gain adds up. And we have an internal goal that involves making our suite run REALLY fast, so patching GSAP will be a big step toward that goal. On another note.... I personally have only been exposed to test driven development for a couple of years now. But I've found a lot of motivation and inspiration for the pattern by watching YouTube lectures from Robert C. Martin (Uncle Bob). I encourage any serious engineer to listen to at least a couple of his talks... he's a pretty smart guy. Here's a good one: https://www.youtube.com/watch?v=BSaAMQVq01E
  3. Thanks for the response, although most of your stated concerns aren't things that provide any real value to us within the context of automated tests. We're not actually interested in testing GSAP itself, because it's already a solid product and I assume you guys are doing your part to keep it that way. We only want to test our own code, and for testing purposes the only things we really care about are the starting position, the ending position, and executing the callbacks in the correct order. So if there isn't any simple or native way of bypassing those internal optimizations to maximize for speed, then leaving the functions intact is not very useful for us. Well, ideally we'd just defer to GSAP's native handling for anything we couldn't appropriately patch. But the vast majority of our animations are not those more complex cases, and we stand to gain a lot by optimizing the very common .to()/.from() tweens. We will eventually have several thousand unit tests and we intend to make them capable of running in just a few seconds. It's an important goal for us that has ramifications on our overall development cycle. There are no browser inconsistencies, because there is no browser. We're using jsDom to create a virtual dom and we run our unit tests in that environment. It's not really just a matter of getting a faster computer because the use-case isn't just for myself as an individual to occasionally run a small set of tests on my local machine. We're going to end up with thousands of tests running hundreds of times per day on dozens of different machines, both locally and on AWS. To optimize this cycle as much as possible we want to monkeypatch GSAP so it does the absolute bare minimum for our testing process to work, but no more. The real value of GSAP is that it helps create a superior user experience for real humans using real browsers. There is essentially no value when running for virtual users in a virtual dom, but there is a lot of additional value in giving GSAP the capacity to "get out of the way" in this case. So we're going to take a run at this, and I'll post updates/questions as we go.
  4. We're currently using TweenMax and TimelineMax extensively in our app, and we're also running a suite of unit tests against the interface via mocha and we've discovered that one of the major factors pushing our automated test times up is that GSAP appears to still be doing a lot of work, even when we monkeyPatch the tween/timeline durations down to 0. I haven't cracked open the GSAP lib to explore the inner workings yet, but my first thought was to just patch to(), from(), and fromTo() with nearly empty functions that will handle the callbacks. Although I still need to move the animated elements to their ending position, so maybe I could clone the .set() method and extend it to execute callbacks? But I have no idea if that's opening up a can of worms... So I was curious if this is something the GSAP team has already addressed in the past... maybe you guys already built out a solution or have some branch somewhere that handles all the potential animation types and all the various callbacks for both tweens and timelines, but has been optimized for an automated testing environment?
  5. I definitely understand why browser authors are trying to prevent code-generated windows from popping up willy-nilly (not that spammers and other various ****-heads haven't found workarounds). The use-case here is to access the oAuth pages of some 3rd party apps, and a few of them have set their X-FRAME-OPTIONS to block iframing. The simple solution is to just forego the button animation, and there is no more problem. But I like animating stuff and I wanted my button-click to look cool... so... ya know.
  6. I'm trying to open a new browser window via window.open() after a user has clicked on a button and the button performs an animation first, but I can't simply execute the window.open() in the tweens' callback because the browser no longer believes the call was initiated by a user action... and so it blocks the window as a popup. I've was working on a fix by creating a pop-under and then focus it from the tween callback, but this doesn't feel clean OR stable. I was thinking/hoping there was some sort of option I might be able to pass to the tween that would somehow maintain the integrity of the main thread and allow me to simply open the new window from the callback. Or if somebody had figured out a cleaner workaround than the pop-under method.
  7. Thanks Carl & OSUBlake, that looks like exactly what I needed.
  8. Thanks for the reply Dipscom. There are definitely some applications where it should be fine to simply coordinate two tweens, but I'm not sure that's going to work with my specific application. The "transition" point (where one element stops animating and the next element starts) in my particular project is dynamic... so it changes, based off of user input and some current application state values. It might be possible to programmatically generate new option objects for the tweens with calculated delays based off of the current easing math and the distance to travel before the transition should occur.. but it seems like a ton of work and frankly I'm not sure I have the chops for something that heavy-duty. The width-example in that code pen works very easily because of the CSS min/max width rules. I can freely apply an identical animation to two objects, and simply give them different min/max values to get the effect I need. Unfortunately, css transforms and other rules don't have any min/max available, so I have to figure out how to get the same effect from within the js. Conceptually, I know what the solution is. But I don't know how to implement it. On every animation frame, gsap is going to write a new set of styles onto the element. I want to intercept that value, make sure it's above/below a particular threshold, and override that value if it doesn't meet the threshold. It seems like it should be within the onUpdate callback, and it seems as though I should be able to target the tween itself from within that callback, then look at the next value being written and have access to change it if I need to. That last bit is what I can't seem to find or figure out.
  9. Seems like something that's actually not that hard to do, but I'm having trouble figuring out what to search for to identify my problem... (As evidenced by the weird topic title). If you look at the codepen, you can see the top two bars both have the same animation, but by positioning them and applying maxwidth to them, you can get kindof get an effect where the ease has been "passed off" from one element to the other. What I'm trying to figure out, is how to do this with other transitions that are not related to width or height (atm, I'm working with a transform.rotate that I would like to get this sort of behavior out of). I figure the solution should be as simple as wedging something like a Math.min(tween.currentVal, 50) into the animation process (maybe via the onUpdate callback?), but I just can't seem to find the entry-point for that.