博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
读Zepto源码之Fx模块
阅读量:6705 次
发布时间:2019-06-25

本文共 9561 字,大约阅读时间需要 31 分钟。

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

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

源码版本

本文阅读的源码为

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'] = ''复制代码

获取浏览器前缀后,为所有的 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 / 1000if (delay === undefined) delay = 0if ($.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 animatethis.size() && this.get(0).clientLeftthis.css(cssValues)复制代码

这里用了点黑科技,读取 clientLeft 属性,触发页面的回流,使得动画的样式设置上去时可以立即执行。

具体可以这篇文章中的解释:

过渡时间不大于零的回调处理

if (duration <= 0) setTimeout(function() {  that.each(function(){ wrappedCallback.call(this) })}, 0)复制代码

duration 不大于零时,可以是参数设置错误,也可能是浏览器不支持过渡或动画,就立即执行回调函数。

系列文章

附文

参考

  • /)

License

最后,所有文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:

作者:对角另一面

转载地址:http://publo.baihongyu.com/

你可能感兴趣的文章
Lua1.0 编译准备
查看>>
存储虚拟化方案的选择与设计完全指南
查看>>
基于Nagios网络监控平台的实现--具体事例
查看>>
mahout in action ----分类的原理
查看>>
数据库:mysql的使用
查看>>
dubbo分布式事务的设想
查看>>
在OS上kill掉v$session中status值为killed的进程
查看>>
网站网络带宽 和 流量的区别
查看>>
JAVA学习日志(7-2-子父类中成员的特点)
查看>>
Struts 标签与ognl的性能问题
查看>>
WordPress的replytocom参数避免被百度蜘蛛抓取解决方案,将这个参数屏蔽掉
查看>>
浅析如何在Linux系统中如何安装软件
查看>>
VirtualBox和VMware镜像文件互转
查看>>
MySql多表联查
查看>>
硬盘整数分区最精确地方法
查看>>
Java基础--关于分支、循环、数组的部分练习题及详解
查看>>
分布式文件存储
查看>>
LVS高可用方案
查看>>
环形链表解决约瑟夫游戏问题
查看>>
NFS服务器
查看>>