读Zepto源码之Fx模块
fx
模块为利用 CSS3
的过渡和动画的属性为 Zepto
提供了动画的功能,在 fx
模块中,只做了事件和样式浏览器前缀的补全,没有做太多的兼容。对于不支持 CSS3
过渡和动画的, Zepto
的处理也相对简单,动画立即完成,马上执行回调。
读 Zepto 源码系列文章已经放到了github上,欢迎star: reading-zepto
源码版本
本文阅读的源码为 zepto1.2.0
GitBook
内部方法
dasherize
function dasherize(str) { return str.replace(/([A-Z])/g, '-$1').toLowerCase() }
这个方法是将驼峰式( camleCase
)的写法转换成用 -
连接的连词符的写法( camle-case
)。转换的目的是让写法符合 css
的样式规范。
normalizeEvent
function normalizeEvent(name) { return eventPrefix ? eventPrefix + name : name.toLowerCase() }
为事件名增加浏览器前缀。
为事件和样式增加浏览器前缀
变量
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
) 。
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
中。
初始化样式
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
$.fx = {
off: (eventPrefix === undefined && testEl.style.transitionProperty === undefined),
speeds: { _default: 400, fast: 200, slow: 600 },
cssPrefix: prefix,
transitionEnd: normalizeEvent('TransitionEnd'),
animationEnd: normalizeEvent('AnimationEnd')
}
- off: 表示浏览器是否支持过渡或动画,如果既没有浏览器前缀,也不支持标准的属性,则判定该浏览器不支持动画
- speeds: 定义了三种动画持续的时间, 默认为
400ms
- cssPrefix: 样式浏览器兼容前缀,即
prefix
- transitionEnd: 过渡完成时触发的事件,调用
normalizeEvent
事件加了浏览器前缀补全 - animationEnd: 动画完成时触发的事件,同样加了浏览器前缀补全
animate
$.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
的接口。
参数:
- properties:需要过渡的样式对象,或者
animation
的名称,只有这个参数是必传的 - duration: 过渡时间
- ease: 缓动函数
- callback: 过渡或者动画完成后的回调函数
- delay: 过渡或动画延迟执行的时间
修正参数
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
也将延迟时间转换为秒。
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 + 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
,即没有动画,立即执行回调。
处理animation动画参数
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
。
处理transition参数
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
不大于零时,可以是参数设置错误,也可能是浏览器不支持过渡或动画,就立即执行回调函数。
系列文章
- 读Zepto源码之代码结构
- 读Zepto源码之内部方法
- 读Zepto源码之工具函数
- 读Zepto源码之神奇的$
- 读Zepto源码之集合操作
- 读Zepto源码之集合元素查找
- 读Zepto源码之操作DOM
- 读Zepto源码之样式操作
- 读Zepto源码之属性操作
- 读Zepto源码之Event模块
- 读Zepto源码之IE模块
- 读Zepto源码之Callbacks模块
- 读Zepto源码之Deferred模块
- 读Zepto源码之Ajax模块
- 读Zepto源码之Assets模块
- 读Zepto源码之Selector模块
- 读Zepto源码之Touch模块
- 读Zepto源码之Gesture模块
- 读Zepto源码之IOS3模块
附文
参考
- 一步一步DIY zepto库,研究zepto源码7--动画模块(fx fx_method)
- How (not) to trigger a layout in WebKit
- 2014-02-07-hidden-documentation.md
License
署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)
作者:对角另一面
读Zepto源码之Fx模块的更多相关文章
- 读Zepto源码之fx_methods模块
fx 模块提供了 animate 动画方法,fx_methods 利用 animate 方法,提供一些常用的动画方法.所以 fx_methods 模块依赖于 fx 模块,在引入 fx_methods ...
- 读Zepto源码之Stack模块
Stack 模块为 Zepto 添加了 addSelf 和 end 方法. 读 Zepto 源码系列文章已经放到了github上,欢迎star: reading-zepto 源码版本 本文阅读的源码为 ...
- 读Zepto源码之Form模块
Form 模块处理的是表单提交.表单提交包含两部分,一部分是格式化表单数据,另一部分是触发 submit 事件,提交表单. 读 Zepto 源码系列文章已经放到了github上,欢迎star: rea ...
- 读Zepto源码之Data模块
Zepto 的 Data 模块用来获取 DOM 节点中的 data-* 属性的数据,和储存跟 DOM 相关的数据. 读 Zepto 源码系列文章已经放到了github上,欢迎star: reading ...
- 读Zepto源码之Callbacks模块
Callbacks 模块并不是必备的模块,其作用是管理回调函数,为 Defferred 模块提供支持,Defferred 模块又为 Ajax 模块的 promise 风格提供支持,接下来很快就会分析到 ...
- 读Zepto源码之Deferred模块
Deferred 模块也不是必备的模块,但是 ajax 模块中,要用到 promise 风格,必需引入 Deferred 模块.Deferred 也用到了上一篇文章<读Zepto源码之Callb ...
- 读Zepto源码之Ajax模块
Ajax 模块也是经常会用到的模块,Ajax 模块中包含了 jsonp 的现实,和 XMLHttpRequest 的封装. 读 Zepto 源码系列文章已经放到了github上,欢迎star: rea ...
- 读Zepto源码之Selector模块
Selector 模块是对 Zepto 选择器的扩展,使得 Zepto 选择器也可以支持部分 CSS3 选择器和 eq 等 Zepto 定义的选择器. 在阅读本篇文章之前,最好先阅读<读Zept ...
- 读Zepto源码之Touch模块
大家都知道,因为历史原因,移动端上的点击事件会有 300ms 左右的延迟,Zepto 的 touch 模块解决的就是移动端点击延迟的问题,同时也提供了滑动的 swipe 事件. 读 Zepto 源码系 ...
随机推荐
- 汇编指令-MRS(读)和MSR(写)指令操作CPSR寄存器和SPSR寄存器使用(1)
1.MSR和MRS指令介绍 MRS 指令: 对状态寄存器CPSR和SPSR进行读操作.通过读CPSR可以获得当前处理器的工作状态.读SPSR寄存器可以获得进入异常前的处理器状态(因为只有异常模式下有 ...
- 小程序脚本语言WXS,你想要的都在这里了
WXS脚本语言是 Weixin Script脚本的简称,是JS.JSON.WXML.WXSS之后又一大小程序内部文件类型.截至到目前小程序已经提供了5种文件类型. 解构小程序的几种方式,其中一种方式就 ...
- CSS3弹性盒模型 display:box
刚开始做网页时就有一个困惑,为什么display:block只能垂直排列,如果要水平排列就要使用float:left等方式.这种方法最难受的当然是当子元素的数量改变时,需要去修改子元素的宽度使重新适应 ...
- HTML语言笔记
html语言即超文本标记语言. 超文本标记语言,标准通用标记语言下的一个应用. "超文本"就是指页面内可以包含图片.链接,甚至音乐.程序等非文字元 ...
- Python并发编程协程(Coroutine)之Gevent
Gevent官网文档地址:http://www.gevent.org/contents.html 基本概念 我们通常所说的协程Coroutine其实是corporate routine的缩写,直接翻译 ...
- Swing-JSlider用法-入门
JSlider是Swing中的滑块控件,在交互过程中用户可拖动它来实现数值的调整.它具有3个基本参数,分别为:最小值.最大值和初始值,如果不指定数值,则默认值分别为:0,100,50.滑块的值发生改变 ...
- 201521123014 《Java程序设计》第6周学习总结
1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图,对面向对象思想进行一个总结. 1.2 可选:使用常规方法总结其他上课内容. GUI与Sw ...
- 201521123038 《Java程序设计》 第五周学习总结
201521123038 <Java程序设计> 第五周学习总结 1. 本周学习总结 2. 书面作业 1.代码阅读:Child压缩包内源代码 1.1 com.parent包中Child.ja ...
- 201521123061 《Java程序设计》第五周学习总结
201521123061 <Java程序设计>第五周学习总结 1. 本周学习总结 1.1 尝试使用思维导图总结有关多态与接口的知识点. 1.2 可选:使用常规方法总结其他上课内容. 1.代 ...
- Java中如何引入结对编程
引自微信: 很多同学说: 我程序写得好,ACM比赛能得分, 就好了,软件工程讲的那些有用么? 有些学校的 <软件工程>课,由于要求太简单,反而不能说明软件工程的价值. 其实好办, 让学生结 ...