【小贴士】关于transitionEnd/animate的一个有趣故事

时间:2022-06-07 23:30:38

前言

在很久之前,我们项目有一个动画功能,功能本身很简单,便是典型的右进左出,并且带动画功能

以当时来说,虽然很简单,但是受限于框架本身的难度,就直接使用了CSS3的方式完成了功能

当时主要使用transform与animation实现功能,并且用了一个settimeout执行回调,然后此事便不了了之了

但是出来混总是要还的,这不,最近相似的东西又提了出来,我们当然可以将原来的那套东西拿来用,但是看着那个settimeout总是不是滋味,因为这样捕捉回调的效果以及可能引起的BUG大家都懂,于是就想使用transitionEnd监控动画结束再执行相关回调,于是便有了一个有趣的想法

当时的心声

嗯,不行,这次我要写一个通用的东西,他至少有这些功能:

① 我可以给他一个CSS变化属性

② 我可以给他一个时间长度

③ 我可以给他一个动画曲线参数

有了以上东西我就可以让一个元素触发动画,并且对其注册transitionEnd事件,最后执行我们的回调,于是我基本就陷进去了

但是,我想着想着突然感觉不对,感觉以上东西好像在哪里见过,于是一个叫animate的东西冒了出来

突然一刹那,我有一个不妙的感觉,搞出来一看:

animate
animate(properties, [duration, [easing, [function(){ ... }]]]) ⇒ self
animate(properties, { duration: msec, easing: type, complete: fn }) ⇒ self
animate(animationName, { ... }) ⇒ self 对当前Zepto集合对象中元素进行css transition属性平滑过渡。 properties: 一个对象,该对象包含了css动画的值,或者css帧动画的名称。
duration (默认 400):以毫秒为单位的时间,或者一个字符串。
fast (200 ms)
slow (600 ms)
任何$.fx.speeds自定义属性
easing (默认 linear):指定动画的缓动类型,使用以下一个:
ease
linear
ease-in / ease-out
ease-in-out
cubic-bezier(...)
complete:动画完成时的回调函数

于是,我自己的想法就只能呵呵了,这个就是我要的嘛......

而且zepto里面便是监听transitionEnd这个事件触发回调,所以,我们今天就来学习这个animate即可!!!

transitionEnd

transitionEnd是CSS3动画transition唯一的事件,我之前还去找个transitionStart,米有找到......

介绍他之前,我们先来个简单的例子,W3C上面的例子:

<!DOCTYPE html>
<html>
<head>
<style>
div { width: 100px; height: 100px; background: blue; transition: width 2s; -moz-transition: width 2s; /* Firefox 4 */ -webkit-transition: width 2s; /* Safari and Chrome */ -o-transition: width 2s; /* Opera */ } div:hover { width: 300px; }
</style>
</head>
<body>
<div>
</div>
<p>
请把鼠标指针移动到蓝色的 div 元素上,就可以看到过渡效果。</p>
<p>
<b>注释:</b>本例在 Internet Explorer 中无效。</p>
</body>
</html>

好了,现在若是我们要在动画结束时候加一个事件该怎么办呢?

<!DOCTYPE html>
<html>
<head>
<style>
div { width: 100px; height: 100px; background: blue; transition: width 1s; -moz-transition: width 1s; /* Firefox 4 */ -webkit-transition: width 1s; /* Safari and Chrome */ -o-transition: width 1s; /* Opera */ } div:hover { width: 300px; }
</style>
</head>
<body>
<div id="demo">
</div>
<br />
<span id="msg"></span>
<p>
请把鼠标指针移动到蓝色的 div 元素上,就可以看到过渡效果。</p>
<p>
<b>注释:</b>本例在 Internet Explorer 中无效。</p>
<script type="text/javascript">
var demo = document.getElementById('demo');
var msg = document.getElementById('msg'); // eventType(this.scroller, 'transitionend', this);
// eventType(this.scroller, 'webkitTransitionEnd', this);
// eventType(this.scroller, 'oTransitionEnd', this);
// eventType(this.scroller, 'MSTransitionEnd', this); demo.addEventListener('webkitTransitionEnd', function () {
msg.innerHTML = '事件回调,当前原始宽度:' + window.getComputedStyle(demo).width;
}); </script>
</body>
</html>

这个例子虽然简单却很好的说明了一些问题,现在我们就来简单模拟一下animate

简单模拟animate

既然zepto已经很好的实现了该功能,我们这里就简单的模拟下即可,然后看看zepto源码

var demo = document.getElementById('demo');
var msg = document.getElementById('msg'); //简单模拟animate,参数问题就不管他了,暂时只考虑width吧
function animate(el, css, time, fn) { if (!el) return; var callback = function () {
fn(arguments);
el.removeEventListener('webkitTransitionEnd', callback);
}; el.addEventListener('webkitTransitionEnd', callback); for (var k in css) {
//这里暂时只考虑webkit内核
el.style['-webkit-transition'] = k + ' ' + time + 's';
} for (var k in css) {
//这里暂时只考虑webkit内核
el.style[k] = css[k];
}
} demo.addEventListener('mouseenter', function () {
animate(demo, { width: '300px' }, 1, fn);
}); demo.addEventListener('mouseout', function () {
animate(demo, { width: '100px' }, 2, fn);
}); var fn = function () {
msg.innerHTML = '事件回调,当前原始宽度:' + window.getComputedStyle(demo).width;
}

这是一个简单的实现,每次执行animate的时候,先会执行一次transitionEnd的事件注册,并且执行一次后就销毁

第二步为其设置transition属性,如果可以的话,这里最好是可以消除

最后一步就是为其设置css属性即可整个逻辑很简单,大概原理就是这样,我接下来来看看zepto高大上的实现!!!

zepto高大上的animate

zepto要实现以上代码的话,这样搞:

var demo = $('#demo');
var msg = $('#msg'); var fn = function () {
msg.html('事件回调,当前原始宽度:' + demo.width());
}; demo.on('mouseenter', function () {
demo.animate({ 'width': '300px' }, 1000, 'ease-out', fn);
}); demo.on('mouseout', function () {
demo.animate({ 'width': '100px' }, 2000, 'ease-out', fn);
});

然后我们现在来看看源码:

;(function($, undefined){
var prefix = '', eventPrefix, endEventName, endAnimationName,
vendors = { Webkit: 'webkit', Moz: '', O: 'o' },
document = window.document, testEl = document.createElement('div'),
supportedTransforms = /^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i,
transform,
transitionProperty, transitionDuration, transitionTiming, transitionDelay,
animationName, animationDuration, animationTiming, animationDelay,
cssReset = {} function dasherize(str) { return str.replace(/([a-z])([A-Z])/, '$1-$2').toLowerCase() }
function normalizeEvent(name) { return eventPrefix ? eventPrefix + name : name.toLowerCase() } $.each(vendors, function(vendor, event){
if (testEl.style[vendor + 'TransitionProperty'] !== undefined) {
prefix = '-' + vendor.toLowerCase() + '-'
eventPrefix = event
return false
}
}) transform = prefix + 'transform'
cssReset[transitionProperty = prefix + 'transition-property'] =
cssReset[transitionDuration = prefix + 'transition-duration'] =
cssReset[transitionDelay = prefix + 'transition-delay'] =
cssReset[transitionTiming = prefix + 'transition-timing-function'] =
cssReset[animationName = prefix + 'animation-name'] =
cssReset[animationDuration = prefix + 'animation-duration'] =
cssReset[animationDelay = prefix + 'animation-delay'] =
cssReset[animationTiming = prefix + 'animation-timing-function'] = '' $.fx = {
off: (eventPrefix === undefined && testEl.style.transitionProperty === undefined),
speeds: { _default: 400, fast: 200, slow: 600 },
cssPrefix: prefix,
transitionEnd: normalizeEvent('TransitionEnd'),
animationEnd: normalizeEvent('AnimationEnd')
} $.fn.animate = function(properties, duration, ease, callback, delay){
if ($.isFunction(duration))
callback = duration, ease = undefined, duration = undefined
if ($.isFunction(ease))
callback = ease, ease = undefined
if ($.isPlainObject(duration))
ease = duration.easing, callback = duration.complete, delay = duration.delay, duration = duration.duration
if (duration) duration = (typeof duration == 'number' ? duration :
($.fx.speeds[duration] || $.fx.speeds._default)) / 1000
if (delay) delay = parseFloat(delay) / 1000
return this.anim(properties, duration, ease, callback, delay)
} $.fn.anim = function(properties, duration, ease, callback, delay){
var key, cssValues = {}, cssProperties, transforms = '',
that = this, wrappedCallback, endEvent = $.fx.transitionEnd,
fired = false if (duration === undefined) duration = $.fx.speeds._default / 1000
if (delay === undefined) delay = 0
if ($.fx.off) duration = 0 if (typeof properties == 'string') {
// keyframe animation
cssValues[animationName] = properties
cssValues[animationDuration] = duration + 's'
cssValues[animationDelay] = delay + 's'
cssValues[animationTiming] = (ease || 'linear')
endEvent = $.fx.animationEnd
} else {
cssProperties = []
// CSS transitions
for (key in properties)
if (supportedTransforms.test(key)) transforms += key + '(' + properties[key] + ') '
else cssValues[key] = properties[key], cssProperties.push(dasherize(key)) if (transforms) cssValues[transform] = transforms, cssProperties.push(transform)
if (duration > 0 && typeof properties === 'object') {
cssValues[transitionProperty] = cssProperties.join(', ')
cssValues[transitionDuration] = duration + 's'
cssValues[transitionDelay] = delay + 's'
cssValues[transitionTiming] = (ease || 'linear')
}
} wrappedCallback = function(event){
if (typeof event !== 'undefined') {
if (event.target !== event.currentTarget) return // makes sure the event didn't bubble from "below"
$(event.target).unbind(endEvent, wrappedCallback)
} else
$(this).unbind(endEvent, wrappedCallback) // triggered by setTimeout fired = true
$(this).css(cssReset)
callback && callback.call(this)
}
if (duration > 0){
this.bind(endEvent, wrappedCallback)
// transitionEnd is not always firing on older Android phones
// so make sure it gets fired
setTimeout(function(){
if (fired) return
wrappedCallback.call(that)
}, (duration * 1000) + 25)
} // trigger page reflow so new elements can animate
this.size() && this.get(0).clientLeft this.css(cssValues) if (duration <= 0) setTimeout(function() {
that.each(function(){ wrappedCallback.call(this) })
}, 0) return this
} testEl = null
})(Zepto)

看代码首先还是看入口,我们这里的入口就是animate

demo.animate({ 'width': '300px' }, 1000, 'ease-out', fn);
 $.fn.animate = function(properties, duration, ease, callback, delay){
if ($.isFunction(duration))
callback = duration, ease = undefined, duration = undefined
if ($.isFunction(ease))
callback = ease, ease = undefined
if ($.isPlainObject(duration))
ease = duration.easing, callback = duration.complete, delay = duration.delay, duration = duration.duration
if (duration) duration = (typeof duration == 'number' ? duration :
($.fx.speeds[duration] || $.fx.speeds._default)) / 1000
if (delay) delay = parseFloat(delay) / 1000
return this.anim(properties, duration, ease, callback, delay)
}

他首先这里做了一些默认处理,因为我们传递的参数是不定的,所以第二个参数极有可能是回调

所以他第一句就是做一个简单的判断,第二句也不例外

其实他整个animate都是做一些属性处理,并未做实际的事情,具体的实现还是在anim中

 $.fn.anim = function(properties, duration, ease, callback, delay){
var key, cssValues = {}, cssProperties, transforms = '',
that = this, wrappedCallback, endEvent = $.fx.transitionEnd,
fired = false if (duration === undefined) duration = $.fx.speeds._default / 1000
if (delay === undefined) delay = 0
if ($.fx.off) duration = 0 if (typeof properties == 'string') {
// keyframe animation
cssValues[animationName] = properties
cssValues[animationDuration] = duration + 's'
cssValues[animationDelay] = delay + 's'
cssValues[animationTiming] = (ease || 'linear')
endEvent = $.fx.animationEnd
} else {
cssProperties = []
// CSS transitions
for (key in properties)
if (supportedTransforms.test(key)) transforms += key + '(' + properties[key] + ') '
else cssValues[key] = properties[key], cssProperties.push(dasherize(key)) if (transforms) cssValues[transform] = transforms, cssProperties.push(transform)
if (duration > 0 && typeof properties === 'object') {
cssValues[transitionProperty] = cssProperties.join(', ')
cssValues[transitionDuration] = duration + 's'
cssValues[transitionDelay] = delay + 's'
cssValues[transitionTiming] = (ease || 'linear')
}
} wrappedCallback = function(event){
if (typeof event !== 'undefined') {
if (event.target !== event.currentTarget) return // makes sure the event didn't bubble from "below"
$(event.target).unbind(endEvent, wrappedCallback)
} else
$(this).unbind(endEvent, wrappedCallback) // triggered by setTimeout fired = true
$(this).css(cssReset)
callback && callback.call(this)
}
if (duration > 0){
this.bind(endEvent, wrappedCallback)
// transitionEnd is not always firing on older Android phones
// so make sure it gets fired
setTimeout(function(){
if (fired) return
wrappedCallback.call(that)
}, (duration * 1000) + 25)
} // trigger page reflow so new elements can animate
this.size() && this.get(0).clientLeft this.css(cssValues) if (duration <= 0) setTimeout(function() {
that.each(function(){ wrappedCallback.call(this) })
}, 0) return this
}

传入anim的参数真的就没有什么问题了

第一个是css属性

第二个是动画运行时间

第三个是动画曲线,这个很神奇,没事不要去搞他

第四个是回调函数

第五个是什么就暂时不知道是什么了

进入后,10行之前还是在做容错性处理,这里我们最主要关注点放在endEvent上面

这个东西由前面的fx对象获取:

$.fx = {
off: (eventPrefix === undefined && testEl.style.transitionProperty === undefined),
speeds: { _default: 400, fast: 200, slow: 600 },
cssPrefix: prefix,
transitionEnd: normalizeEvent('TransitionEnd'),
animationEnd: normalizeEvent('AnimationEnd')
}

而我们要做的chrome、firefox等兼容全部被normalizeEvent做了,这里

vendors = { Webkit: 'webkit', Moz: '', O: 'o' }
testEl = document.createElement('div')
$.each(vendors, function(vendor, event){
if (testEl.style[vendor + 'TransitionProperty'] !== undefined) {
prefix = '-' + vendor.toLowerCase() + '-'
eventPrefix = event
return false
}
})

这里根据这种方式得出了兼容事件的前缀,webkit的话会返回webkit前缀:

$.fx.transitionEnd => "webkitTransitionEnd"

然后下一步简单仍然是先设置transition相关的属性,并且指定事件结束事件回调:

cssValues[animationName] = properties
cssValues[animationDuration] = duration + 's'
cssValues[animationDelay] = delay + 's'
cssValues[animationTiming] = (ease || 'linear')
endEvent = $.fx.animationEnd

当然,如果我们传入的CSS不止一个的话,下面的处理会相对复杂点

cssProperties = []
// CSS transitions
for (key in properties)
if (supportedTransforms.test(key)) transforms += key + '(' + properties[key] + ') '
else cssValues[key] = properties[key], cssProperties.push(dasherize(key)) if (transforms) cssValues[transform] = transforms, cssProperties.push(transform)
if (duration > 0 && typeof properties === 'object') {
cssValues[transitionProperty] = cssProperties.join(', ')
cssValues[transitionDuration] = duration + 's'
cssValues[transitionDelay] = delay + 's'
cssValues[transitionTiming] = (ease || 'linear')
}

这里先放下处理Transform等新属性之外,与上面的操作无他

然后关键步骤又来了,

 wrappedCallback = function(event){
if (typeof event !== 'undefined') {
if (event.target !== event.currentTarget) return // makes sure the event didn't bubble from "below"
$(event.target).unbind(endEvent, wrappedCallback)
} else
$(this).unbind(endEvent, wrappedCallback) // triggered by setTimeout fired = true
$(this).css(cssReset)
callback && callback.call(this)
}
if (duration > 0){
this.bind(endEvent, wrappedCallback)
// transitionEnd is not always firing on older Android phones
// so make sure it gets fired
setTimeout(function(){
if (fired) return
wrappedCallback.call(that)
}, (duration * 1000) + 25)
}

他这里首先声明了回调函数wrappedCallback,这个函数首先干的事情是注销事件

然后执行传入的回调,这里将this指向了调用者,也就是绑定的标签

后面便是真实的事件绑定操作,里面仍然有一个延时函数执行

其中有一个状态机fired,来记录该事件是否触发

然后就为css复制了,这个时候动画执行结束便会触发transitionEnd事件了

最后,代码结束.......

结语

今天,我们简单的说了下zepto的animate方法,希望对各位有帮助,若是文中有任何问题请提出

【小贴士】关于transitionEnd/animate的一个有趣故事的更多相关文章

  1. 一个有趣的小例子&comma;带你入门协程模块-asyncio

    一个有趣的小例子,带你入门协程模块-asyncio 上篇文章写了关于yield from的用法,简单的了解异步模式,[https://www.cnblogs.com/c-x-a/p/10106031. ...

  2. Angular2 小贴士 Name

    Angular2 正式版已经发布了一个月了,我也是通过各种方式在进行验证是否可以满足我们的需求,今天我就发现了一个问题.现在我们来一起说明一下,这个可能不算是bug,而应该需要我们记住就可以了. 我们 ...

  3. 【小贴士】虚拟键盘与fixed带给移动端的痛!

    前言 今天来公司的主要目的就是研究虚拟键盘与fixed的问题,期间因为同事问起闭包与事件委托(阻止冒泡)相关问题,便穿插了一篇别的: [小贴士]工作中的”闭包“与事件委托的”阻止冒泡“,有兴趣的朋友可 ...

  4. SVN小贴士

    我辛辛苦苦写的到哪里了? SVN小贴士SVN服务器上的代码项目组公用,你的每一个提交都会体现给项目组每个人,所以提交要慎重,要注意避免代码冲突,使用SVN小贴士: 1.提前宣布开发计划,保持项目组成员 ...

  5. android性能小贴士 翻译

    转自http://developer.android.com/training/articles/perf-tips.html 性能小贴士: 这篇文档主要一些微优化可以提升应用程序性能,但是这些改变不 ...

  6. 小贴士——提高PHP程序在NGINX代理服务器的性能

    NGINX本身就是面向最大性能的代理服务器,因此在使用NGINX,并没有性能调整的配置工作.但是却有很多选项可用于定制NGINX的行为,利用底层硬件和操作系统. 下面将介绍用于提供PHP在NGINX的 ...

  7. 初识bd时的一些技能小贴士

    既然小豆腐如此给力,而且充分的利用主动学习的优势,已经有了迅速脑补,压倒式的优势,不过这只是表面而已,一切才刚刚开始,究竟鹿死谁手,还有待验证. 以上可以看到,小豆腐为什么拼命的要teach我们了么, ...

  8. SharePoint每日小贴士Web部件

    SharePoint每日小贴士Web部件 项目描写叙述         此Web部件从指定SP自己定义列表或一个选定的 RSS源选择一个随机项目.并显示一张图片.标题和一个Tip.         适 ...

  9. C和C&plus;&plus;的内存操作小贴士(一):const char&ast;的内存释放问题

    C和C++的内存操作一直是困扰开发人员的老问题,基本概念相信老司机们都很清楚了,在这里就不做过多的描述了,只是把在实际开发中可能遇到的一些小问题的案例列举下,供大家参考.“C和C++的内存操作小贴士” ...

随机推荐

  1. MyEclipse新建web project和navicat110&lowbar;mysql&lowbar;en工具

    首先注意几点: 1.eclipse web项目:项目名称不得超过五个字符,要求全部小写,不管变量名.类名.函数名.文件名,在没有特殊理由的时候,不要用下划线,同时表名和类名用两个单词,尽量不要用Stu ...

  2. Educational Codeforces Round 16---部分题解

    710A. King Moves 给你图中一点求出它周围有几个可达的点: 除边界之外都是8个,边界处理一下即可: #include<iostream> #include<cstdio ...

  3. ASP&period;NET MVC应用程序使用异步及存储过程

    ASP.NET MVC应用程序使用异步及存储过程 是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译 ...

  4. 音频处理库—librosa的安装与使用

    序言 Librosa是一个用于音频.音乐分析.处理的python工具包,一些常见的时频处理.特征提取.绘制声音图形等功能应有尽有,功能十分强大.本文主要介绍librosa的安装与使用方法. 一.lib ...

  5. asp&period;net 网页跳转的几种常用方法

    Response.Redirect("http://www.baidu.com",false); 后面的bool值为是否停止执行当前页.跳转向新的页面,原窗口被代替.浏览器中的UR ...

  6. 4QC(四象限变流器)

    1.什么是4QC? 4QC又叫四象限整流器 它是全控型晶闸管的整流电路,现在一般用水冷的IGBT组成的整流电路,它可以调节电压和电流,所谓的四象限就是 u+,i+,为第一象限: u-,i+.,为第二象 ...

  7. css图片的全屏显示代码-css3

    <!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8&quot ...

  8. apache的MultipartEntityBuilder文件上传

    本文讲解多文件上传方法,不比较上传有几种方法和效率,而是定向分析apache的httpmime包的MultipartEntityBuilder类,源码包:httpmime-4.5.2.jar 一.常用 ...

  9. Android开发——代码中实现WAP方式联网

    ,移动和联通的WAP代理服务器都是10.0.0.172,电信的WAP代理服务器是10.0.0.200. 在Android系统中,对于获取手机的APN设置,需要通过ContentProvider来进行数 ...

  10. JAVA中的File&period;separate&lpar;跨平台路径&rpar;

    转: JAVA中的File.separate(跨平台路径) 2016年03月27日 23:33:50 才不是本人 阅读数:1952   在Windows下的路径分隔符和Linux下的路径分隔符是不一样 ...