资讯专栏INFORMATION COLUMN

读Zepto源码之Fx模块

luzhuqun / 399人阅读

摘要:绑定过渡或动画的结束事件绑定过渡或动画的结束事件,在动画结束时,执行处理过的回调函数。的回调执行比动画时间长,目的是让事件响应在之前,如果浏览器支持过渡或动画事件,会在回调执行时设置成,的回调函数不会再重复执行。

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

系列文章

读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模块

附文

译:怎样处理 Safari 移动端对图片资源的限制

参考

一步一步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)

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

作者:对角另一面

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/88915.html

相关文章

  • Zepto源码fx_methods模块

    摘要:所以模块依赖于模块,在引入前必须引入模块。原有的方法分析见读源码之样式操作方法首先调用原有的方法,将元素显示出来,这是实现动画的基本条件。如果没有传递,或者为值,则表示不需要动画,调用原有的方法即可。 fx 模块提供了 animate 动画方法,fx_methods 利用 animate 方法,提供一些常用的动画方法。所以 fx_methods 模块依赖于 fx 模块,在引入 fx_m...

    junbaor 评论0 收藏0
  • Zepto源码Stack模块

    摘要:读源码系列文章已经放到了上,欢迎源码版本本文阅读的源码为改写原有的方法模块改写了以上这些方法,这些方法在调用的时候,会为返回的结果添加的属性,用来保存原来的集合。方法的分析可以看读源码之模块。 Stack 模块为 Zepto 添加了 addSelf 和 end 方法。 读 Zepto 源码系列文章已经放到了github上,欢迎star: reading-zepto 源码版本 本文阅读的...

    crossea 评论0 收藏0
  • Zepto源码Form模块

    摘要:模块处理的是表单提交。表单提交包含两部分,一部分是格式化表单数据,另一部分是触发事件,提交表单。最终返回的结果是一个数组,每个数组项为包含和属性的对象。否则手动绑定事件,如果没有阻止浏览器的默认事件,则在第一个表单上触发,提交表单。 Form 模块处理的是表单提交。表单提交包含两部分,一部分是格式化表单数据,另一部分是触发 submit 事件,提交表单。 读 Zepto 源码系列文章已...

    陈江龙 评论0 收藏0
  • Zepto源码Data模块

    摘要:的模块用来获取节点中的属性的数据,和储存跟相关的数据。获取节点指定的缓存值。如果存在,则删除指定的数据,否则将缓存的数据全部删除。为所有下级节点,如果为方法,则节点自身也是要被移除的,所以需要将自身也加入到节点中。 Zepto 的 Data 模块用来获取 DOM 节点中的 data-* 属性的数据,和储存跟 DOM 相关的数据。 读 Zepto 源码系列文章已经放到了github上,欢...

    wua_wua2012 评论0 收藏0
  • Zepto源码Gesture模块

    摘要:模块基于上的事件的封装,利用属性,封装出系列事件。这个判断需要引入设备侦测模块。然后是监测事件,根据这三个事件,可以组合出和事件。其中变量对象和模块中的对象的作用差不多,可以先看看读源码之模块对模块的分析。 Gesture 模块基于 IOS 上的 Gesture 事件的封装,利用 scale 属性,封装出 pinch 系列事件。 读 Zepto 源码系列文章已经放到了github上,欢...

    coolpail 评论0 收藏0

发表评论

0条评论

luzhuqun

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<