fx
模块为利用 CSS3
的过渡和动画的属性为 Zepto
提供了动画的功能,在 fx
模块中,只作了事件和样式浏览器前缀的补全,没有作太多的兼容。对于不支持 CSS3
过渡和动画的, Zepto
的处理也相对简单,动画当即完成,立刻执行回调。javascript
读 Zepto 源码系列文章已经放到了github上,欢迎star: reading-zeptocss
本文阅读的源码为 zepto1.2.0html
《reading-zepto》java
function dasherize(str) { return str.replace(/([A-Z])/g, '-$1').toLowerCase() }
这个方法是将驼峰式( camleCase
)的写法转换成用 -
链接的连词符的写法( camle-case
)。转换的目的是让写法符合 css
的样式规范。git
function normalizeEvent(name) { return eventPrefix ? eventPrefix + name : name.toLowerCase() }
为事件名增长浏览器前缀。github
var prefix = '', eventPrefix, vendors = { Webkit: 'webkit', Moz: '', O: 'o' }, 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 = {}
vendors
定义了浏览器的样式前缀( key
) 和事件前缀 ( value
) 。web
testEl
是为检测浏览器前缀所建立的临时节点。浏览器
cssReset
用来保存加完前缀后的样式规则,用来过渡或动画完成后重置样式。微信
if (testEl.style.transform === undefined) $.each(vendors, function(vendor, event){ if (testEl.style[vendor + 'TransitionProperty'] !== undefined) { prefix = '-' + vendor.toLowerCase() + '-' eventPrefix = event return false } })
检测到浏览器不支持标准的 transform
属性,则依次检测加了不一样浏览器前缀的 transitionProperty
属性,直至找到合适的浏览器前缀,样式前缀保存在 prefix
中, 事件前缀保存在 eventPrefix
中。app
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'] = ''
获取浏览器前缀后,为全部的 transition
和 animation
属性加上对应的前缀,都初始化为 ''
,方便后面使用。
$.fx = { off: (eventPrefix === undefined && testEl.style.transitionProperty === undefined), speeds: { _default: 400, fast: 200, slow: 600 }, cssPrefix: prefix, transitionEnd: normalizeEvent('TransitionEnd'), animationEnd: normalizeEvent('AnimationEnd') }
400ms
prefix
normalizeEvent
事件加了浏览器前缀补全$.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
这个方法,animate
这个方法至关灵活,由于它主要作的是参数修正的工做,作得参数适应 anim
的接口。
animation
的名称,只有这个参数是必传的if ($.isFunction(duration)) callback = duration, ease = undefined, duration = undefined
这是处理传参为 animate(properties, callback)
的状况。
if ($.isFunction(ease)) callback = ease, ease = undefined
这是处理 animate(properties, duration, callback)
的状况,此时 callback
在参数 ease
的位置
if ($.isPlainObject(duration)) ease = duration.easing, callback = duration.complete, delay = duration.delay, duration = duration.duration
这是处理 animate(properties, { duration: msec, easing: type, complete: fn })
的状况。除了 properties
,后面的参数还能够写在一个对象中传入。
若是检测到为对象的传参方式,则将对应的值从对象中取出。
if (duration) duration = (typeof duration == 'number' ? duration : ($.fx.speeds[duration] || $.fx.speeds._default)) / 1000
若是过渡时间为数字,则直接采用,若是是 speeds
中指定的 key
,即 slow
、fast
甚至 _default
,则从 speeds
中取值,不然用 speends
的 _default
值。
由于在样式中是用 s
取值,因此要将毫秒数除 1000
。
if (delay) delay = parseFloat(delay) / 1000
也将延迟时间转换为秒。
$.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 + delay) * 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 }
animation
最终调用的是 anim
方法,Zepto
也将这个方法暴露了出去,其实我以为只提供 animation
方法就能够了,这个方法彻底能够做为私有的方法调用。
if (duration === undefined) duration = $.fx.speeds._default / 1000 if (delay === undefined) delay = 0 if ($.fx.off) duration = 0
若是没有传递持续时间 duration
,则默认为 $.fx.speends._default
的定义值 400ms
,这里须要转换成 s
。
若是没有传递 delay
,则默认不延迟,即 0
。
若是浏览器不支持过渡和动画,则 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 }
若是 properties
为 string
, 即 properties
为动画名,则设置动画对应的 css
,duration
和 delay
都加上了 s
的单位,默认的缓动函数为 linear
。
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') } }
supportedTransforms
是用来检测是否为 transform
的正则,若是是 transform
,则拼接成符合 transform
规则的字符串。
不然,直接将值存入 cssValues
中,将 css
的样式名存入 cssProperties
中,而且调用了 dasherize
方法,使得 properties
的 css
样式名( key
)支持驼峰式的写法。
if (transforms) cssValues[transform] = transforms, cssProperties.push(transform)
这段是检测是否有 transform
,若是有,也将 transform
存入 cssValues
和 cssProperties
中。
接下来判断动画是否开启,而且是否有过渡属性,若是有,则设置对应的值。
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) }
若是浏览器支持过渡或者动画事件,则在动画结束的时候,取消事件监听,注意在 unbind
时,有个 event.target !== event.currentTarget
的断定,这是排除冒泡事件。
若是事件不存在时,直接取消对应元素上的事件监听。
而且将状态控制 fired
设置为 true
,表示回调已经执行。
动画完成后,再将涉及过渡或动画的样式设置为空。
最后,调用传递进来的回调函数,整个动画完成。
if (duration > 0){ this.bind(endEvent, wrappedCallback) setTimeout(function(){ if (fired) return wrappedCallback.call(that) }, ((duration + delay) * 1000) + 25) }
绑定过渡或动画的结束事件,在动画结束时,执行处理过的回调函数。
注意这里有个 setTimeout
,是避免浏览器不支持过渡或动画事件时,能够经过 setTimeout
执行回调。setTimeout
的回调执行比动画时间长 25ms
,目的是让事件响应在 setTimeout
以前,若是浏览器支持过渡或动画事件, fired
会在回调执行时设置成 true
, setTimeout
的回调函数不会再重复执行。
// trigger page reflow so new elements can animate this.size() && this.get(0).clientLeft this.css(cssValues)
这里用了点黑科技,读取 clientLeft
属性,触发页面的回流,使得动画的样式设置上去时能够当即执行。
具体能够这篇文章中的解释:2014-02-07-hidden-documentation.md
if (duration <= 0) setTimeout(function() { that.each(function(){ wrappedCallback.call(this) }) }, 0)
duration
不大于零时,能够是参数设置错误,也多是浏览器不支持过渡或动画,就当即执行回调函数。
署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)
最后,全部文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:
做者:对角另外一面