Sunday, October 21, 2007

Javascript Animation : Curving the path (7)

We've seen that we could define the core concept of an animation thru a linear equation that took basic elements like time, begin, end, change.
We have changed the equation to create some easing effects, making the animation a lot more interesting.
But there is one thing that we cannot do : describe a curve when going from point A to point B.
Let's see how to implement a curved path!

Curve by example


I guess it's easier to understand what we are talking about if we have a grasp of the final result.
First, this is what we are able to do know:
We can go from y0,x0 to y1,x1 or from top0, left0 to top1, left1.
Even if we use an easing effect, going from point A to B will describe a straight line:

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Go up
As you see, the easing effect is applied and the container goes from point A to B in a straight line.
Here is the code to create the effect:
function goUp() {
 elm=document.getElementById('goup');
 var ops1={
   backgroundColor:{start:'#000000',end:'#FF0000'},
   borderColor:{end:'#000000',start:'#FF0000'},
   top:{start:0,end:-100},
   left:{start:0,end:250}
 };
 animate(elm,ops1,1500,20,easeInCirc);
}
So we get the element,set the css properties we want to animate and then specify an animation of 1 second and a half with an easeInCirc effect.

That's find, but it could be nice if we could control the path going from top.0,left.0 to top.-100,left.250.
We can set an in-between point that will 'attract' the animation.
we have 3 divs/square on the screen here:
The beginning square,
The attracting square,
The final square
You can set different value for the attracting point and see how the animation will behave:

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Attraction Point:
top:
left:
animate with curve

As you can see, the animation will not go straight to the final point but will describe a curve according to the attracting point you have set!
We can now have little control on the path!
But let's see how it works.

Going back to the root : y=mx+b


As you certainly remember, we've seen that a linear displacement from one value to an other could be resumed to one equation:
value =(change/duration)*frame + start
or
y=mx+b
Where m is (change/duration) or c/d

In fact, we can rewrite this linear equation so that it becomes the root of an other equation : the Bezier equation.
Let's rewrite it:
value = begin+(begin+change-begin)*(frame/totalframes)
If you look at the changement, you will see that we do we take a deturned path:
change = begin+change-begin
Which is not very helping in here!
In fact, this is the basic equation for a linear Bezier curve!
We can rewrite it like so:
value = begin+(end-begin)*(frame/totalframes)
or
var t=frame/totalframes
var b=begin
value=begin+(end-begin)*t

In the Bezier curve, t must always be within 0 and 1 and this will always be true for how frame/totalframes.
The upper equation can be rewritten again:
value = (1-t)*begin + t*end

This is an other way of creating a linear easing!

From linear to Quadratic Bezier curve


As for now, we were just rewritting a linear equation to an other linear equation!
In fact, we can add an other point, like we did in the animation above and this will allow us to curve the path.
We will need p0,p1 and p2 where p1 is the deviator point:
value= (1-t)*(1-t)*p0 + 2*t*(1-t)*p1 + t*t*p2
In our case, with javascript variable name, this will give us:
var t=frame/totalframes;
value = (1-t)*(1-t)*begin + 2*t*(1-t)*deviator + t*t*end;

Let's see the Bezier curve with a javascript implementation:
function linearBezier(frame,begin,end,totalframes) {
      var t=frame/totalframes;
      return (1-t)*begin + t*end;
}
function QuadBezier(frame,begin,end,deviator,totalframes) {
    var t=frame/totalframes;
    return (1-t)*(1-t)*begin + 2*t*(1-t)*deviator + t*t*end;
}

Implementing the Bezier Curve


Now that we know how to curve the path thru a Bezier curve, we just need to see how we can implement this in our animate function.
First, we need to add a third point, a deviator point when setting the values.
A simple way of doing it,will be to set the deviators like so:
 var ops1={
     backgroundColor:{start:'#000000',end:'#FF0000'},
     borderColor:{end:'#000000',start:'#FF0000'},
     top:{start:0,end:-100,bezier:-300},
     left:{start:0,end:450,bezier:-100}
 };
 animate(elm,ops1,1500,20,easeInCirc);
I have written it bezier because this is a common denomination that you can find in photoshop, illustrator, Flash...
The Bezier curve is used in many graphic applications so let's use a standard name!
Now, let's add this bezier into the animate function:
function animate(elm,props, duration, fps,easing) {

 duration = (duration) ? parseFloat(duration) : 1000;
 fps      = (fps)      ? parseFloat(fps)      : 20;
 easing   = (easing)   ? easing               : linearEase;

 var interval    = Math.ceil(1000/fps);
 var totalframes = Math.ceil(duration/interval);

 for(i=1;i <= totalframes;i++) {
   (function() {
      var frame=i;
      displacement=function() {
          for(var prop in props){
              if(!/olor/.test(prop)) {
                  var begin = props[prop].start*100;
                  var end   = props[prop].end*100;
    var bezier = props[prop].bezier*100;
    var actualDisplacement=
                      easing(frame, begin, end-begin, totalframes);
    if(bezier) 
                    actualDisplacement=
                    QuadBezier(frame,actualDisplacement,end,bezier,totalframes);
           setStyle(elm,prop,actualDisplacement/100;
               } else {
                    var b = hexStr2rgbArray(props[prop].start);
                    var e = hexStr2rgbArray(props[prop].end);
      var rgb=[];
                    for(j=0;j<3;j++) 
                    rgb.push(parseInt(easing(frame, b[j], e[j]-b[j], totalframes)));
                    setStyle(elm,prop,'rgb('+rgb.join(',')+')');  
        }
   }
       }
       timer = setTimeout(generalProperty,interval*frame);
   })();   
 }
}
So what do we do?
We first calculate the actual value of the property according to the easing type.
If we find that a bezier property exists,we use the value calculated by the easing type as the start for the bezier equation !
That's all!

A new effect thru Bezier : backAndForth


In the above example, we need to supply a value for the bezier curve to happen, but we can always calculate the bezier point by manipulating the beging and end value!
Let' see the code:
function backAndForth(frame,begin,change,totalframes) {
 var t=frame/totalframes;
 var end=begin+change;
 return (1-t)*(1-t)*begin + 2*t*(1-t)*(end+change) + t*t*begin;
}
As we are using the backAndForth function as an easing effect, we are not getting the end directly but the change so we need to get back the change.
Here I've decided that the deviator will be the end+change and that the final point will be the beginning!
Let's see what we get with the following settings:
 var ops1={
     backgroundColor:{start:'#000000',end:'#FF0000'},
     borderColor:{end:'#000000',start:'#FF0000'},
     top:{start:0,end:-100},
     left:{start:0,end:450},
     borderWidth:{start:5,end:1}
 };

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
back and forth
As you can see, the animation goes to the end value and goes back to where it started!
This is not really an easing effect but more an effect in itself but this can be useful!

What happens if we mix the backAndForth easing with some bezier points??
Until up to now, we've been applying the bezier points only on the top or left property but can we apply the bezier points to other properties?
Let's try, with the following settings:
 var ops={
     backgroundColor:{start:'#000000',end:'#FF0000'},
     borderColor:{end:'#000000',start:'#FF0000'},
     top:{start:0,end:-100,bezier:-300},
     left:{start:0,end:450,bezier:200},
     borderWidth:{start:5,end:1,bezier:80}
 };
 animate(elm,ops,1500,20,backAndForth);
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Go back and forth with bezier
Well, this is not going back and forth anymore!
The bezier equation is last in the function so it will reach the final destination and the backAndForth will create a kind of easing effect.
If we inverse the order in which bezier/easing is calculated, we will get an other effect...
We notice that the value set in the border-width can be used as a simple intermediate value for the animation too!

Conclusion

We have seen a basic way of implementing the a quadratic bezier curve in our animation process and we have seen that playing around with the value used in the bezier can allow some unexpected effects!
But as always, drawbacks are numerous:
- we can set only one bezier point per property, most of the time cubic bezier is used, which allows to set up 2 points per property. We could even find a way to set as many as bezier points as we like!
- we cannot add a bezier points to the colors as this will require a different calculation.
- we modified the animation directly which could lead to some weird bugs if we were to add more features (several beziers, beziers to colors...).
The core code is starting to be huge and it will certainly be better to find a way to improve some functionalities of the animate function thru callbacks for example.
Therefore we could keep the basic function simple in its process and allow us to extend its possibilities without fearing to create bugs or to create a monster function!
After all, we may not need the bezier possibilities so why put it in the core of the function?
Obviously I have specified other drawbacks in the previous entry and they still hold true!
But anyway, enjoy the function as for now and try to create your own effects based on all the tools you have!

1 comment:

Anonymous said...

Nice demo...good to see some downloads as well...Really great