【小贴士】关于transitionEnd/animate的一个有趣故事

简介:
前言

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

以当时来说,虽然很简单,但是受限于框架本身的难度,就直接使用了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);
});
复制代码
然后我们现在来看看源码:

 View Code
看代码首先还是看入口,我们这里的入口就是animate

demo.animate({ 'width': '300px' }, 1000, 'ease-out', fn);
 1 $.fn.animate = function(properties, duration, ease, callback, delay){
 2   if ($.isFunction(duration))
 3     callback = duration, ease = undefined, duration = undefined
 4   if ($.isFunction(ease))
 5     callback = ease, ease = undefined
 6   if ($.isPlainObject(duration))
 7     ease = duration.easing, callback = duration.complete, delay = duration.delay, duration = duration.duration
 8   if (duration) duration = (typeof duration == 'number' ? duration :
 9                   ($.fx.speeds[duration] || $.fx.speeds._default)) / 1000
10   if (delay) delay = parseFloat(delay) / 1000
11   return this.anim(properties, duration, ease, callback, delay)
12 }
他首先这里做了一些默认处理,因为我们传递的参数是不定的,所以第二个参数极有可能是回调

所以他第一句就是做一个简单的判断,第二句也不例外

其实他整个animate都是做一些属性处理,并未做实际的事情,具体的实现还是在anim中

 1 $.fn.anim = function(properties, duration, ease, callback, delay){
 2   var key, cssValues = {}, cssProperties, transforms = '',
 3       that = this, wrappedCallback, endEvent = $.fx.transitionEnd,
 4       fired = false
 5 
 6   if (duration === undefined) duration = $.fx.speeds._default / 1000
 7   if (delay === undefined) delay = 0
 8   if ($.fx.off) duration = 0
 9 
10   if (typeof properties == 'string') {
11     // keyframe animation
12     cssValues[animationName] = properties
13     cssValues[animationDuration] = duration + 's'
14     cssValues[animationDelay] = delay + 's'
15     cssValues[animationTiming] = (ease || 'linear')
16     endEvent = $.fx.animationEnd
17   } else {
18     cssProperties = []
19     // CSS transitions
20     for (key in properties)
21       if (supportedTransforms.test(key)) transforms += key + '(' + properties[key] + ') '
22       else cssValues[key] = properties[key], cssProperties.push(dasherize(key))
23 
24     if (transforms) cssValues[transform] = transforms, cssProperties.push(transform)
25     if (duration > 0 && typeof properties === 'object') {
26       cssValues[transitionProperty] = cssProperties.join(', ')
27       cssValues[transitionDuration] = duration + 's'
28       cssValues[transitionDelay] = delay + 's'
29       cssValues[transitionTiming] = (ease || 'linear')
30     }
31   }
32 
33   wrappedCallback = function(event){
34     if (typeof event !== 'undefined') {
35       if (event.target !== event.currentTarget) return // makes sure the event didn't bubble from "below"
36       $(event.target).unbind(endEvent, wrappedCallback)
37     } else
38       $(this).unbind(endEvent, wrappedCallback) // triggered by setTimeout
39 
40     fired = true
41     $(this).css(cssReset)
42     callback && callback.call(this)
43   }
44   if (duration > 0){
45     this.bind(endEvent, wrappedCallback)
46     // transitionEnd is not always firing on older Android phones
47     // so make sure it gets fired
48     setTimeout(function(){
49       if (fired) return
50       wrappedCallback.call(that)
51     }, (duration * 1000) + 25)
52   }
53 
54   // trigger page reflow so new elements can animate
55   this.size() && this.get(0).clientLeft
56 
57   this.css(cssValues)
58 
59   if (duration <= 0) setTimeout(function() {
60     that.each(function(){ wrappedCallback.call(this) })
61   }, 0)
62 
63   return this
64 }
传入anim的参数真的就没有什么问题了

第一个是css属性

第二个是动画运行时间

第三个是动画曲线,这个很神奇,没事不要去搞他

第四个是回调函数

第五个是什么就暂时不知道是什么了

进入后,10行之前还是在做容错性处理,这里我们最主要关注点放在endEvent上面

这个东西由前面的fx对象获取:

$.fx = {
  off: (eventPrefix === undefined && testEl.style.transitionProperty === undefined),
  speeds: { _default: 400, fast: 200, slow: 600 },
  cssPrefix: prefix,
  transitionEnd: normalizeEvent('TransitionEnd'),
  animationEnd: normalizeEvent('AnimationEnd')
}
而我们要做的chrome、firefox等兼容全部被normalizeEvent做了,这里

vendors = { Webkit: 'webkit', Moz: '', O: 'o' }
testEl = document.createElement('div')
$.each(vendors, function(vendor, event){
  if (testEl.style[vendor + 'TransitionProperty'] !== undefined) {
    prefix = '-' + vendor.toLowerCase() + '-'
    eventPrefix = event
    return false
  }
})
这里根据这种方式得出了兼容事件的前缀,webkit的话会返回webkit前缀:

$.fx.transitionEnd => "webkitTransitionEnd"
然后下一步简单仍然是先设置transition相关的属性,并且指定事件结束事件回调:

cssValues[animationName] = properties
cssValues[animationDuration] = duration + 's'
cssValues[animationDelay] = delay + 's'
cssValues[animationTiming] = (ease || 'linear')
endEvent = $.fx.animationEnd
当然,如果我们传入的CSS不止一个的话,下面的处理会相对复杂点

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')
}
这里先放下处理Transform等新属性之外,与上面的操作无他

然后关键步骤又来了,

 1 wrappedCallback = function(event){
 2   if (typeof event !== 'undefined') {
 3     if (event.target !== event.currentTarget) return // makes sure the event didn't bubble from "below"
 4     $(event.target).unbind(endEvent, wrappedCallback)
 5   } else
 6     $(this).unbind(endEvent, wrappedCallback) // triggered by setTimeout
 7 
 8   fired = true
 9   $(this).css(cssReset)
10   callback && callback.call(this)
11 }
12 if (duration > 0){
13   this.bind(endEvent, wrappedCallback)
14   // transitionEnd is not always firing on older Android phones
15   // so make sure it gets fired
16   setTimeout(function(){
17     if (fired) return
18     wrappedCallback.call(that)
19   }, (duration * 1000) + 25)
20 }
他这里首先声明了回调函数wrappedCallback,这个函数首先干的事情是注销事件

然后执行传入的回调,这里将this指向了调用者,也就是绑定的标签

后面便是真实的事件绑定操作,里面仍然有一个延时函数执行

其中有一个状态机fired,来记录该事件是否触发

然后就为css复制了,这个时候动画执行结束便会触发transitionEnd事件了

最后,代码结束.......

结语

今天,我们简单的说了下zepto的animate方法,希望对各位有帮助,若是文中有任何问题请提出




本文转自叶小钗博客园博客,原文链接:http://www.cnblogs.com/yexiaochai/p/3602303.html,如需转载请自行联系原作者
相关文章
|
机器学习/深度学习 前端开发
学css动画很难? 不妨来看看这篇css动画
CSS动画 1. CSS动画的基本概念 动画是一种使元素从一种样式逐渐转变为另一种样式的效果。CSS动画是通过改变元素的样式来实现的,这些样式可以是元素的位置、大小、颜色、背景、边框等等。
|
前端开发 JavaScript
复刻解析一个流光溢彩炫到掉渣的 CSS 动画按钮
最近在看 next.js 官网是被引流到 conf 页面,发现上面有一个炫酷的按钮,按钮的边框色彩不断变动给人感觉是光在随着按钮旋转一般,感觉挺酷的,复刻一下讲解下原理。
|
前端开发 JavaScript
手把手用 CSS 绘制一个忽闪忽闪的可爱小幽灵
通过几个 div 的拼接,和 css 的样式加成,便可制作出这样一个可爱的小幽灵。下面一起看下制作过程吧。
|
存储 程序员
七夕快到了,用SwiftUI做一个表达爱意的心形动画
传统的七夕快到了,作为一个程序猿,最浪漫的礼物当然是自己写的啦! 思来想去也不知道写什么好,在某天在某音上学习时看到点赞的动画效果还不错,那不如就做一个表达爱意的动画吧。
297 0
七夕快到了,用SwiftUI做一个表达爱意的心形动画
|
前端开发 JavaScript 计算机视觉
手撸一个在线css三角形生成器
为了提高 前端开发 效率, 笔者先后写了上百个前端工具, 有些是给公司内部使用的, 有些单纯是因为自己太“懒”, 不想写代码, 所以才“被迫”做的. 接下来介绍的一款工具——css三角形生成器也是因为之前想要解放设计师的生产力, 自己又懒得切图或者写css代码, 所以想来想去还是自己做一个能自动生成css三角形代码的工具吧.
302 0
|
前端开发 安全
仅使用 CSS 旋转制作八卦迷惑动画 🧘‍♀️
我之所以将其拿出来分享,一方面觉得它看起来比较酷,也像原作者所说的那样:看起来是个令人生畏的病毒,另一方面觉得作者的编码思路和代码值得了解学习下。
仅使用 CSS 旋转制作八卦迷惑动画 🧘‍♀️
|
前端开发 JavaScript 程序员
文字烟花特效,用JS代码示爱!程序员小姐姐用这个代码挽回了爱情~
竟然有一个前端小阿姨问我,如果想要烟花放出来是文字的话怎么实现,她要给男朋友做一个。 好家伙,这狗粮洒一地呀
518 0
文字烟花特效,用JS代码示爱!程序员小姐姐用这个代码挽回了爱情~
|
JavaScript Android开发
第二十二章:动画(二十一)
使用AnimationExtensions为什么ViewExtensions不包含ColorTo动画? 这种方法没有你最初假设的那么明显有三个可能的原因:首先,VisualElement定义的唯一Color属性是BackgroundColor,但通常不是要设置动画的Color属性。
545 0
|
JavaScript Android开发
第二十二章:动画(二十)
实现贝塞尔动画一些图形系统实现动画,该动画沿着贝塞尔曲线移动视觉对象,甚至(可选地)旋转视觉对象,使其保持与曲线相切。Bezier曲线以法国工程师兼数学家PierreBézier的名字命名,他在雷诺工作期间开发了用于汽车车身交互式计算机辅助设计的曲线。
618 0
|
JavaScript Android开发
第二十二章:动画(十九)
更多你自己的等待方法之前,您已经了解了如何将TaskCompletionSource与Device.StartTimer一起使用来编写自己的异步动画方法。 您还可以将TaskCompletionSource与Animation类结合使用,编写自己的异步动画方法,类似于ViewExtensions类中的方法。
633 0