文档章节

最近学习JS的感悟-2(动画方面)

阳光test
 阳光test
发布于 2013/05/03 18:58
字数 2835
阅读 1084
收藏 9

        昨天写那篇文章花费了一个多小时,所以后面就没写了,今天继续写一点吧,主要就想说一下动画方面的内容。

        W3C现在已经定义了WindowAnimationTiming interface规范,其核心方法是requestAnimactionFrame和cancelRequestAnimationFrame,在很多新浏览器中都有实现,用它可以实现动画,但是貌似兼容性不太好,特别是在天国,还有IE6扫尾,所以貌似不靠谱,不过可以做一个判定,如果支持这个规范,则使用它,否则,使用我后面讲的这种方式,不过貌似比较麻烦,所以我没有使用这种方式,如果后面有时间了,可以去写着玩儿一下。

        除了这种不靠谱的方式,可能大家用的比较多的就是setInterval了,用它来完成动画是比较靠谱的。其实它的原理非常简单,就是在规定的时间内每隔一段时间执行一次回调,这个回调中可以进行一些动画的处理,比如修改元素大小,位置等。

       说到这儿,就必须要提几个概念了,首先是duration(动画的执行时间),frameTime(每次执行的时间间隔),然后是FPS(每秒的帧数,也就是1000 / frameTime)。一般来说,FPS越高,动画就表现的越流畅,FPS越低,动画就越不流畅。在Jquery中,默认的frameTime为13ms,在理想情况下,FPS可以达到70多。

       为什么说是理想下呢,因为JS的定时器存在精度问题,间隔不能太小,资源暂用较大的时候,这个间隔还没办法得到保障,更不靠谱的是,在一些高级浏览器中,页面不可见(如切换到其他选项卡,最小化)的时候,这个时间间隔会被自动提高,比如firefox5开始,setInterval的间隔在浏览器最小化之后至少被提高到1000ms。

        好,我们来总结一下上面这一段话,也就是说,这个frameTime是不靠谱的,根本不可控的,即使你写了setInterval(xxx,20),但也不一定会是20ms执行一次。所以这里就分化出两种策略了,一种是弃时保帧,另一种是弃帧保时。所谓的弃时保帧很容易理解,我要保证执行的次数有这么多,但这样就不保证duration;所谓的弃帧保时也好理解,我保证动画在duration结束,但是不保证到底执行了多少次(理论上执行次数为duration/frameTime)。

        在我写这个库的最开始,由于不知道setInterval的问题, 我采用的是弃时保帧的策略,也就是我定义了frameTime和steps,如果内部指定一个计数器,伪代码如下:

if(counter >= steps) {
   clearInterval(interval);
} else {
   counter++;
   //动画函数
}
      最开始的完整代码如下:
tp.animate.base = function(elem,options,callback) {
	var _options = {
		property : "left",
		from : 0,
		to : 200,
		steps : 30,
		time : 1000,
		needAddPx : true,
		autoStart : true
	};
	for(var name in options) {
		_options[name] = options[name];
	}
	callback = callback ? callback : function(){};
	var curStep = 0; 
	var suffix = _options.needAddPx ? "px" : "";
	var interval = parseFloat((_options.from - _options.to)) / parseInt(_options.steps);
	var intervalHandler = null; 
	var _publicMethod = {
		start : function() {
			intervalHandler = setInterval(_animateHelper,_options.time / _options.steps);
			return this;
		},
		stop : function() {
			clearInterval(intervalHandler);
			return this;
		},
		toStart : function() {
			curStep = 0;
			elem.style[_options.property] = _options.from + suffix;
			return this;
		},
		toEnd : function() {
			curStep = _options.steps;
			elem.style[_options.property] = _options.to + suffix;
			return this;
		}
	};
	var _updatePropertyVal = null;
	if("opacity" === _options.property) {
		_updatePropertyVal = function(val) {
			tp.dom.opacity(elem,val);
		};
	} else {
		_updatePropertyVal = function(val) {
			elem.style[_options.property] = val;
		};
	}
	function _animateHelper() {
		var newVal = _options.from - (++curStep * interval);
		if(curStep < _options.steps) {
			_updatePropertyVal(newVal + suffix);
		} else {
			_updatePropertyVal(_options.to + suffix);
			_publicMethod.stop();
			callback(elem);
		}
	}
	if(_options.autoStart) {
		_publicMethod.start();
	}
	return _publicMethod;
};
       这个代码当时出现了严重的抖动问题,其根本原因也是因为setInterval不准,而且IE下面有时候一个600ms的动画2m了还是没有执行完成,所以,这个直接pass掉。

       使用了弃帧保时的策略之后,整个动画的编写和之前完全不一样了,动画这一块儿看了很多qwrap的代码,这个库的具体信息可点此:http://www.qwrap.com/

       之前采用的是steps,也就是必须要执行那么多帧,而现在肯定保证不了,所以,我们换一种思路,我们在动画开始的时候记录一下时间,也就是通过:

var startTime = (new Date()).getTime();
       然后定时器执行的时候,我们再查看一下当前的时间,即currentTime,这里我们定义一个per,用这个来定义动画的执行进度,它的取值为0-1,0表示还没有开始,1代表动画执行结束。per的计算如下:
per = (currentTime - startTime) / duration;
       如果per大于等于1,那么说明动画结束,也就是需要clearInterval了。

       使用这个策略之后,整个代码如下:

(function() {
	var animate = function(dur,options,animateFn) {
		tp.object.extend(this,options);
		tp.object.extend(this,{
			animateFn : animateFn,
			dur : dur,
			per : 0,
			frameTime : options.frameTime || 13,
			status : 0,
			startDate : 0 ,
			interval : null
		});
		changePer(this,0);
	};
	function changePer(aniObj,per) {
		aniObj.per = per;
		aniObj.startDate = (new Date()).getTime() - per * aniObj.dur;
	}
	function turnOn(aniObj) {
		aniObj.step(null);
		if(aniObj.isPlaying()) {
			aniObj.interval = window.setInterval(function() {
				aniObj.step(null);
			},aniObj.frameTime);
		}
	}
	function turnOff(aniObj) {
		window.clearInterval(aniObj.interval);
	}
	tp.object.extend(animate.prototype,{
		isPlaying : function() {
			return this.status == 1;
		},
		play : function() {
			var me = this;
			if(me.isPlaying()) {
				me.pause();
			}
			changePer(me,0);
			me.status = 1;
			me.onplay && me.onplay();
			turnOn(me);
		},
		step : function(per) {
			var me = this;
			if(null != per) {
				changePer(me,per);
			} else {
				//计算出per
				per = ((new Date()).getTime() - me.startDate) / me.dur;
				me.per = per;
			}
			if(me.per > 1) {
				me.per = 1;
			}
			me.animateFn(me.per);
			me.onstep && me.onstep();
			if(me.per >= 1) {
				me.end();
			}
		},
		end : function() {
			changePer(this,1);
			this.animateFn(1);
			this.status = 2;
			turnOff(this);
			this.onend && this.onend();
		},
		pause : function() {
			this.status = 4;
			turnOff(this);
			this.onpause && this.onpause();
		},
		resume : function() {
			changePer(this,this.per);
			this.status = 1;
			this.onresume && this.onresume();
			turnOn(this);
		},
		reset : function() {
			changePer(this,0);
			this.animateFn(0);
			this.onreset && this.onreset();
		}
	});
	tp.animate.base = animate;
})();
        tp.object.extend可以扩展某一个对象的方法,其实作用和一般库的mix一样。

        其实最基本的动画代码非常简单,如下:

<script>
var timerId, startTime, frameTime = 13, dur = 3 * 1000;
function animFun(time) {
    var per = Math.min(1.0, (new Date - startTime) / dur);
    if(per >= 1) {
        clearTimeout(timerId);
    } else {
        document.getElementById("animated").style.left = Math.round(500 * per) + "px";
    }
}
function start() {
    startTime = new Date;
    timerId = setInterval(animFun, frameTime);
}
</script>
<div id="animated" onclick="start()" style="position: absolute; left: 0px; padding: 50px;background: crimson; color: white">Click Me</div>

       PS:这个代码是我理解动画原理的代码 ,当时网上找的,感谢作者啊!!

       之前我们的动画只能进行匀速的动画,对于复杂的动画,这样肯定满足不了要求,那么怎么做呢?

       这里就需要引入算子了,具体的解释大家可以google一下哈。

       具体如下:


<script>
var timerId, startTime, frameTime = 13, dur = 3 * 1000;
function easing(p) {
	return p * p;
}
function animFun(time) {
    var per = Math.min(1.0, (new Date - startTime) / dur);
    if(per >= 1) {
        clearTimeout(timerId);
    } else {
        document.getElementById("animated").style.left = Math.round(500 * easing(per)) + "px";
    }
}
function start() {
    startTime = new Date;
    timerId = setInterval(animFun, frameTime);
}
</script>
<div id="animated" onclick="start()" style="position: absolute; left: 0px; padding: 50px;background: crimson; color: white">Click Me</div>
         这里面多的代码就是:



function easing(p) {
	return p * p;
}
          现在网上这种算子还蛮多的,比如KISSY:



KISSY.add('anim/easing', function () {
    var PI = Math.PI,
        pow = Math.pow,
        sin = Math.sin,
        BACK_CONST = 1.70158;
    var Easing = {

        /**
         * swing effect.
         */
        swing: function (t) {
            return ( -Math.cos(t * PI) / 2 ) + 0.5;
        },

        /**
         * Uniform speed between points.
         */
        'easeNone': function (t) {
            return t;
        },

        /**
         * Begins slowly and accelerates towards end. (quadratic)
         */
        'easeIn': function (t) {
            return t * t;
        },

        /**
         * Begins quickly and decelerates towards end.  (quadratic)
         */
        easeOut: function (t) {
            return ( 2 - t) * t;
        },

        /**
         * Begins slowly and decelerates towards end. (quadratic)
         */
        easeBoth: function (t) {
            return (t *= 2) < 1 ?
                .5 * t * t :
                .5 * (1 - (--t) * (t - 2));
        },

        /**
         * Begins slowly and accelerates towards end. (quartic)
         */
        'easeInStrong': function (t) {
            return t * t * t * t;
        },

        /**
         * Begins quickly and decelerates towards end.  (quartic)
         */
        easeOutStrong: function (t) {
            return 1 - (--t) * t * t * t;
        },

        /**
         * Begins slowly and decelerates towards end. (quartic)
         */
        'easeBothStrong': function (t) {
            return (t *= 2) < 1 ?
                .5 * t * t * t * t :
                .5 * (2 - (t -= 2) * t * t * t);
        },

        /**
         * Snap in elastic effect.
         */

        'elasticIn': function (t) {
            var p = .3, s = p / 4;
            if (t === 0 || t === 1) return t;
            return -(pow(2, 10 * (t -= 1)) * sin((t - s) * (2 * PI) / p));
        },

        /**
         * Snap out elastic effect.
         */
        elasticOut: function (t) {
            var p = .3, s = p / 4;
            if (t === 0 || t === 1) return t;
            return pow(2, -10 * t) * sin((t - s) * (2 * PI) / p) + 1;
        },

        /**
         * Snap both elastic effect.
         */
        'elasticBoth': function (t) {
            var p = .45, s = p / 4;
            if (t === 0 || (t *= 2) === 2) return t;

            if (t < 1) {
                return -.5 * (pow(2, 10 * (t -= 1)) *
                    sin((t - s) * (2 * PI) / p));
            }
            return pow(2, -10 * (t -= 1)) *
                sin((t - s) * (2 * PI) / p) * .5 + 1;
        },

        /**
         * Backtracks slightly, then reverses direction and moves to end.
         */
        'backIn': function (t) {
            if (t === 1) t -= .001;
            return t * t * ((BACK_CONST + 1) * t - BACK_CONST);
        },

        /**
         * Overshoots end, then reverses and comes back to end.
         */
        backOut: function (t) {
            return (t -= 1) * t * ((BACK_CONST + 1) * t + BACK_CONST) + 1;
        },

        /**
         * Backtracks slightly, then reverses direction, overshoots end,
         * then reverses and comes back to end.
         */
        'backBoth': function (t) {
            var s = BACK_CONST;
            var m = (s *= 1.525) + 1;

            if ((t *= 2 ) < 1) {
                return .5 * (t * t * (m * t - s));
            }
            return .5 * ((t -= 2) * t * (m * t + s) + 2);

        },

        /**
         * Bounce off of start.
         */
        bounceIn: function (t) {
            return 1 - Easing.bounceOut(1 - t);
        },

        /**
         * Bounces off end.
         */
        bounceOut: function (t) {
            var s = 7.5625, r;

            if (t < (1 / 2.75)) {
                r = s * t * t;
            }
            else if (t < (2 / 2.75)) {
                r = s * (t -= (1.5 / 2.75)) * t + .75;
            }
            else if (t < (2.5 / 2.75)) {
                r = s * (t -= (2.25 / 2.75)) * t + .9375;
            }
            else {
                r = s * (t -= (2.625 / 2.75)) * t + .984375;
            }

            return r;
        },

        /**
         * Bounces off start and end.
         */
        'bounceBoth': function (t) {
            if (t < .5) {
                return Easing.bounceIn(t * 2) * .5;
            }
            return Easing.bounceOut(t * 2 - 1) * .5 + .5;
        }
    };

    return Easing;
});
          刚才我们写的tp.animate.base可以完成万能动画,我们只需要填写具体的帧动画即可。但对于颜色动画来说,还是比较麻烦的,我这儿写了一个组件,代码如下:



define("tp.animate.color",[],function() {
	//特殊的颜色值
	var KEYWORDS = {
		'black':[0, 0, 0],
		'silver':[192, 192, 192],
		'gray':[128, 128, 128],
		'white':[255, 255, 255],
		'maroon':[128, 0, 0],
		'red':[255, 0, 0],
		'purple':[128, 0, 128],
		'fuchsia':[255, 0, 255],
		'green':[0, 128, 0],
		'lime':[0, 255, 0],
		'olive':[128, 128, 0],
		'yellow':[255, 255, 0],
		'navy':[0, 0, 128],
		'blue':[0, 0, 255],
		'teal':[0, 128, 128],
		'aqua':[0, 255, 255]
	};
	/*
	 解析颜色,支持rgb和hex颜色值,特殊颜色如black
	 */
	function parseColor(color) {
		var r = 0,
			g = 0,
			b = 0;
		if(/rgb/.test(color)) {
			//参数为RGB模式,不做进制转换,直接截取字符串,如rgb(2,2,2)
			var arr = color.match(/\d+/g);
			r = parseInt(arr[0]);
			g = parseInt(arr[1]);
			b = parseInt(arr[2]);
		} else if(/#/.test(color)) {
			//参数为十六进制,需要进行进制转换
			var len = color.length;
			if(7 === len) {
				//非简写,#ffccdd
				r = parseInt(color.slice(1,3),16);
				g = parseInt(color.slice(3,5),16);
				b = parseInt(color.slice(5),16);
			} else if(4 === len) {
				//简写模式 #eaf
				r = parseInt(color.charAt(1) + color.charAt(1),16);
				g = parseInt(color.charAt(2) + color.charAt(2),16);
				b = parseInt(color.charAt(3) + color.charAt(3),16);
			} else {
				tp.log("error format of color:" + color);
			}
		} else {
			//特殊颜色,如black
			var tmpColor = KEYWORDS[color.toLowerCase()];
			if(tmpColor) {
				r = tmpColor[0];
				g = tmpColor[1];
				b = tmpColor[2];
			} else {
				tp.log("error format of color:" + color);
			}
		}
		return [r,g,b];
	}
	function setValue(ele,attr,color) {
		if(tp.type.isString(color)) {
			//直接设置如#cccccc
			tp.dom.css(ele,attr,color);
		} else {
			//数组,设置如rgb(22,22,22)
			tp.dom.css(ele,attr,"rgb(" + color.join(",") +  ")");
		}
	}
	//preColor为arr
	function getNewColor(preColor,spacing,per) {
		var arr = new Array();
		arr[0] = Math.max(Math.min(parseInt(preColor[0] + per * spacing[0]),255),0);
		arr[1] = Math.max(Math.min(parseInt(preColor[1] + per * spacing[1]),255),0);
		arr[2] = Math.max(Math.min(parseInt(preColor[2] + per * spacing[2]),255),0);
		return "rgb(" + arr.join(",") + ")";
	}

	/**
	 * @namespace tp.animate.color
	 * @param {Object} options
	 * @config {Object} [ele] 动画元素
	 * @config {String} [from] 起始颜色值
	 * @config {String} [to] 结束颜色值
	 * @config {String} [attr] 操作的属性名
	 */
	var color = function(options) {
		var fromArr = options.from ? parseColor(options.from) : parseColor(tp.dom.css(options.ele,options.attr)),
			toArr = parseColor(options.to);
		tp.object.extend(this,{
			spacing : [toArr[0] - fromArr[0] , toArr[1] - fromArr[1] , toArr[2] - fromArr[2]],
			attr : options.attr,
			fromArr : fromArr,
			ele : options.ele
		});
	};
	tp.object.extend(color.prototype,{
		action : function(per) {
			var newColor = getNewColor(this.fromArr,this.spacing,per);
			setValue(this.ele,this.attr,newColor);
		}
	});
	tp.modules.add("tp.animate.color",color);
});
         调用如下:



tp.use(["tp.animate.color","tp.animate.easing"],function(color,easing) {
		var _color = new color({
			ele : test,
			attr : "backgroundColor",
			to : "#AACCBB"
		});
		tp.event.on(btn,"click",function() {
			var _a = new tp.animate.base(900,{
				dur : 150
			},function(per) {
				_color.action(easing.backIn(per));
			});
			_a.play();
		});
	});
              其实颜色动画的原理也很简单,比如从一个颜色A到另一个颜色B,每一个颜色都有一个RGB值,所以可以把整个过程分开,R从A到B,G从A到B,B从A到B,而我们之前已经实现了tp.animate.base了,这里有一个per,在任意时刻,假设计算R,R差值C为B的R值减去A的R值,那么此刻的R值为:A的R值+per * C ,但是由于R的范围为0-255,所以稍微改变一下:



Math.max(Math.min(parseInt(A_R + per * C),255),0);
           个人感觉这个里面有点麻烦的还是颜色值的获取,而我在parseColor中也解决了这个问题,其实就是将一个颜色值如:#232322变成RGB的数组,方便后面调用而已。


            除了这种,动画中还有动画队列,这里我就不说了。

            差不多一个小时,写完收工。

© 著作权归作者所有

上一篇: 小小感触
阳光test

阳光test

粉丝 541
博文 71
码字总数 91741
作品 1
杭州
程序员
私信 提问
加载中

评论(1)

假装在纽约
假装在纽约
体力不支,坚决顶。顶你的学习态度和精神,菜鸟向你看齐!
semantic ui使用问题

@二的基本算合格 你好,想跟你请教个问题:最近在学习semantic ui,它里面有一些自带动画效果,为什么我用到自己的网页上却没有起作用呢?引入了semantic.min.js和jquery.js 这两个js文件。是...

月牙儿-sky
2015/05/22
7K
7
前端技术月刊 📖 2019-01

聚焦前端,记录过去一个月看到的优秀的文章,内容来源主要有 、、、、、、 以及邮件RSS订阅和发散阅读。可能会有设计方面的分享。每月28日更新,本期第一期。同步到Github和我的博客。 星推 ...

小蘿蔔丁
01/28
0
0
数据库学习之ACCESS与SqlServer配什么编程语言好?

作为一个刚开始学习编程的菜鸟,对数据库自然是不能马虎的,我最近就在学习这方面的知识,先了解一下ACCESS这种微软的轻量级小型数据库,然后再深入学习sql server中型数据库,这样由浅入深,...

原创小博客
2018/06/07
83
0
给前端开发者的 20 款实用文档和指南

又到了该学习的时候了!之前,我已经收集了许多不同的学习资源,包括教程,文档,和其他一些有用的网站,这些资源可以用来帮助你快速掌握前端开发的不同技术。 1. CSS 取模查询和范围选择器 ...

oschina
2016/11/21
5.9K
13
MV* 框架 与 DOM操作为主 JS库 的案例对比

最近分别使用 Zepto 和 Avalon框架写了个 SPA项目,贴出来讨论下 JS DOM操作为主 JS库 与 MV* 框架的对比 案例(MV* 框架 与 DOM操作 JS库 实例对比) 购物车页面 JS业务逻辑(忽略 Ajax请求...

子凡
09/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

聊聊nacos的LocalConfigInfoProcessor

序 本文主要研究一下nacos的LocalConfigInfoProcessor LocalConfigInfoProcessor nacos-1.1.3/client/src/main/java/com/alibaba/nacos/client/config/impl/LocalConfigInfoProcessor.java p......

go4it
昨天
4
0
前端技术之:webpack热模块替换(HMR)

第一步:安装HMR中间件: npm install --save-dev webpack-hot-middleware 第二步:webpack配置中引入webpack对象 const webpack = require('webpack’); 第三步:增加devServer配置项: ho......

popgis
昨天
4
0
死磕 java线程系列之线程池深入解析——体系结构

(手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本。 简介 Java的线程池是块硬骨头,对线程池的源码做深入研究不仅能提高对Java整个并发编程的理解,也能提高自己...

彤哥读源码
昨天
4
0
虚函数表 图解

虚函数表 图解 p504

天王盖地虎626
昨天
3
0
java反射

学习目标  什么是反射  反射运行原理  了解反射机制的相关类  获取 class 对象的 3 种方式  通过反射获取构造方法并使用  通过反射获取成员变量并调用  通过反射获取成员方法并...

流川偑
昨天
4
2

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部