Animated Google circles effect with TweenMax (greensock) and JQuery UI

A tutorial for creating an animated circle imitating Google+™ circles effect, which includes drag & drop (Jquery UI) functionality and the animating power of TweenLite/Max (greensock).

DEMO

Step 1 Installing libs

First off we need to setup a page and install the libraries that are needed, so we create the code below:


<!DOCTYPE html>

<html lang="en">

  <head>

    <title>HTML Animations</title>

    <!-- custom css -->

    <link rel="stylesheet" href="animationCircle.css" type="text/css" />

    <!-- greensock pack -->

    <script type="text/javascript" src="libs/greenshock/TimelineLite.js"></script>

    <script type="text/javascript" src="libs/greenshock/easing/EasePack.js"></script>

    <script type="text/javascript" src="libs/greenshock/TimelineMax.js"></script>

    <script type="text/javascript" src="libs/greenshock/TweenMax.js"></script>

    <!-- JQuery -->

    <script type="text/javascript" src="libs/jquery-1.7.1.js"></script>

    <!-- JQuery UI -->

    <script type="text/javascript" src="libs/jquery-ui-1.8.21.custom.min.js"></script>

    <!-- custom script -->

    <script src="animationCircle.js" type="text/javascript"></script>

  </head>

  <body>

  </body>

</html>


You can find these libraries in their homepages:


Step 2 Injecting the HTML Markup

Now that we have our libraries imported we can start placing the html.

First step is to create the box for the dragable color circles

so inside the body tag write the following



<div id="circlesArray">

    <div class="draggableCircles red" ></div>

    <div class="draggableCircles purple" ></div>

    <div class="draggableCircles green" ></div>

    <div class="draggableCircles black" ></div>

    <div class="draggableCircles yellow" ></div>

    <div class="draggableCircles pink" ></div>

</div>

This code created a container that has 6 children, and we are going to style them with CSS later on.

Then we create the circle that will animate as well as a circle with a label in the center.



<div id="mainCircleContainer">

  <div id="dragWindow"></div>

  <div id="smallCircle">

    <label id="circleLabel">Contacts</label>

  </div>

        

  <div id="outerCircle">

          

  </div>

</div>

Lets take a step back and see what we wrote here.

The mainCirlceContainer is the main container of all the circles.

The dragWindow div will be our placeholder for the drag&drop process, it will be invisible and only has use for the javascript.

The smallCircle div holds the label of the circle “contacts” or anything that we want to name the circle.

The outerCircle is our star here, this will animate and hold the circles that we add to it


Step 3 Adding some style

Simple html will get us nothing so let’s add some color and style with css.

Note: Because the css rules are quite extensive we will showcase here the main ones (the rest are either repetitions and are included in the source files).



.red{

  width: 70px;

   height: 70px;

   background: red;

   -moz-border-radius: 70px;

   -webkit-border-radius: 70px;

   border-radius: 70px;

   float:left;

}

#outerCircle{

   float:left;

   left:50%;

   top:50%;

   margin-left:-42px;

   margin-top:-42px;

   position:relative;

   width: 80px;

   height: 80px;

   background: #005FE6;

   -moz-border-radius: 80px;

   -webkit-border-radius: 80px;

   border-radius: 80px;

   border: 2px solid #00BBE6;

    z-index: 50;

}

.resultCircle{

  width:30px;

   height: 30px;

   background: #ffffff;

   -moz-border-radius: 30px;

   -webkit-border-radius: 30px;

   border-radius: 30px;

   float:left;

   position:absolute;

   opacity:0;

}

You are probably wondering where is the class resultCircle placed, well this class will be added to our newly created circles inside the outerCircle once we complete

our drag&drop functionality. What is important here is that the width and height in the css indicate the radius of the circle.

red is the class of one of the dragable circles, same rules (different color) applies for the rest of them.


Step 4 Scripting time

Now that we have some static shapes, build-up and styled, we can move on to adding some life into them.

Adding Events

In our animationCircle.js file we begin by declaring some events.



$(document).ready(function(){

  $("#mainCircleContainer").mouseover(function(){});

  $("#mainCircleContainer").mouseleave(function(){});
});

These events allows us to have control over the mainCircleContainer and its children when it accepts mouse interaction.

Animating the circle

We take things one step further by animating the outerCircle when the user mousses over it. So the above code changes to:



  $(document).ready(function(){

  $("#mainCircleContainer").mouseover(function(){

    TweenLite.to($("#outerCircle"), 0.4, {css: {width:146,height:146,marginLeft:-75, marginTop:-75}, ease:Power2.easeOut});

  });

  $("#mainCircleContainer").mouseleave(function(){

        TweenLite.to($("#outerCircle"), 0.5, {css: {width:80,height:80,marginLeft:-42, marginTop:-42}, ease:Expo.easeOut, delay:0.4, overwrite:"all"});

  });

});

What we are doing here is to call the TweenLite/TweenMax function and tell it to animate the properties of the outerCircle div in 0.4 seconds. Particularly here we make the circle expand outwards and centering itself in the process by modifying its margins.

When the mouse leaves the mainCircleContainer the TweenLite is called again to return outerCircle component to its former state, note the overwrite attribute which means that if two animations are happening at the same time this one is overwriting them and take precedence.

Readers with actionscript3 background might recognize this type of writing as TweenLite/Max is quite famous for its tweening engine for flash.

You can find complete reference of the TweenLite/Max declaration here:


Step 5 Adding Drag & Drop support

Some steps before we created a box with some nice colorful circles inside, it is time we add them some drag & drop functionality.

In our previous script we add the following lines:



  $(".draggableCircles").draggable({

      cancel: "a.ui-icon",

      revert: "invalid",

      helper: "clone",

      zIndex: 120,

      cursor: "move"

    });

  

  $("#mainCircleContainer").droppable({

    accept: ".draggableCircles",

    activeClass: "ui-state-highlight",

    drop: function( event, ui ) {

      

    }

  });

What we have just wrote is the expected behavior when we drag or drop one of these circles that we created, we define their functionality through the methods of JQuery UI.

So let’s go step by step and see what we have done.

  • Draggable options
  • cancel attribute means that by clicking an icon won’t initiate draging
  • revert attribute means that when not dropped, the item will revert back to its initial position
  • helper attribute create a small tooltip like element that notifies the user of the action happening
  • zIndex attribute allows the dragable item to hover on top of other elements
  • cursor attribute changes the cursor to show moving action
  • Droppable options
  • accept attribute indicates which class elements are accepted in the drop area
  • activeClass attribute indicates the class that is applied on the droppable area when hovered over while draging an item
  • drop defines a callback function that fires when we drop an item

You can find complete reference of the JQuery UI options here:


Step 6 Adding circles

Now that we have animations and drag and drop support let’s combine them all together.

The first step here is to create some extra functions, so we create the following:



function addCircle($item, fromDrop){

}

function calculatePositions(){

}

We then add the addCircle to the drop callback and add the functionality to the function



$("#mainCircleContainer").droppable({

    accept: ".draggableCircles",

    activeClass: "ui-state-highlight",

    drop: function( event, ui ) {

      <strong>addCircle( ui.draggable,true );</strong>

    }

  });

  

function addCircle($item){

    var id = $item.attr('class');

    id = String(id).split(' ');

    id = id[1];

    

    $('#outerCircle').append("<div id='"+id+"' class='resultCircle'></div>");

    

    calculatePositions();

    

  }

What we have done here is to add a function on the drop callback that gets called when we drop an item, and when this is done we append a small circle onto the outerCircle element. The trick with the id is because we have the following rule in the css:



#red{

  background:red;

}

At the end we call the calculatePositions function to re-arrange all the circles in the outerCircle element in a formatted manner.

Summing up the script

Up until this point the full script is:



  $(document).ready(function(){

  

  /* Make draging a reality */

  $(".draggableCircles").draggable({

      cancel: "a.ui-icon",

      revert: "invalid",

      helper: "clone",

      zIndex: 120,

      cursor: "move"

    });

  $(".draggableCircles").live('click',function(){

    addCircle($(this),false);

  });

  $("#mainCircleContainer").droppable({

    accept: ".draggableCircles",

    activeClass: "ui-state-highlight",

    drop: function( event, ui ) {

      addCircle( ui.draggable,true );

    }

  });

    
  $("#mainCircleContainer").mouseover(function(){

    TweenLite.to($("#outerCircle"), 0.4, {css: {width:146,height:146,marginLeft:-75, marginTop:-75}, ease:Power2.easeOut});

  });

  $("#mainCircleContainer").mouseleave(function(){

    TweenLite.to($("#outerCircle"), 0.5, {css: {width:80,height:80,marginLeft:-42, marginTop:-42}, ease:Expo.easeOut, delay:0.4, overwrite:"all"});

  });  

  // end of ready //

});

function addCircle($item){

  var id = $item.attr('class');

    id = String(id).split(' ');

    id = id[1];

    $('#outerCircle').append("<div id='"+id+"' class='resultCircle'></div>");

    calculatePositions();

}

function calculatePositions(){

}


Step 7 Adding mathematical juice

Distributing and animating the small circles.

As you might have noticed the small circles are placed one on top of the other and they have no animation or any sense of equal distribution within the outerCircle let’s see how we can change that.

The key here is how the inner circles will be distributed, to do that we need to calculate their position in the circle and then translate this position from polar coordinates(r,θ) into Cartesian ones (x,y)

So we add some code to the calculatePositions function



  function calculatePositions(){

  var radius  = 40+16; // offset here to add some spacing

  var num    = $("#outerCircle").children().length;

  var dividers  = 360/num;

  var center  = 58;

  var theta  = 0.0;

  for(var i=0;i<num;i++){

    var x0 = Math.round(center+radius*Math.cos(theta));

    var y0 = Math.round(center+radius*Math.sin(theta));

    $("#outerCircle :nth-child("+(i+1)+")").css({'left':x0,'top':y0});    

    var radians = dividers * (Math.PI / 180);

    theta +=  radians;

  }

  TweenLite.to($('#outerCircle .resultCircle'),0.2,{css:{autoAlpha:1,scaleX:1,scaleY:1}});

}

Pretty simple huh? Lets actually see what is going on here:

  • First off we create some variables that hold:
    • The radius, of the circle plus some offset for positioning
    • The dividers, that calculate how many degrees correspond to each circle
    • The num, which is the number of children (small circles) of the outerCircle element
    • The center, an offset that helps with internal positioning of the small circles
    • And the theta, which is the number of radians for each small circle
  • Next we iterate for each small circle and
    • Calculate the positions of each small circle in the Cartesian coordinates (x,y), more info
    • Apply these positions to the element
    • Convert degrees to radians and increase their number for the next iteration
  • When all small circles are distributed in their positions in the circle, we animate their visibility and their scale by calling TweenLite with autoAlpha and scaleX, scaleY attributes

Note: autoAlpha attribute animates opacity (alpha) and visibility as well.

Adding final touches

Now that we have all the math in place what is missing is to make the small circles dissapear when the circle “closes” and appear when it “opens”. To do that we add a callback function to the animation function that we created earlier



  $("#mainCircleContainer").mouseover(function(){

    TweenLite.to($("#outerCircle"), 0.4, {css: {width:146,height:146,marginLeft:-75, marginTop:-75}, ease:Power2.easeOut,onComplete:

      function(){

        calculatePositions();

      }

    });

  });

This allows the calculations to happen when the circle has finished animating, and it works even when we are draging an item on top of it.

And to make them dissapear we add the following TweenLite code



$("#mainCircleContainer").mouseleave(function(){

    <strong>TweenLite.to($('#outerCircle .resultCircle'),0.3,{css:{autoAlpha:0,scaleX:0.1,scaleY:0.1}});</strong>

    TweenLite.to($("#outerCircle"), 0.5, {css: {width:80,height:80,marginLeft:-42, marginTop:-42}, ease:Expo.easeOut, delay:0.4, overwrite:"all"});

  });  

This code makes the small circles become very small and finally dissapear in 0.3 seconds. Note that these two events happen sequentially and not in parallel, although we could also do that if we wanted, but it’s more impressive this way.


Step 8 Finalized code

The final script.



$(document).ready(function(){

  /* Make dragging a reality */

  $(".draggableCircles").draggable({

      cancel: "a.ui-icon",

      revert: "invalid",

      containment: $( "#demo-frame" ).length ? "#demo-frame" : "document",

      helper: "clone",

      zIndex: 120,

      cursor: "move"

    });

  $(".draggableCircles").live('click',function(){
    
    addCircle($(this),false);

  });

  $("#mainCircleContainer").droppable({

    accept: ".draggableCircles",

    activeClass: "ui-state-highlight",

    drop: function( event, ui ) {

      addCircle( ui.draggable,true );

    }
  });

  $("#mainCircleContainer").mouseover(function(){

    TweenLite.to($("#outerCircle"), 0.4, {css: {width:146,height:146,marginLeft:-75, marginTop:-75}, ease:Power2.easeOut,onComplete:

      function(){

        calculatePositions();
      }

    });

  });

  $("#mainCircleContainer").mouseleave(function(){

    TweenLite.to($('#outerCircle .resultCircle'),0.3,{css:{autoAlpha:0,scaleX:0.1,scaleY:0.1}});

    TweenLite.to($("#outerCircle"), 0.5, {css: {width:80,height:80,marginLeft:-42, marginTop:-42}, ease:Expo.easeOut, delay:0.4, overwrite:"all"});

  });  
});

function addCircle($item, fromDrop){

  var id = $item.attr('class');

    id = String(id).split(' ');

    id = id[1];

    $('#outerCircle').append("<div id='"+id+"' class='resultCircle'></div>");

    if(fromDrop){

      calculatePositions();

    }
}

function calculatePositions(){

  var radius     = 40+16; // offset here to add some spacing

  var num        = $("#outerCircle").children().length;

  var dividers    = 360/num;

  var center     = 58;

  var theta       = 0.0;

  for(var i=0;i<num;i++){

    var x0 = Math.round(center+radius*Math.cos(theta));

    var y0 = Math.round(center+radius*Math.sin(theta));

    $("#outerCircle :nth-child("+(i+1)+")").css({'left':x0,'top':y0});

    var radians = dividers * (Math.PI / 180);

    theta +=  radians;
  }
  TweenLite.to($('#outerCircle .resultCircle'),0.2,{css:{autoAlpha:1,scaleX:1,scaleY:1}});

}

Surely there must be many ways this can be extended, (adding mask to circles, images, text), but this is the main idea, I would love to hear real applications of this.

DEMO

I hope you’ve enjoyed this tutorial! Let me know what you think in the comment section below.

Facebooktwittergoogle_pluspinterestlinkedin
linkedin
Tagged , , , , . Bookmark the permalink.

3 Responses to Animated Google circles effect with TweenMax (greensock) and JQuery UI

  1. Rob says:

    Really good tutorial, there’s a little bug when the mouse leave animation is interrupted my mousing over again.

    https://docs.google.com/open?id=0B5i-3K7ouEzeZzFWakNQOHpQbG8

  2. coder says:

    Yes I see, a little tweaking with the timers should be able to fix this. Or perhaps by making sure the closing animation completes in any case.

  3. Awesome tutorial, I was looking for something like this, but I’ll be tweaking mine differently.

    Thanks for sharing. *thumbs up*

Leave a Reply

Your email address will not be published. Required fields are marked *