Advanced tutorial: CubeDial, a 3D Carousel made with GSAP

February 2nd 2015 | Carl
favorite

36232

The following is a guest post by Chris Gannon. Chris is the leading authority on using GSAP with Edge Animate. A veteran of the Flash world, Chris has been applying his animation and design skills to many cutting-edge HTML5 projects. We asked Chris to explain to our audience some of the techniques that he uses in his client work and top-selling components on CodeCanyon.net. The concepts he describes have many practical applications and can serve to radically transform how you approach complex projects. Be sure to explore the demos and study the source code. This is not intended to be a step-by-step tutorial.

I love 3D stuff and I'm always trying out interesting ways to add depth to my projects. In this article I'll talk about how the CubeDial below was made, the concepts surrounding its underlying mechanism and how some of the solutions I employ overcome some common issues. Ok, so let's get going.

Explore the CubeDial demo

In the demo below, spin the dial. Notice that spinning the dial spins the cube. You can also swipe the cube and the dial will spin. Both the cube and the dial spin using momentum-based physics. If you are really clever, you may notice that the cube isn't really a cube, as it has 6 front-facing sides.

See the Pen Gannon - Cube / Dial by GreenSock (@GreenSock) on CodePen.

What's it using under the hood?

The core functionality is handled by the GreenSock Animation Platform (GSAP). I always load TweenMax because it includes all the things I need in one script load: TweenLite, CSSPlugin, EasePack, timeline sequencing tools, etc.

I use TweenMax all over the place not only to immediately set CSS properties (using set()) but also to delay the setting of them, to tween them, and to trigger events not only when they start or stop but crucially whilst they're animating too.

Next up is Draggable - a very useful and flexible utility that I use in practically all of my projects now as most UIs need something dragged or moved.

Finally we add in ThrowPropsPlugin (and couple it up to Draggable) for that flick/throw/physics/inertia that we have all become so used to on our mobile devices.

So the three main GreenSock tools I will be using are: Draggable, TweenMax and ThrowPropsPlugin.

The Cube's Structure

A lot of you reading this will be visually led developers so below is a diagram of what's going on with the cube (ok it's a hexahedron I think as it has 6 sides).

image002

Each face of the 3D object is a <div> with a background image. Each <div> has its Z transformOrigin point set a fair bit away from the actual face (behind it) so that when its rotationY is animated it pivots left to right in perspective.

This diagram illustrates the 6 faces - their transformOrigin X and Y are simply set to the middle of the faces (50% 50%) but the crucial part is the transformOrigin Z position which is -200px. In the actual code I dynamically work out what that distance should be based on the number of faces but to keep the diagram simple, I use -200px.

The dotted center is that value (-200px) and once that's set each face will appear to swing around a point 200px behind itself when you tween its rotationY. By spinning each face around the same point, we achieve the illusion of the entire cube spinning around its center.

To programmatically figure out the rotational offset of each face I use this equation:

rotationY: (360/numFaces) * i;

What wizardry is used to make a 6-sided object look like a cube?

image003

There's a simple answer to this and to demonstrate what's going on I have coded it so that all the faces become slightly transparent when you drag the cube. Try dragging and then holding it halfway through a drag - you'll see the other faces are distorted behind (see sceenshot on left). That's because the transformPerspective on each face is set fairly low (meaning exaggerated) in order to 'bend' the other faces behind.

I've also added a slider to help illustrate this in the demo at the top of the page. As you drag the slider, the faces' transformPerspective is set higher and higher to the point where if the slider is fully to the right the perspective is so flat that the cube looks more like an infinite slide show. Try dragging it halfway then spinning the dial or the cube.

Creating the dial

In simple terms, the dial is just a png with some divs with some numbers in them. I do a little loop based on the number of sides in the cube to generate those divs and position them over the dial image.

To make the dial "spin-able" literally takes one line of code using GSAP's Draggable.

myDialDraggable = Draggable.create(dial, {
    type:'rotation',
    throwProps:true // for momentum-based animation
})

That's really all you need to spin something. Amazing.

However, the dial I use for this project is a little more advanced. I've isolated some of the dial's code in the demo below.
Take note of how the numbers stay vertically oriented as the dial spins.

Spin the dial

See the Pen Gannon - Dial Only by GreenSock (@GreenSock) on CodePen.

In order to keep the numbers upright I use Draggable's onUpdate callback to call a function that adjusts the rotation of each number as the dial spins. Checkout the setNumberRotation() function in the JS source code above.

So now that I've discussed a bit of how the cube and dial are constructed let's get to the heart of the matter, linking the cube to the dial so that they work together in harmony.

The null object that ties it all together

Both the cube and dial are UI input mechanisms so it's wise to have a third element storing their position/transform data. That way there's only ever one unified point of reference for the whole shebang.

If you've ever used After Effects you may have come across null objects (some people call them proxies) - it's nothing more than an invisible object that can have properties applied to it. This object then becomes a central place whose properties can be applied to other elements and graphics. This concept means that any element that is assigned to that null object can be manipulated centrally rather than each element having its properties assigned individually. So even though they might sound complicated I assure you they're not - a null object is just a 'thing' that generate values that can be applied to other (multiple) things.

In this project the null object is nothing more than a <div> that moves left and right on the X axis (whose position is absolute and width and height are 0px so it's invisible). Its X position is used to control two things - the rotation of the dial (if the 3D object is dragged) and the rotationY of the 3D object's faces. It might seem odd to use the X position to control rotation properties but really an X position is just a value that Draggable is happy to use.

Let's take a look at how the dial's callbacks "talk to the null object" and how the null object controls the cube.

//this is the onUpdate function on the dial's Draggable - it is used by its onDrag and onThrowUpdate functions (when you drag and when you throw the dial)
 
function onDialDrag(){
    //this bit normalises it so it never goes past fullRotation (which is 360)
    dial._gsTransform.rotation = dial._gsTransform.rotation % fullRotation;
    //tell the null to move in the X axis based on the dial's rotation and call the onCubeDrag function whilst it's doing it
    TweenMax.set(nullObject, {
        x:dial._gsTransform.rotation, // current rotation of the dial
        onUpdate:onCubeDrag // function that rotates faces of cube
 });
           
     //this tells the numbers on the dial to stay the right way round as it we spin (it's the details that count eh)
setNumberRotation();
          
}

Likewise, the cube's Draggable will set properties on the null object that will then be applied to the dial. Using this model we could add functionality that allows you to blow into a microphone, set a value on the null object based on volume, and the cube and dial would both spin. Crazy!

Simplified null object demo

Here's a demo to demonstrate the null object's mechanism a bit more visually (albeit just in 2D):

See the Pen Greensock Proxy/Null Object Demo by GreenSock (@GreenSock) on CodePen.


Using this method keeps everything in sync and it allows for multiple UI inputs - the null object is always controlled by user interaction and its X position is used to determine the rotation value of the dial (if the cube is dragged) and rotationY value(s) of the faces (if the dial is dragged). You can also use it to work out which face is at the front and because Draggable has the brilliant snap function you can ensure that when you release your drag/throw on either the 3D element or the dial it will always animate the null (and consequently all dependent objects) to a position where a face is flat on. Once it's come to rest you can also fire an onComplete event and have something happen - you might want the active/front face to load an iframe or animate its content. Or maybe you'd like a sound to play or you might want to perform a calculation based on the X position of null. Examples of using onComplete to trigger an animation when the spin is complete can be seen in demos for EdgeRotater and EdgeDial.

Interacting with the 3D cube

Unlike the simplified 2D demo above, grabbing and throwing the cube is a little more involved. The secret here is that you aren't directly touching the cube at all. In fact it would be literally impossible to effectively drag the cube by a face as the face would eventually disappear in to the distance of 3D space and overlap with other faces. It would be extremely difficult to assess which face receives the touch / mouse input for dragging.

To solve this issue a Draggable instance is created that has the null object as its target and uses the <div> that contains the faces of the cube as its trigger. In simple terms this means that any time you click and drag on the div containing the cube it controls the x position of the null object, which in turn sets the rotation of each face of the cube and the rotation of the dial.

Its sort of like interacting with a touch screen. There is a piece of glass between you and the UI elements you tap. Where you tap on the screen dictates which UI elements respond to your input. In the CubeDial, the div that contains the cube is like the glass screen of your phone. As you move your finger over the container, the app tracks your motion and applies the new values to the null object.

Wrap up

Ok that's enough of the complexities - it's hopefully not that complicated when you play around with it and adjust some values and see how things react. And if you're not already familiar with this kind of mechanism, once you've got your head around it you'll probably find you use it everywhere as it can be applied in pretty much all of your interactive projects.

So that's all for now - I hope you found some (if not all) of this article interesting and/or informative. Admittedly it introduces the concept of null objects using a fairly complex example but it really doesn't have to be complex (or 3D). The 2D null object demo above might be a great place to start if all of this is pretty new to you as it uses a null object at its most basic level.

Dive into the entire source code of the CubeDial Demo.

My first draft of this article was peppered with gushing compliments regarding GSAP and I was told to tone it down a bit and maybe leave them until the end. So here it is (it's toned down a bit because I'm quite an excitable person!).

GSAP rocks my world and the world of all my clients. If you aren't using it yet you are potentially missing out on one of the best (if not the best) animation platforms for JavaScript/CSS3. Its flexibility, ease of use and performance is light years ahead of anything else and if you're not using it and are curious then I heartily recommend you dive in and see for yourself. Jack has created amazing tools for designers and developers like us and Carl does an extraordinary job of explaining how they work in a simple, relevant and, most importantly, usable way.

Happy tweening!


Get GSAP

Version: 2.0.2 updated 2018-08-27

Core

    Extras

      Plugins

        By using GreenSock code, you agree to the terms of use.

        For an all-access pass to premium content

        Join Club GreenSock