Example: Animating Along a Curved Path

This demonstrates how to animate the position of an element along a curve.

Click the button or drag the dots to see the animation. You can use this example as a tool to find the right XY values for start, end, and control points that will give you the right shape path for your own animation on a curve.

The curved line shown here between points 0 and 3 is just for preview purposes, and is not part of animation on a curve. The draggable points 0 - 3 are displayed only for UI interactivity.

To see anim along a path, click the button above, or drag point 0, 1, 2, or 3.

The X and Y of point 0 is [92,103]

The X and Y of 1, 2, and 3, are the sub-arrays in array value of "curve:" in the anim.set()

anim.set('to', {curve: [ [147,317], [532,179], [538,288] ]});

0
1
2
3
A

Setting up the HTML

First we add some HTML to animate.

<div class="example">
    <button id="btn-animate" class='yui3-button'>Animate On Path</button>
    <div id="demo">A</div>
</div>

Creating the Anim Instance

Now we create an instance of Y.Anim, passing it a configuration object that includes the node we wish to animate.

var demoA = Y.one('#demo');

var anim = new Y.Anim({
    node: demoA,
    duration: 1.5,
    easing: Y.Easing.easeOut
});

Changing Attributes

A click handler will set the to value before run is called.

var startAnim = function(e) {
    anim.set('to', {
        curve: [ [x1, y1], [x2, y2], [x3, y3] ] // Where 1 and 2 are curve control points, and 3 is the end point.
    });
    anim.run();
};

Running the Animation

If the animation will be run multiple times, you'll need to reset the position of the animated element.

Finally we add an event handler to run the animation.

var resetToAnimStart = function(){
	demoA.setStyles({'left': x0, 'top': y0}); // Where x0, y0 is the animation starting point  
}

Y.one('#btn-animate').on('click', function(){
	resetToAnimStart();
	startAnim();
});

Complete Example Source

The code shown above does the basics. This example however, allows dragging the points, displays xy values for each point while you drag, and displays a path preview of the animation using YUI Graphics Utility. This adds some complexity.

<style>
.example {
    position: relative;
    padding: 0;
    margin: 0;
    width: 100%;
}

.example div, .example p, .example span{
    padding: 0;
    margin: 0;
}

#mygraphiccontainer {
    width: 650px;
    height: 400px;
}

.example #demo {
    position: absolute;
    left: 92px;
    top: 103px;
    background-color: #f00;
    text-align: center;
    line-height: 1.5em;
    border: solid 1px #cc0000;
    border-radius: 0;
    -moz-box-shadow: 3px 3px 7px rgba(0,0,0,0.4);
    -webkit-box-shadow: 3px 3px 7px rgba(0,0,0,0.4);
    width: 20px;
    height: 20px;
}

.dot {
    position: absolute;
    color: #396491;
    font-size: 20px;
    height: 0;
    line-height: 12px;
    opacity: 0.8;
    width: 0;
    cursor: move;
}

.dot .point { 
    background-color: #000;
    position: absolute;
    left: -3px;
    top: -3px;
    width: 6px;
    height: 6px;
    border-radius: 3px;
    font-size: 0px;
}

.number-label {
    position: absolute;
    top: -1em;
    left: -0.9em;
    width: 1em;
    height: 1em;    
    line-height: 1em;
}

/* Gives the points a larger target area to drag */
.dot .fat-finger {
    position: absolute;
    top: -30px; /* minus half the fat-finger height */
    left: -30px;
    width: 60px;
    height: 60px;
    -moz-border-radius: 30px;
    -webkit-border-radius: 30px;
    border-radius: 30px;
    background-color: #abc;
    opacity: 0.3;
    filter: alpha (opacity = 30);
}

#dot-3 .fat-finger {
    background-color: #f80;
    opacity: 0.4;
}

#dot-0 .fat-finger {
    background-color: #8DAA16;
    opacity: 0.4;
}

#dot-0, #demo {
    top: 103px;
    left: 92px;        
}

#dot-1 {
    top: 317px;
    left: 147px;        
}

#dot-2 {
    top: 179px;
    left: 532px;        
}

#dot-3 {
    left: 538px;
    top: 288px;
}

#info {
    position: absolute;
    width: 450px;
    height: 15em;
    right: 1em;
    top: 1em;
}

code span {
    color: #CC3300;
}

.point {
    font-size: 24px;
    left: 5px;
    position: absolute;
    top: -5px;
}

</style>
<!-- In order for the script to run, this HTML markup must be nested in a <div class="example"> --> 
    <div id="mygraphiccontainer"></div> <!-- Container for the preview of the curve line -->
    <div id="info">
        <button id="btn-animate" class='yui3-button'>Animate On Path</button>
        <p>To see anim along a path, click the button above, or drag point 0, 1, 2, or 3.</p>
        <p>The X and Y of point 0 is <code>[<span class="arr0">92,103</span>]</code></p>
        <p>The X and Y of 1, 2, and 3, are the sub-arrays in array value of "curve:" in the anim.set()</p>
        <p><code>anim.set('to', {curve: [
        [<span class="arr1">147,317</span>],
        [<span class="arr2">532,179</span>],
        [<span class="arr3">538,288</span>] ]});</code></p>
    </div>
    <div id="dot-0" class="dot zero">
        <div class="point"></div>
        <div class="fat-finger" title="Drag to change start point"></div> <!-- Gives the points a larger target area to drag -->
        <div class="number-label">0</div>
    </div>
    <div id="dot-1" class="dot one">
        <div class="point"></div>
        <div class="fat-finger" title="Drag to change path"></div>
        <div class="number-label">1</div>
    </div>
    <div id="dot-2" class="dot two">
        <div class="point"></div>
        <div class="fat-finger" title="Drag to change path"></div>
        <div class="number-label">2</div>
    </div>
    <div id="dot-3" class="dot three">
        <div class="point"></div>
        <div class="fat-finger" title="Drag to change end point"></div>
        <div class="number-label">3</div>
    </div>
    <div id="demo">A</div>
    
<script>
YUI().use('anim', 'dd-drag', 'graphics', 'cssbutton', function(Y){

    var mygraphic = new Y.Graphic({render:"#mygraphiccontainer"}),
        origin = Y.one('.example'), // The XY values for the animation are based on the upper-left corner of this element
        demoA = Y.one('#demo'), // The animated element
        dotList = Y.all('.dot');
        // Draggable points
        dot0 = Y.one('#dot-0'), dot1 = Y.one('#dot-1'), dot2 = Y.one('#dot-2'), dot3 = Y.one('#dot-3'),
        
        // Array of XY arrays of draggable points
        arrDot = [ 
            [parseInt(dotList.item(0).getStyle('left'), 10), parseInt(dotList.item(0).getStyle('top'), 10)], 
            [parseInt(dotList.item(1).getStyle('left'), 10), parseInt(dotList.item(1).getStyle('top'), 10)], 
            [parseInt(dotList.item(2).getStyle('left'), 10), parseInt(dotList.item(2).getStyle('top'), 10)], 
            [parseInt(dotList.item(3).getStyle('left'), 10), parseInt(dotList.item(3).getStyle('top'), 10)] 
        ],
        
        // Make points draggable
        dd0 = new Y.DD.Drag({
            node: '#dot-0'
        }),
        dd1 = new Y.DD.Drag({
            node: '#dot-1'
        }),
        dd2 = new Y.DD.Drag({
            node: '#dot-2'
        }),
        dd3 = new Y.DD.Drag({
            node: '#dot-3'
        });

    // Puts current XY values of points into displayed code snippet
    var updateCodeSnippetValues = function(){
        Y.one('.arr0').setHTML(arrDot[0][0] + ',' + arrDot[0][1]); // Start       
        Y.one('.arr1').setHTML(arrDot[1][0] + ',' + arrDot[1][1]); // Control point 1     
        Y.one('.arr2').setHTML(arrDot[2][0] + ',' + arrDot[2][1]); // Control point 2       
        Y.one('.arr3').setHTML(arrDot[3][0] + ',' + arrDot[3][1]); // End        
    }
    
    /** 
     * Stops the animation
     * Updates the array of point XY values      
     * Updates the curve preview
     * Updates displayed XY point values in code snippet
     */
    var dotDragHandler = function(dot){
        Y.Anim.stop();
        arrDot[dotList.indexOf(dot)] = [parseInt(dot.getStyle('left'), 10), parseInt(dot.getStyle('top'), 10)];
        drawCurve();
        updateCodeSnippetValues();
    }
    
    dd0.on('drag:drag', function(e){
        dotDragHandler(this.get('dragNode'));
    });
    dd1.on('drag:drag', function(e){
        dotDragHandler(this.get('dragNode'));
    });
    dd2.on('drag:drag', function(e){
        dotDragHandler(this.get('dragNode'));
    });
    dd3.on('drag:drag', function(e){
        dotDragHandler(this.get('dragNode'));
    });
    
    // button handler
    Y.one('#btn-animate').on('click', function(){
        Y.Anim.stop();
        setTimeout(startAnim, 500);
    });
      
    Y.all('.dot').on('mouseup', function(e){
        setTimeout(startAnim, 500);
    });

    // Create the animation instance
    var anim = new Y.Anim({
        node: demoA,
        duration: 1.5,
        easing: Y.Easing.easeOut
    });
    
    /**
     * Sets the anim curve values with the XY values from the arrDot array
     * Adds the origin offset values because anim works off page coordinates
     */
    var startAnim = function(e){
        var oX = origin.getX(),
            oY = origin.getY();
        
        // Reset the animated element to the start position. 
        // This is needed for running the animation multiple times
        demoA.setStyles({'left':arrDot[0][0], 'top':arrDot[0][1]});
        
        anim.set('to', {
            curve: [ [(arrDot[1][0] + oX), (arrDot[1][1] + oY)], [ (arrDot[2][0] + oX), (arrDot[2][1] + oY) ], [ (arrDot[3][0] + oX), (arrDot[3][1] + oY) ]  ]
        });
        anim.run();
    };
    
    // Adds a YUI Graphics path shape to the Graphics instance mygraphic
    var animPath = mygraphic.addShape({
        type: "path",
        stroke: {
            weight: 4,
            color: "#aabbcc"
        }
    });
    
    // Draw a preview curve with the Graphics animPath shape to match the Anim curve
    var drawCurve = function(){
        animPath.clear();
        animPath.moveTo(arrDot[0][0],arrDot[0][1]);
        animPath.curveTo(arrDot[1][0],arrDot[1][1],   arrDot[2][0],arrDot[2][1],    arrDot[3][0],arrDot[3][1]);
        animPath.end();
    }

    drawCurve(); // Initial drawing of the preview curve
    updateCodeSnippetValues(); // Initial setting of code snippet XY values

});

</script>