在很久之前,我们项目有一个动画功能,功能本身很简单,便是典型的右进左出,并且带动画功能

以当时来说,虽然很简单,但是受限于框架本身的难度,就直接使用了CSS3的方式完成了功能

当时主要使用transform与animation实现功能,并且用了一个settimeout执行回调,然后此事便不了了之了

但是出来混总是要还的,这不,最近相似的东西又提了出来,我们当然可以将原来的那套东西拿来用,但是看着那个settimeout总是不是滋味,因为这样捕捉回调的效果以及可能引起的BUG大家都懂,于是就想使用transitionEnd监控动画结束再执行相关回调,于是便有了一个有趣的想法

当时的心声

嗯,不行,这次我要写一个通用的东西,他至少有这些功能:

① 我可以给他一个CSS变化属性

② 我可以给他一个时间长度

③ 我可以给他一个动画曲线参数

有了以上东西我就可以让一个元素触发动画,并且对其注册transitionEnd事件,最后执行我们的回调,于是我基本就陷进去了

但是,我想着想着突然感觉不对,感觉以上东西好像在哪里见过,于是一个叫animate的东西冒了出来

突然一刹那,我有一个不妙的感觉,搞出来一看:

animate
animate(properties, [duration, [easing, [function(){ ... }]]])   ⇒ self
      animate(properties, { duration: msec, easing: type, complete: fn })   ⇒ self
      animate(animationName, { ... })   ⇒ self
  
对当前Zepto集合对象中元素进行css transition属性平滑过渡。

properties: 一个对象,该对象包含了css动画的值,或者css帧动画的名称。
duration (默认 400):以毫秒为单位的时间,或者一个字符串。
fast (200 ms)
slow (600 ms)
任何$.fx.speeds自定义属性
easing (默认 linear):指定动画的缓动类型,使用以下一个:
ease
linear
ease-in / ease-out
ease-in-out
cubic-bezier(...)
complete:动画完成时的回调函数

于是,我自己的想法就只能呵呵了,这个就是我要的嘛......

而且zepto里面便是监听transitionEnd这个事件触发回调,所以,我们今天就来学习这个animate即可!!!

transitionEnd

transitionEnd是CSS3动画transition唯一的事件,我之前还去找个transitionStart,米有找到......

介绍他之前,我们先来个简单的例子,W3C上面的例子:

<!DOCTYPE html>
<html>
<head>
    <style>
        div { width: 100px; height: 100px; background: blue; transition: width 2s; -moz-transition: width 2s; /* Firefox 4 */ -webkit-transition: width 2s; /* Safari and Chrome */ -o-transition: width 2s; /* Opera */ }
        
        div:hover { width: 300px; }
    </style>
</head>
<body>
    <div>
    </div>
    <p>
        请把鼠标指针移动到蓝色的 div 元素上,就可以看到过渡效果。</p>
    <p>
        <b>注释:</b>本例在 Internet Explorer 中无效。</p>
</body>
</html>

好了,现在若是我们要在动画结束时候加一个事件该怎么办呢? 

<!DOCTYPE html>
<html>
<head>
    <style>
        div { width: 100px; height: 100px; background: blue; transition: width 1s; -moz-transition: width 1s; /* Firefox 4 */ -webkit-transition: width 1s; /* Safari and Chrome */ -o-transition: width 1s; /* Opera */ }
        
        div:hover { width: 300px; }
    </style>
</head>
<body>
    <div id="demo">
    </div>
    <br />
    <span id="msg"></span>
    <p>
        请把鼠标指针移动到蓝色的 div 元素上,就可以看到过渡效果。</p>
    <p>
        <b>注释:</b>本例在 Internet Explorer 中无效。</p>
    <script type="text/javascript">
        var demo = document.getElementById('demo');
        var msg = document.getElementById('msg');

//        eventType(this.scroller, 'transitionend', this);
//        eventType(this.scroller, 'webkitTransitionEnd', this);
//        eventType(this.scroller, 'oTransitionEnd', this);
//        eventType(this.scroller, 'MSTransitionEnd', this);

        demo.addEventListener('webkitTransitionEnd', function () {
            msg.innerHTML = '事件回调,当前原始宽度:' + window.getComputedStyle(demo).width;
        });
        
    </script>
</body>
</html>

这个例子虽然简单却很好的说明了一些问题,现在我们就来简单模拟一下animate

简单模拟animate

既然zepto已经很好的实现了该功能,我们这里就简单的模拟下即可,然后看看zepto源码

var demo = document.getElementById('demo');
var msg = document.getElementById('msg');

//简单模拟animate,参数问题就不管他了,暂时只考虑width吧
function animate(el, css, time, fn) {
      
  if (!el) return;

  var callback = function () {
    fn(arguments);
    el.removeEventListener('webkitTransitionEnd', callback);
  };

  el.addEventListener('webkitTransitionEnd', callback);

  for (var k in css) {
    //这里暂时只考虑webkit内核
    el.style['-webkit-transition'] = k + ' ' + time + 's';
  }

  for (var k in css) {
    //这里暂时只考虑webkit内核
    el.style[k] = css[k];
  }
}

demo.addEventListener('mouseenter', function () {
  animate(demo, { width: '300px' }, 1, fn);
});

demo.addEventListener('mouseout', function () {
  animate(demo, { width: '100px' }, 2, fn);
});

var fn = function () {
  msg.innerHTML = '事件回调,当前原始宽度:' + window.getComputedStyle(demo).width;
}

这是一个简单的实现,每次执行animate的时候,先会执行一次transitionEnd的事件注册,并且执行一次后就销毁

第二步为其设置transition属性,如果可以的话,这里最好是可以消除

最后一步就是为其设置css属性即可整个逻辑很简单,大概原理就是这样,我接下来来看看zepto高大上的实现!!!

zepto高大上的animate

zepto要实现以上代码的话,这样搞:

var demo = $('#demo');
var msg = $('#msg');

var fn = function () {
  msg.html('事件回调,当前原始宽度:' + demo.width());
};

demo.on('mouseenter', function () {
  demo.animate({ 'width': '300px' }, 1000, 'ease-out', fn);
});

demo.on('mouseout', function () {
  demo.animate({ 'width': '100px' }, 2000, 'ease-out', fn);
});

然后我们现在来看看源码:

;(function($, undefined){
  var prefix = '', eventPrefix, endEventName, endAnimationName,
    vendors = { Webkit: 'webkit', Moz: '', O: 'o' },
    document = window.document, 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 = {}

  function dasherize(str) { return str.replace(/([a-z])([A-Z])/, '$1-$2').toLowerCase() }
  function normalizeEvent(name) { return eventPrefix ? eventPrefix + name : name.toLowerCase() }

  $.each(vendors, function(vendor, event){
    if (testEl.style[vendor + 'TransitionProperty'] !== undefined) {
      prefix = '-' + vendor.toLowerCase() + '-'
      eventPrefix = event
      return false
    }
  })

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

  $.fx = {
    off: (eventPrefix === undefined && testEl.style.transitionProperty === undefined),
    speeds: { _default: 400, fast: 200, slow: 600 },
    cssPrefix: prefix,
    transitionEnd: normalizeEvent('TransitionEnd'),
    animationEnd: normalizeEvent('AnimationEnd')
  }

  $.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)
  }

  $.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 * 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
  }

  testEl = null
})(Zepto)
View Code

相关文章: