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)
最後,全部文章都會同步發送到微信公衆號上,歡迎關注,歡迎提意見:
做者:對角另外一面