Jump to content
Search Community

How can I Hijack RequestAnimationFrame

Aaron test
Moderator Tag

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

Hello. I'd like to be able to turn requestAnimationFrame on and off for a number of elements that use it. Some of those elements aren't Tweens.

 

Rather than specifically turning it on or off, I'd rather have control over the function that runs as a result of the requestAnimationFrame inside of TweenLite via a method like tick().

 

Psudocode

var tween = TweenLite.to(element, 1, {width:"50%"});
var running = true;

tween.override();

function renderTween(){
     if(!running){ return false };
     requestAnimFrame(renderTween);
     tween.tick();
}
renderTween();



Anyone have any ideas?

 

Thanks,

Aaron

Link to comment
Share on other sites

Hi;

 

I think that tick is event dispatched by the static ticker object;

 

TweenLite.ticker.addEventListener("tick", renderTween);

function renderTween(event) {

  //this function will be executed on requestAnimationFrame if available, or on setTimeout if not
}

Link to comment
Share on other sites

Hi;

 

I think that tick is event dispatched by the static ticker object;

 

TweenLite.ticker.addEventListener("tick", renderTween);

 

function renderTween(event) {

  //this function will be executed on requestAnimationFrame if available, or on setTimeout if not

}

 

Thank you for your reply.

 

This would work if you're trying to hitch something to run parallel to the frame rendering of TweenLite. What I'm trying to do is take complete control of the timer itself inside of TweenLite. So i can variably control its "ticking".

Link to comment
Share on other sites

Is there some reason you don't want to just use pause() and resume()? You could certainly add a "tick" listener to TweenLite.ticker and run any logic you want, perfectly synchronized with the core and then pause()/resume() as you please. Or even render() a particular time I suppose.  

 

I'm struggling to grasp why (or if) you'd want to add an extra layer of complexity into the render cycle that runs on every single tween in every frame. Maybe it'd help if you explained a use case scenario. 

  • Like 1
Link to comment
Share on other sites

Hi Jack.

I have a scrolling javascript library I'm working on. The gist of the project is to micromanage scroll and requestAnimFrame events to ensure that objects that control DOM elements that aren't currently in the viewport aren't executing.

 

The code goes something like this:

var scrollable = {
element: $('#foo_div'),
scroll: function(e){
console.log("the page was scrolled");
},
render: function(e){
console.log("render animation frame is active");
}
};

ScrollLibrary.watch(scrollable);

/*
If scrollable.element is on the screen, when the user scrolls the page, scrollable.scroll() fires.
If scrollable.element is on the screen, the scrollable.render() method fires

when scrollable.element is not on the screen, scrollable.scroll() and scrollable.render() are ignored
*/

This is made slightly more complex by a feature that I've implemented to help control what happens when you scroll.

 

As a DOM element makes its way from the bottom of the viewport to the top of the viewport, the scrollable.scroll() method is passed a number between -1 (bottom of the screen) and 1 (top of the screen).

var scrollable = {
element: $('#foo_div'),
scroll: function(e){
console.log("offset = " + e);
},
render: function(e){
console.log("render animation frame is active");
}
};

ScrollLibrary.watch(scrollable);

What that allows me to do is proportionally scale between two values. Given a map method like this

//mathutils for proportional scaling
var mathutils = {
    normalize: function ($value, $min, $max) {
        return ($value - $min) / ($max - $min);
    },
    interpolate: function  ($normValue, $min, $max) {
        return $min + ($max - $min) * $normValue;
    },
    map: function ($value, $min1, $max1, $min2, $max2) {
      if ($value < $min1){
       $value = $min1; 
      }
      if($value > $max1){
       $value = $max1; 
      }
      var res = this.interpolate(this.normalize($value, $min1, $max1), $min2, $max2);
      return res;
    }
};

I can proportionally manage where on the screen elements land while scrolling

var scrollable = {
element: $('#foo_div'),
scroll: function(e){
var elem_left = mathutils.map(e, -1, 1, 0, 100);
this.element.css('left', elem_left);
},
render: function(e){
console.log("render animation frame is active");
}
};

ScrollLibrary.watch(scrollable);

So now I've set the element's left value to 0 when it has just come onto the bottom of the screen, and 100 when it has just left the top of the screen.

 

Taking this a bit further, and to finally get to the point, I can use this same mapping logic to seek to a percentage of a tween.

//create circle
var $circle = $('.circle');

//tween it
var tween = TweenLite.to($circle, 2, {
  top: "200"
});
tween.pause();
controlTween(tween, 1);


function controlTween(tween, percentage){
  var tDuration = tween.duration();
  var timecodeByPercent = mathutils.map(percentage, 0, 1, 0, tDuration);
  tween.seek(timecodeByPercent);  
} 

http://jsbin.com/edewem/3/edit

 

 

So by adding that along with the scrolling bit, I can tween any element as I scroll from the top to the bottom. Which really opens some doors to what the web can do, and makes rich scrolling sites pretty amazing.

 

So, rather than calling pause on n number of tweening elements in the system, I'd rather just not send the render event to them. When they are back in the viewport they would just pick back up where they left off like nothing happened.

 

If you've made it this far, Here's an example of it all working

http://digitalsurgeonsdev.com/aaron/

 

 

The bottom panel is seeking to a percentage of a TimelineMax animation sequence of the InfinityBlade guy chopping the head off of a monster and blood spraying out.

Link to comment
Share on other sites

Sorry about the delayed response - I've been traveling and this slipped through the cracks. 

 

To answer your question, if you pause() a tween, it causes the engine to skip over that tween when rendering (it doesn't actually stop the requestAnimationFrame altogether - it just doesn't allow it to affect that particular paused tween/timeline). So it freezes the virtual playhead inside that tween/timeline until you resume(). Even when a tween is paused, you can manually move the virtual playhead by changing its "time()" or "totalTime()" or seek() to a new time or even use the undocumented render() method. 

 

Okay, so here are some other thoughts:

  1. Typically JS execution isn't the bottleneck at all in situations like this - browser graphics rendering is far more CPU-intensive. So you could allow the tweens to continue but simply try toggling the element's "visibility" css property to "hidden" or (if you don't care about document flow) use display:none. That essentially allows the browser to not worry about graphics rendering on that element. For example, I had 3000 tweens going simultaneously on 3000 elements, and when I just toggled visibility to "hidden", the fps jumped to probably 20x what they were previously, proving that the JS execution in GSAP is extremely fast, but the bottleneck was the DOM graphics rendering in the browser. 
  2. If you want to control the rendering, you can pause() the tweens associated with that particular element and then manually move the playhead if/when you want. 

Does that help?

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...