fx 模块为利用 CSS3 的过渡和动画的属性为 Zepto 提供了动画的功能,在 fx 模块中,只做了事件和样式浏览器前缀的补全,没有做太多的兼容。对于不支持 CSS3 过渡和动画的, Zepto 的处理也相对简单,动画立即完成,马上执行回调。

读 Zepto 源码系列文章已经放到了github上,欢迎star: reading-zepto

源码版本

本文阅读的源码为 zepto1.2.0

GitBook

reading-zepto

内部方法

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'] = ''

获取浏览器前缀后,为所有的 transitionanimation 属性加上对应的前缀,都初始化为 '',方便后面使用。

方法

$.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 ,即 slowfast 甚至 _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
}

如果 propertiesstring, 即 properties 为动画名,则设置动画对应的 cssdurationdelay 都加上了 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 方法,使得 propertiescss 样式名( key )支持驼峰式的写法。

if (transforms) cssValues[transform] = transforms, cssProperties.push(transform)

这段是检测是否有 transform ,如果有,也将 transform 存入 cssValuescssProperties 中。

接下来判断动画是否开启,并且是否有过渡属性,如果有,则设置对应的值。

回调函数的处理

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 会在回调执行时设置成 truesetTimeout 的回调函数不会再重复执行。

触发页面回流

 // 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 不大于零时,可以是参数设置错误,也可能是浏览器不支持过渡或动画,就立即执行回调函数。

系列文章

  1. 读Zepto源码之代码结构
  2. 读Zepto源码之内部方法
  3. 读Zepto源码之工具函数
  4. 读Zepto源码之神奇的$
  5. 读Zepto源码之集合操作
  6. 读Zepto源码之集合元素查找
  7. 读Zepto源码之操作DOM
  8. 读Zepto源码之样式操作
  9. 读Zepto源码之属性操作
  10. 读Zepto源码之Event模块
  11. 读Zepto源码之IE模块
  12. 读Zepto源码之Callbacks模块
  13. 读Zepto源码之Deferred模块
  14. 读Zepto源码之Ajax模块
  15. 读Zepto源码之Assets模块
  16. 读Zepto源码之Selector模块
  17. 读Zepto源码之Touch模块
  18. 读Zepto源码之Gesture模块
  19. 读Zepto源码之IOS3模块

附文

参考

License

署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)

作者:对角另一面

读Zepto源码之Fx模块的更多相关文章

  1. 读Zepto源码之fx_methods模块

    fx 模块提供了 animate 动画方法,fx_methods 利用 animate 方法,提供一些常用的动画方法.所以 fx_methods 模块依赖于 fx 模块,在引入 fx_methods ...

  2. 读Zepto源码之Stack模块

    Stack 模块为 Zepto 添加了 addSelf 和 end 方法. 读 Zepto 源码系列文章已经放到了github上,欢迎star: reading-zepto 源码版本 本文阅读的源码为 ...

  3. 读Zepto源码之Form模块

    Form 模块处理的是表单提交.表单提交包含两部分,一部分是格式化表单数据,另一部分是触发 submit 事件,提交表单. 读 Zepto 源码系列文章已经放到了github上,欢迎star: rea ...

  4. 读Zepto源码之Data模块

    Zepto 的 Data 模块用来获取 DOM 节点中的 data-* 属性的数据,和储存跟 DOM 相关的数据. 读 Zepto 源码系列文章已经放到了github上,欢迎star: reading ...

  5. 读Zepto源码之Callbacks模块

    Callbacks 模块并不是必备的模块,其作用是管理回调函数,为 Defferred 模块提供支持,Defferred 模块又为 Ajax 模块的 promise 风格提供支持,接下来很快就会分析到 ...

  6. 读Zepto源码之Deferred模块

    Deferred 模块也不是必备的模块,但是 ajax 模块中,要用到 promise 风格,必需引入 Deferred 模块.Deferred 也用到了上一篇文章<读Zepto源码之Callb ...

  7. 读Zepto源码之Ajax模块

    Ajax 模块也是经常会用到的模块,Ajax 模块中包含了 jsonp 的现实,和 XMLHttpRequest 的封装. 读 Zepto 源码系列文章已经放到了github上,欢迎star: rea ...

  8. 读Zepto源码之Selector模块

    Selector 模块是对 Zepto 选择器的扩展,使得 Zepto 选择器也可以支持部分 CSS3 选择器和 eq 等 Zepto 定义的选择器. 在阅读本篇文章之前,最好先阅读<读Zept ...

  9. 读Zepto源码之Touch模块

    大家都知道,因为历史原因,移动端上的点击事件会有 300ms 左右的延迟,Zepto 的 touch 模块解决的就是移动端点击延迟的问题,同时也提供了滑动的 swipe 事件. 读 Zepto 源码系 ...

随机推荐

  1. 汇编指令-MRS(读)和MSR(写)指令操作CPSR寄存器和SPSR寄存器使用(1)

    1.MSR和MRS指令介绍 MRS 指令:  对状态寄存器CPSR和SPSR进行读操作.通过读CPSR可以获得当前处理器的工作状态.读SPSR寄存器可以获得进入异常前的处理器状态(因为只有异常模式下有 ...

  2. 小程序脚本语言WXS,你想要的都在这里了

    WXS脚本语言是 Weixin Script脚本的简称,是JS.JSON.WXML.WXSS之后又一大小程序内部文件类型.截至到目前小程序已经提供了5种文件类型. 解构小程序的几种方式,其中一种方式就 ...

  3. CSS3弹性盒模型 display:box

    刚开始做网页时就有一个困惑,为什么display:block只能垂直排列,如果要水平排列就要使用float:left等方式.这种方法最难受的当然是当子元素的数量改变时,需要去修改子元素的宽度使重新适应 ...

  4. HTML语言笔记

     html语言即超文本标记语言.         超文本标记语言,标准通用标记语言下的一个应用.         "超文本"就是指页面内可以包含图片.链接,甚至音乐.程序等非文字元 ...

  5. Python并发编程协程(Coroutine)之Gevent

    Gevent官网文档地址:http://www.gevent.org/contents.html 基本概念 我们通常所说的协程Coroutine其实是corporate routine的缩写,直接翻译 ...

  6. Swing-JSlider用法-入门

    JSlider是Swing中的滑块控件,在交互过程中用户可拖动它来实现数值的调整.它具有3个基本参数,分别为:最小值.最大值和初始值,如果不指定数值,则默认值分别为:0,100,50.滑块的值发生改变 ...

  7. 201521123014 《Java程序设计》第6周学习总结

    1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图,对面向对象思想进行一个总结. 1.2 可选:使用常规方法总结其他上课内容. GUI与Sw ...

  8. 201521123038 《Java程序设计》 第五周学习总结

    201521123038 <Java程序设计> 第五周学习总结 1. 本周学习总结 2. 书面作业 1.代码阅读:Child压缩包内源代码 1.1 com.parent包中Child.ja ...

  9. 201521123061 《Java程序设计》第五周学习总结

    201521123061 <Java程序设计>第五周学习总结 1. 本周学习总结 1.1 尝试使用思维导图总结有关多态与接口的知识点. 1.2 可选:使用常规方法总结其他上课内容. 1.代 ...

  10. Java中如何引入结对编程

    引自微信: 很多同学说: 我程序写得好,ACM比赛能得分, 就好了,软件工程讲的那些有用么? 有些学校的 <软件工程>课,由于要求太简单,反而不能说明软件工程的价值. 其实好办, 让学生结 ...