Select Page

The Setup

Before I was a ‘modern web application developer’, I actually learned how to program in Flash and then Flex. Platform rhetoric aside, the Flash guys are the original gangstas when it comes to slick, animated user interfaces. In this post, I want to show you a technique for doing smooth rollovers that I have been using for years on a Flash timeline and how I was able to replicate it with Greensock’s TimelineLite with surprisingly little effort. The idea is that you lay your animations out on a ‘timeline’ and when the user mouses over the element the timeline begins to play and on mouse out the timeline plays the timeline in reverse. This creates a really nice animation by having the outro animation be the exact opposite of the intro animation. Mouse over the demo below for an example.

Demo

Download the code below to play with my idea and post your result in the comments for ‘valuable prizes’.

Code

Include the Javascripts

To make this animation work, we need to include jQuery, TweenMax and AngularJS in our HTML file. The entire animation effect is going to be encapsulated into a directive that we will put in our app.js file.

<body>
    <!-- Omitted -->

<pre><code><script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.13.1/TweenMax.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.0/angular.min.js"></script>
<script src="js/app.js"></script>
</code></pre>
</body>

Create the Smooth Button Template

Because this is a fairly focused and small example, I wanted to abstract the button HTML to keep the DOM clean but I didn’t really want to create an external template since this is not a ‘real’ project. AngularJS allows you to define a template in a script tag with the type of text/ng-template and a string id to identify it later. In this case, we are going to call it smooth-button.tmpl.html which we will reference in the smoothButton directive in a moment.

<body>
    <!-- Omitted -->

<pre><code><script type="text/ng-template" id="smooth-button.tmpl.html">
    <div class="circle red"></div>
    <div class="circle orange"></div>
    <div class="circle yellow"></div>
    <div class="circle grey"></div>
</script>

<!-- Omitted -->
</code></pre>
</body>

The layout for the button is essentially four colored circles that are going to lay on top of each other. To see the circles individually, take off the position: absolute on the circle class and you will have something that looks like the image below.

Timeline Shapes

.circle {
    border-radius: 50%;
    width: 100px;
    height: 100px;
    position: absolute;
}

.yellow {
    background-color: #ffff00;
}

.orange {
    background-color: orange;
}

.red {
    background-color: red;
}

.grey {
    background-color: grey;
}

We are also giving each circle a color by assigning a color class to it as well.

Create the Smooth Button Directive

Now that we have the template defined, we can create the smoothButton directive and reference it in the directive definition object (DDO) via templateUrl: ‘smooth-button.tmpl.html’. One of the interesting ‘features’ of AngularJS directives is that they share a single scope object by default. This is why I have defined scope: true on the DDO so that each directive instance gets its own child scope. This allows us to modify one directive without it updating all of the other directives. #proTip!

var app = angular.module('website', []);

app.directive('smoothButton', function(){
    var linker = function (scope, element, attrs) {
        // Omitted
    };

<pre><code>return {
    scope: true,
    link: linker,
    templateUrl: 'smooth-button.tmpl.html'
}
</code></pre>
});

Define the TimelineLite Instance

And now we are going to create a TimelineLite instance on the tl variable so that we can act upon it when the user interacts with the directive. Once tl is defined, we are going to call tl.stop() so that the tl does not automatically play the animation sequence. We are also going to expose two methods on scope called scope.play and scope.reverse that act as a proxy to tl.play and tl.reverse, respectively. This will allow us to control the playback of the animations that we add to the TimelineLite instance.

var app = angular.module('website', []);

app.directive('smoothButton', function(){
    var linker = function (scope, element, attrs) {
        var tl = new TimelineLite();
        // Omitted
        tl.stop();

<pre><code>    scope.play = function() {
        tl.play();
    };

    scope.reverse = function() {
        tl.reverse();
    };
};

return {
    scope: true,
    link: linker,
    templateUrl: 'smooth-button.tmpl.html'
}
</code></pre>
});

Smooth Button Enter Stage Left

We will now add three smoothButton instances to our layout by creating three div tags and adding smooth-button as an attribute. Remember that AngularJS converts camel case to snake case in HTML which is why it went from smoothButton to smooth-button.

<body>

<div smooth-button class="container" ng-mouseenter="play()" ng-mouseleave="reverse()"></div>
<div smooth-button class="container" ng-mouseenter="play()" ng-mouseleave="reverse()"></div>
<div smooth-button class="container" ng-mouseenter="play()" ng-mouseleave="reverse()"></div>
<pre><code><!-- Omitted -->
</code></pre>
</body>

And now we can invoke the play method by calling ng-mouseenter=”play()” and the reverse method by calling ng-mouseleave=”reverse()”.

Grand Finale: Show Me The Animations!

And now the only thing left to do is add the animations. TimelineLite makes this really easy by providing an add method that we can use to add our animations. We are going to use TweenLite.to to animate the red, orange and yellow circles. Because element is a jQuery object, we can query element to get us the circle we want such as element.find(‘.red’) to fetch the red circle. We will set the duration of all three animations to 0.4 seconds to keep things lively and we are going to set a negative delay on the last two animations by adding ‘-=0.2’ as the last parameter. Timeline animations are generally sequential but you can cause them to overlap with a negative delay. #proTip!

Now that we know WHO we are animating and WHEN it is all going down, it is time to define the WHAT. For each circle, we want to increase width and height by some number and we can do this with scaleX and scaleY. Also, we want to make it pretty so we use Power2.easeOut as our easing function. Hooray!

var app = angular.module('website', []);

app.directive('smoothButton', function(){
    var linker = function (scope, element, attrs) {
        var tl = new TimelineLite();
        tl.add(TweenLite.to(element.find('.red'), 0.4, {scaleX:1.8, scaleY:1.8, ease: Power2.easeOut}));
        tl.add(TweenLite.to(element.find('.orange'), 0.4, {scaleX:1.6, scaleY:1.6, ease: Power2.easeOut}), '-=0.2');
        tl.add(TweenLite.to(element.find('.yellow'), 0.4, {scaleX:1.4, scaleY:1.4, ease: Power2.easeOut}), '-=0.2');
        tl.stop();

<pre><code>    scope.play = function() {
        tl.play();
    };

    scope.reverse = function() {
        tl.reverse();
    };
};

return {
    scope: true,
    link: linker,
    templateUrl: 'smooth-button.tmpl.html'
}
</code></pre>
});

And the HTML in its entirety.

<body>

<div smooth-button class="container" ng-mouseenter="play()" ng-mouseleave="reverse()"></div>
<div smooth-button class="container" ng-mouseenter="play()" ng-mouseleave="reverse()"></div>
<div smooth-button class="container" ng-mouseenter="play()" ng-mouseleave="reverse()"></div>
<pre><code><script type="text/ng-template" id="smooth-button.tmpl.html">
    <div class="circle red"></div>
    <div class="circle orange"></div>
    <div class="circle yellow"></div>
    <div class="circle grey"></div>
</script>

<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.13.1/TweenMax.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.0/angular.min.js"></script>
<script src="js/app.js"></script>
</code></pre>
</body>

Review

For a quick review, we were able to encapsulate an interesting animation within a directive and then control playback via the TimelineLite API. In this example, we used circles but you could easily change this by defining other shapes via CSS and swapping them out.

One of my primary goals as I share the things that I have learned is to not only present them in such a simple way that anyone can get them but also leave the stage wide open for you to extend my ideas into anything you want. So for homework, grab the code and post a variation. I would love to see it!

Resources

Getting Started with the JavaScript Version of the GreenSock Animation Platform (GSAP)

Greensock TimelineLite Documentation

Code