JavaScript实现动画插件

在这之前,大家应该了解了缓动函数(Easing Functions)的概念:

动画的每一帧需要计算一次元素样式,如果样式改变则需要重绘屏幕。细一点讲,当我们每调用一次计时器函数,需要通过向缓动函数传入一些动画上下文变量,从而获取到元素的某个样式在当前帧合理的值。

我所了解的缓动函数实现方式有两种,一种是tbcd方式(Robert Penner's Easing Functons)

 

function(t,b,c,d){

  return c*t/d + b;

}

 

t: timestamp 以毫秒(ms)为单位,指从第一帧到当前帧所经历的时间
b: beginning position,变量初始值
c: change 变量改变量(即在整个动画过程中,变量将从 b 变到 b + c)
d: duration 动画时间

另一种是mootools的单参数方式,由于我没了解过,这里就不说了,这里主要说一下第一种方式。

整个动画模块为Animation,其接受多个参数(元素, 动画样式, 持续时间[, 缓动函数名][, 回调函数]),是一个构造函数,调用方式为:

 

var animation = new Animation(test, {width: {value: "500px"}, 500, "sin", function(){

  console.log("complete");

});

animation.stop();

 

其中,每个样式属性可单独指定持续时间与缓动函数名,但回调函数必须等到所有动画结束才调用。

 

Animaion模块定义如下:

 

  1 var Animation = function(){

  2 

  3     var debug = false,          //如果debug,遇到异常将抛出

  4     unit = {},               //样式存取函数,详见下方each函数

  5     fx = {                   //缓动函数

  6         linear: function(currentTime, initialDistance, totalDistance, duration){    //自带一个线性缓动函数

  7             return initialDistance + (currentTime / duration * totalDistance);

  8         }

  9     },

 10     getTime = function(){                                                       //获取当前时间(ms或更精确)

 11         return window.performance.now && performance.now() || new Date().getTime();

 12     },

 13     executorCanceler = window.cancelAnimationFrame,    //取消帧函数

 14     executor = window.requestAnimationFrame                            //帧执行函数

 15             || window.webkitRequestAnimationFrame

 16             || window.msRequestAnimationFrame

 17             || window.mozRequestAnimationFrame

 18             || window.oRequestAnimationFrame

 19             || function(){

 20             var callbacks = [];

 21 

 22             !function frame(){

 23                 var oldTime = getTime(),

 24                     tmp = callbacks;

 25 

 26                 callbacks = [];

 27 

 28                 for(var i = 0, length = tmp.length; i < length; i++){

 29                     tmp[i].callback(oldTime);

 30                 }

 31 

 32                 var currentTime = getTime(),

 33                     delayTime = Math.max(16.66 - currentTime + oldTime, 0);

 34 

 35                 setTimeout(frame, delayTime);

 36             }();

 37 

 38             executorCanceler = function(id){

 39                 for(var i = 0, length = callbacks.length; i < length; i++){

 40                     if(callbacks[i].id === id) callbacks.splice(i, 1);

 41                 }

 42             }

 43 

 44             return function(callback){

 45                 var context = {callback: callback, id: Math.random()};

 46                 callbacks.push(context);

 47                 return context.id;

 48             }

 49     }(),

 50     /*

 51      * 为每个属性运行此函数,类似于启动一个线程(虽然不是真正的线程)

 52      */

 53     animate = function(element, attribute, distances, duration, timingFunction, completeCallback){

 54         var oldTime = getTime(),

 55                 animationPassedTime = 0,

 56                 executorReference = executor(function anonymous(currentTimeStamp){

 57                     animationPassedTime = currentTimeStamp - oldTime;

 58 

 59                     var computedValues = [];        //computedValues为缓动函数计算值,可能返回数值或者数组(按动画属性不同,比如rgb)

 60 

 61                     if(animationPassedTime >= duration){

 62                         if(distances.length > 1){

 63                             for(var j = 0, length = distances.length; j < length; j++){

 64                                 computedValues.push(distances[j][0] + distances[j][1]);

 65                             }

 66                         } else {

 67                             computedValues = distances[0][0] + distances[0][1];

 68                         }

 69 

 70                         stop();

 71                     } else {

 72                         if(distances.length > 1){

 73                             for(var i = 0, length = distances.length; i < length; i++){

 74                                 computedValues.push(fx[timingFunction](animationPassedTime, distances[i][0], distances[i][1], duration));

 75                             }

 76                         } else {

 77                             computedValues = fx[timingFunction](animationPassedTime, distances[0][0], distances[0][1], duration);

 78                         }

 79 

 80                         animationPassedTime = getTime() - oldTime;

 81                         executorReference = executor(anonymous);

 82                     }

 83                     unit[attribute].setter(element, computedValues);

 84                 }, Math.random()),

 85                 completed = false,

 86                 stop = function(){

 87                     executorCanceler(executorReference);

 88                     completeCallback();      //执行回调函数

 89                 };

 90 

 91         return {

 92             stop: stop

 93         }

 94     },

 95     /*

 96     * Animation 引用的函数,此函数返回一个包含所有动画属性的控制对象(如停止操作),因此可以采取函数调用或者new的方式创建一个动画对象

 97     */

 98     init = function(element, animationVars, duration, timingFunction, callback){

 99 

100         var animateQueue = {}, animationCount = 0, animationCompletedCount = 0, completeCallback = function(){

101             return function(){      //每个animate完成后调用此函数,当计数器满调用callback

102                 animationCompletedCount ++;

103 

104                 if(animationCount === animationCompletedCount){

105                     typeof timingFunction === "function" ? timingFunction() : callback && callback();

106                 }

107             }

108         }();

109 

110         if(!element.nodeType){

111             if(debug)

112                 throw "an htmlElement is required";

113             return;

114         }

115 

116         for(var attribute in animationVars){

117             if(!(attribute in unit)){

118                 if(debug){

119                     throw "no attribute handler";

120                 }

121 

122                 return;

123             }

124 

125             try {

126                 var initialDistance = unit[attribute].getter(element),

127                         finalDistance = unit[attribute].getter(animationVars[attribute].value || animationVars[attribute]),

128                         distances = [];

129 

130                 if(typeof initialDistance === "number"){

131                     distances.push([initialDistance, finalDistance - initialDistance]);

132                 } else {

133                     for(var i = 0, length = initialDistance.length; i < length; i++){

134                         distances.push([initialDistance[i], finalDistance[i] - initialDistance[i]]);

135                     }

136                 }

137                 /*

138                  * 可以为每个属性指定缓动函数与时间

139                  */

140                 animateQueue[attribute] = animate(element, attribute, distances, animationVars[attribute].duration || duration, animationVars[attribute].timingFunction || (typeof timingFunction === "string" ? timingFunction : false) || "linear", completeCallback);

141             } catch (e) {

142                 if(debug) {

143                     throw "an error occurred: " + e.stack;

144                 }

145 

146                 return;

147             }

148 

149             animationCount ++;

150         }

151 

152         animateQueue.stop = function() {

153             for(var attribute in animateQueue) {

154                 animateQueue[attribute].stop && animateQueue[attribute].stop();

155             }

156         }

157 

158         return animateQueue;

159     };

160 

161     init.config = function(configVars){

162         if(configVars){

163             if(configVars.fx) {

164                 for(var fxName in configVars.fx){

165                     if(typeof configVars.fx[fxName] === "function"){

166                         fx[fxName] = configVars.fx[fxName];

167                     }

168                 }

169             }

170 

171             if(configVars.unit) {

172                 for(var unitName in configVars.unit){

173                     if(typeof configVars.unit[unitName] === "object"){

174                         unit[unitName] = configVars.unit[unitName];

175                     }

176                 }

177             }

178 

179             if(configVars.debug) {

180                 debug = configVars.debug || false;

181             }

182         }

183     };

184 

185     init.each = function(array, handler){

186         if(typeof handler === "function"){

187             for(var i = 0, length = array.length; i < length; i++){

188                 handler.call(array[i], i, array);

189             }

190         }

191     };

192 

193     /*

194     * 赠送几个单位存取函数(暂时实现行内样式读取,单位px -。-)

195     */

196     init.each("width, height, left, right, top, bottom, marginLeft, marginTop".split(/\s*,\s*/), function(index, array){

197         var attributeName = this;

198         unit[attributeName] = {

199             getter: function(element){

200                 return parseInt((element.nodeType && element.style[attributeName] || element)["match"](/\d+/)[0]);

201             },

202             setter: function(element, value){

203                 element.style[attributeName] = value + "px";

204             }

205         }

206     });

207 

208     return init;

209 

210 }();

 


测试如下(需引入Animation):

详见:http://runjs.cn/code/lgrfeykn

 

 1 <!DOCTYPE html>

 2 <html>

 3 <head>

 4     <title></title>

 5     <script type="text/javascript">

 6 

 7 

 8         function init(){

 9 

10             Animation.config({            //可以在这里设置或扩充功能

11                 debug: true,

12                 fps: 60,

13                 fx: {

14                     easeOutElastic: function(t,b,c,d){

15                         var s=1.70158;var p=0;var a=c;

16                         if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;

17                         if (a < Math.abs(c)) { a=c; var s=p/4; }

18                         else var s = p/(2*Math.PI) * Math.asin (c/a);

19                         return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;

20                     }

21                 },

22                 unit: {

23                     backgroundColor: {        //

24                         getter: function(element){

25                             var backgroundColor = (element.nodeType && element.style.backgroundColor || element)["match"](/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);

26                             return [parseInt(backgroundColor[1]), parseInt(backgroundColor[2]), parseInt(backgroundColor[3])];

27                         },

28                         setter: function(element, value){

29                             element.style.backgroundColor = "rgb(" + parseInt(value[0]) + ", " + parseInt(value[1]) + ", " + parseInt(value[2]) + ")";

30                         }

31                     }

32                 }

33             });

34 

35 

36 

37             var animation = new Animation(test, {width: {value: "100px"}, height: {value: "100px"}, marginLeft: {value: "50px"}, marginTop: {value: "50px"}, backgroundColor: {value: "rgb(203,215,255)"}}, 1000, "easeOutElastic", function(){

38                     console.log("complete");

39             });

40 

41         }

42 

43     </script>

44 </head>

45 <body onload="init();">

46 

47     <div id="test" style="width: 200px; height: 200px; background: rgb(255,104,228);margin-left: 0; margin-top: 0"></div>

48 

49 </body>

50 </html>

 

你可能感兴趣的:(JavaScript)