在痛苦的IE8时代,所有的动画都只能基于自己计算相关动画属性,开定时器setTimeout/setInterval轮询动画任务。
而肩负重任的HTML5,早已注意到了日益增强的动画,随着HTML5的降临,带来了强劲的CSS3动画,本文主要探讨:乘着CSS3的风,实现JS动画——探索现代画风的js动画。
本文内容如下:
- CSS3动画
- 基于CSS3的动画本质
- 封装基于CSS3的动画API
- 事件处理
- 结语
- 参考和引用
Javascript - 前端开发交流群:377786580
CSS3的动画各种文章漫天飞已经讲烂了,CSS3到目前为止总共新增了两个动画属性:transition
、animation
。这里只关注我们目前要用的的部分:transition
。至于animation
的部分请参考《MDN - 使用CSS动画》。
CSS3让动画前所未有的简单,下面的例子演示了transition
,当点击div.demo的时候,div.demo向右偏移200px:
<style> .demo { background-color: #0094ff; width: 100px; height: 100px; position: absolute;left: 0; transition: left /*要执行动画的属性*/ linear /*动画曲线*/ 1s; /*动画执行时间*/ } </style> <div class="demo" onclick="javascript: this.style.left = '200px';"></div>
上面一段transition
的意思就是:
仔细看看代码也就能发现,其实transition
和background
一样也是简写属性,是这几个CSS3新增属性的简写:
这两段代码意义相同:
transition: left /*要执行动画的属性*/ linear: ; /*动画曲线*/ 1s; /*动画执行时间*/ transition-property:left; /*动画属性*/ transition-timing-function:linear; /*动画曲线*/ transition-duration:1s; /*执行时间*/ transition-delay:0; /*动画延迟时间*/
效果如图:
早期实现动画比较麻烦,需要使用类似下面JS的原理来实现:
<div class="demo" id="demo" style="left:0"></div> <script> var elem = document.getElementById('demo'),//获取元素 elemStyleSheet = elem.style,//元素的内联样式对象 left = parseInt(elemStyleSheet.left),//获取当前left targetLeft = 200,//目标left time = 13,//动画每帧间隔 offsetValue = targetLeft / parseInt(1000 / 13),//每帧偏移量 intervalId, temp;//临时变量 elem.onclick = function () { intervalId = setInterval(function () { //追加偏移量 temp = parseInt(elemStyleSheet.left) + offsetValue; elemStyleSheet.left = temp + 'px'; if (temp >= targetLeft)//完成动画 clearInterval(intervalId); }, time); }; </script>
效果和上面的css3实现的一样。大体意思就是计算出动画的帧数、每帧间隔、每帧动画的偏移量,然后开个定时器一直重复执行,直到动画完成。具体高能版实现可以参阅jQuery.animate。
这里需要注意:早期的动画都是基于定时器setTimeout/setInterval
来轮询动画任务,它们本身的模型就不是为了动画而打造的,实现动画的性能上实在堪忧,所以现代浏览器都部署了新的API requestAnimationFrame
来弥补setTimeout/setInterval
在动画方面天生的表现力不足。
近期jQuery发布了jQuery3.0 预览版,就使用了requestAnimationFrame
来完成动画。
?
transition
可以驱动(作为动画)的属性太多太多,例如:
transform
变形和z轴偏移,参阅CSS3 Transform在支持CSS3的情况下,如果我们想执行动画,本质上都可以使用transition
来驱动。因为transition
已经封装好了动画的行为,我们只需要指定transition
需要的一些关键属性值即可。所以这个动画的实现,本质上就是一个给DOM赋上CSS3的属性transition
,然而我们早已就看透了一切。
?
既然CSS3自身就已经实现了相关动画属性,那么封装API这种事情就变得十分简单了,拿我们最初的例子来说,可以使用如下js代码:
<style> .demo { background-color: #0094ff; width: 100px; height: 100px; position: absolute; } </style> <div class="demo" id="demo"></div> <script> var elem = document.getElementById('demo'), elemStyleSheet = elem.style;//元素的内联样式对象 //赋上transition elemStyleSheet.cssText = 'left:0; transition: left linear 1s;'; elem.onclick = function () { elemStyleSheet.left = '200px'; } </script>
核心代码其实就2行,附上transition
和关键属性即可。是不是突然觉得动画真是so easy~~~
程序是要有健壮性的,既然我们发现了这么个"天大的秘密",是不是封装成API以后给自己、给基友使用更好呢?看起来就觉得很高大上一样,那我们来封装下API吧。
最简短的封装就是把transition
的四个属性封装传递进来就可以了:
var animate = function (elem, propertys, ease, duration, delay) { var cssText = [], props = []; for (var name in propertys) { props.push(name);//提取要执行动画的属性 //提取动画目标样式 cssText.push(name + ':' + //如果是number,则追加px单位 (typeof propertys[name] === 'number' ? propertys[name] + 'px' : propertys[name])); } //添加transition样式 cssText.push('transition-property:' + props.join('')); cssText.push('transition-timing-function:' + (ease || 'linear')); cssText.push('transition-duration:' + (duration || 300) + 's') cssText.push('transition-delay:' + (delay || 0)); //添加元素样式 elem.style.cssText += ';' + cssText.join(';'); };
大体意思就是把transition-*
的属性通过外面的参数传递进来,然后我们做下拼接的处理,然后给元素新增上样式属性就可以了。代码到了这里,我们来看看成果,毕竟我们仅使用了12行代码就完成了JS的动画。戳这里查看运行demo。
?
到了这里,我们会发现其实API和jQuery.animate
很像很像,哟吼,看起来很不错的样子,等等,好像还缺了点什么。仔细想想,我们还缺少一个重要的东西:动画结束事件。我们往往有很多的需要的任务都是在动画结束事件里完成的,但是我们怎么能没有动画结束这么重要的事件呢,别着急,国外那群搞浏览器的,也已经为大家讨论出了结果(当然也还有其他动画事件):使用transition
的动画,提供一个动画结束的事件:onTransitionEnd
。
我们再在刚才的demo下追加一行添加onTransitionEnd
事件的代码:
//动画结束事件 elem.addEventListener('transitionend', function () { alert('动画结束了!!!'); });
戳这里查看运行demo
我们再来看看onTransitionEnd
事件的兼容性:
嗯,其实有些浏览器很早以前的实现都是私有实现,这里为了防止出现意外,还是嗅探一下浏览器吧,针对浏览器的私有实现,我们绑定私有事件。当然我们应该考虑的更周全一点,既然都已经嗅探了私有事件,我们一同嗅探出私有属性吧,防止有些浏览器不支持标准的CSS但是私有实现了transition
:
var testElem = document.createElement('div'), //各大浏览器私有属性:transitionProperty、webkitTransitionProperty、transitionProperty、oTransitionProperty、msTransitionProperty vendors = { '': '', 'Webkit': 'webkit', 'Moz': '', 'O': 'o', 'ms': 'ms' }, /* https://github.com/madrobby/zepto/pull/742 Firefox从未支持过mozTransitionEnd或MozTransitionEnd,firefox一直支持标准的事件transitionend */ normalizeEvent = function (name) { return eventPrefix ? eventPrefix + name : name.toLowerCase(); }, //私有css前缀 cssPrefix = null, //私有事件前缀 eventPrefix = null, //私有事件 onTransitionEnd = null; for (var name in vendors) { //嗅探特性 if (testElem.style[(name ? name + 'T' : 't') + 'ransitionProperty'] !== undefined) { cssPrefix = name ? '-' + name.toLowerCase() + '-' : name; eventPrefix = vendors[name]; onTransitionEnd = normalizeEvent('TransitionEnd'); break; } }
?
最后,我们整理一下代码,把这些私有属性的嗅探和之前的代码进行融合,同时做一些优雅降级的处理。一个轻量级的,基于CSS3的JS动画就这么实现了。
?
(function (window) { var Support = { cssPrefix: null, eventPrefix: null,
新闻热点
疑难解答