greensock / GSAP

GSAP (GreenSock Animation Platform), a JavaScript animation library for the modern web
https://gsap.com
19.56k stars 1.72k forks source link

Ticker not working the same way for TweenLite and TweenMax #3

Closed ain closed 11 years ago

ain commented 11 years ago

The following code works nicely with TweenLite/TimelineLite but something fails for TweenMax/TimelineMax:

<!DOCTYPE html>
<html>
    <head>
        <title>TimelineMax tick problem</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script type="text/javascript" src="TimelineMax.min.js"></script>
        <script type="text/javascript" src="TweenMax.min.js"></script>
        <script type="text/javascript">
            function init() {
                var ctx = document.getElementById('cnv').getContext('2d');
                var img = new Image();
                img.xpos = 5;
                img.ypos = 5;
                var anim = function() {
                    ctx.clearRect(0, 0, 201, 150);
                    ctx.drawImage(img, img.xpos, img.ypos);
                };
                img.addEventListener('load', function() {
                    console.log('image loaded!');
                    TweenMax.ticker.addEventListener('tick', anim);
                });
                img.src = "http://www.flashbit.net/fb_32px.png";

                var timeline = new TimelineMax({repeat:-1});
                timeline.to(img, 0, {xpos:0, ypos:20})
                    .to(img, 0.4, {xpos:20, ypos:50})
                    .to(img, 2.5, {xpos:20, ypos:75, ease: Linear.easeNone})
                    .to(img, 0.3, {xpos:30, ypos:100})
                    .play();
            };
        </script>
    </head>
    <body onload="init()" style="background-color: #ccc;">
        <canvas id="cnv" width="200" height="150" style="background-color: #fff;">Canvas not supported!</canvas>
    </body>
</html>
jackdoyle commented 11 years ago

Ah yes, believe it or not, this is expected behavior (although I completely see why it would seem otherwise) and there's an easy solution (actually, a few). Let me explain...

At its core GSAP is an extremely flexible tweening engine that isn't just for animating DOM elements - it can tween ANY numeric property of ANY object. To do special stuff (like handling DOM element styles), plugins are used, like CSSPlugin. When we originally launched GSAP, if you wanted to animate DOM element styles, you had to wrap those properties in a css:{} object to clarify your intent (like {css:{left:100, top:20}}). This made a lot of sense in terms of raw logic, but we came to realize that since it is SO common to animate DOM element styles, the css:{} wrapper degraded usability and made mistakes a bit more common (like people nesting things incorrectly, like an onComplete or ease inside the css:{} instead of outside). Therefore, in version 1.8.0 we added some logic to the TweenLite core that allows you to skip the css:{} wrapper. See http://www.greensock.com/update-2013-01/ for details.

Basically, it will check to see if the target is a DOM element and the CSSPlugin is loaded and you did NOT define a css wrapper - if all of those conditions are true, it assumes your intent is to animate css styles of the target and it creates the css:{} wrapper for you internally, passing the non-reserved values to CSSPlugin to animate. This is a boon for productivity because it shortens your code and reduces nesting errors. However, if you're trying to tween non-css values of a DOM element (which is what you're doing here - xpos and ypos obviously are made-up properties) and you loaded the CSSPlugin (it's inside TweenMax), then it ends up treating non-css properties as if they're css, thus not giving you the results you want.

The solution is to simply pass autoCSS:false into your vars object which tells it not to automatically create the css wrapper for you. Either that or you can create an empty css:{} wrapper so that it understands the delineation between css props and non-css props of your DOM element. We didn't want to create an internal dictionary of all valid css properties and check against those because not only would it degrade performance (checking every property against the dictionary for every tween), but it'd also mean that as new features become available in the future inside the browser, they wouldn't get recognized by CSSPlugin. We'd rather have a very flexible system that can handle just about anything you throw at it.

The reason it worked when you used TweenLite instead of TweenMax is because without CSSPlugin loaded, TweenLite knew it shouldn't create the css:{} wrappers for you since the plugin wasn't there to handle it. Since CSSPlugin is inside TweenMax (for convenience), it created the wrappers for you.

So that was a really long-winded response that can be summed up like this: either pass autoCSS:false or css:{} into your tween(s) that have a DOM element as their target but non-css properties that you're tweening. Or you could use a generic object as your target like this:

img.position = {x:5, y:5}; TweenMax.to(img.position, 1, {x:20, y:20});

Does that clear things up?

ain commented 11 years ago

Fantastic explanation, thanks a lot for such a comprehensive feedback! I'm quite impressed with the work you've done for the JS branch, couldn't have imagined that it's as comparable to the AS3 version.

Test results: autoCSS setting resolved my impasse on TweenMax nicely, everything worked out.

The idea would be to somehow document it more prominently. What happened in my case was that I googled for the rAF deployments with GSAP JS and came up with the post that exclusively discussed using TweenLite. So this was confusing and from there on, after switching to TweenMax, it behaved completely differently. Something that I didn't expect, GSAP AS3 has been my preference for years. To be honest the difference came across as a bug so maybe there's something that can be done about it other than changing the implementation in TweenMax that is well reasoned.

Thanks again for the great support!

jackdoyle commented 11 years ago

In version 1.8.3, I added a new conditional check so that if the dom element has a particular property defined, TweenLite/Max won't assume it's css-related. This means that if you update to 1.8.3, your original code should work just fine in both TweenLite and TweenMax. Thanks for pointing this out and helping us address something that will surely help some other folks.