Bootstrap transition.js 插件详解
Bootstrap 自带的 JavaScript 插件的动画效果几乎都是使用 CSS 过渡实现的,而其中的 transition.js 就是为了判断当前使用的浏览器是否支持 CSS 过渡。下面先来简单了解下 CSS 过渡。
CSS 过渡
CSS 过渡是指在 CSS 属性发生改变时在一段时间内平滑地过渡,使用 CSS 伪类可以很方便地使用:
a { color: #333; transition: color 1s linear; } a:hover { color: #36f; }
这里给链接设置了一个 1秒的颜色过渡效果,当鼠标经过链接激活链接 :hover
状态的时候文字的颜色会从黑色平滑地变化为蓝色,鼠标移开时又平滑地变回来。
仅仅依靠伪类来使用过渡显得太单调了,使用 JavaScript 来动态添加删除 class
才能尽情地玩弄过渡:
/* 这是一个圆 */ .circle { background-color: red; border-radius: 50%; height: 100px; margin-left: 220px; transition: transform 1s linear; width: 100px; } /* 添加 left 类水平向左偏移 200 像素 */ .circle.left { transform: translateX(-200px); } /* 添加 right 类水平向右偏移 200 像素 */ .circle.right { transform: translateX(200px); }
JavaScript 的工作就是简单的删除和添加 class:
function toLeft() { $('.circle').removeClass('right').addClass('left'); } function toRight() { $('.circle').removeClass('left').addClass('right'); }
你让它往左,它不敢往右! demo 上面的例子展示了当元素状态改变或者添加了某个 class
的时候过渡就开始发生了,那么如何知道过渡什么时候结束呢?
Transitionend 事件
想要知道过渡什么时候结束,就要监听 transitionend
事件(事件名称全是小写字母)。
$('.circle').one('transitionend', function() {
alert('过渡结束啦!');
});
这里使用 one
方法而不是 on
方法是为了避免 transitionend
事件多次执行。 one
方法添加的事件回调只会执行一次,更多信息参考 官方 API 。
不过这只是标准的事件名称写法,在标准之前浏览器有各自的实现方式以及不同的事件名称(比如低版本的 Chrome 和 Safari 的该事件名称就叫 webkitTransitionEnd
),所以为了兼容更多的浏览器,一种比较笨拙的方式可以写成像下面这样:
$('.circle').one('transitionend webkitTransitionEnd oTransitionEnd otransitionend', function() {});
很长一串,而且这种写法还有一点问题!
在这里 可以看到为什么是上面这些事件名称,而没有 msTransitionEnd
之类。
为了根据浏览器更有针对性地添加 transitionend
事件回调,而不是像上面那样一骨碌地全加上,就需要先判断一下该浏览器到底支持哪种 transitionend
事件名称,判断方法则是根据浏览器支持 CSS 过渡的属性名称而定:
var el = document.createElement('bootstrap'); // 创建一个元素用于测试 if (el.style.transition !== undefined) { // 判断元素的 style.transition 属性如果存在 // 则以此类推该浏览器支持标准的 CSS transition 属性以及标准的 transitionend 事件 } else if (el.style.WebkitTransition !== undefined) { // 判断元素的 style.WebkitTransition 属性如果存在 // 则该浏览器支持替代的 webkitTransitionEnd 事件 } else {...}
按照当前的主流浏览器趋势总共需要判断四种不同前缀的属性名称:
el.style.transition
el.style.WebkitTransition
el.style.MozTransition
el.style.OTransition
判断方式就是这样,废话就说到这里,直接上正牌代码,Bootstrap transition.js 内部的判断函数:
function transitionEnd() { // 创建一个元素用于测试 var el = document.createElement('bootstrap'); // 将所有主流浏览器实现方式整合成一个对象,用于遍历 // key 是属性名称 // value 是事件名称 var transEndEventNames = { WebkitTransition : 'webkitTransitionEnd', MozTransition : 'transitionend', OTransition : 'oTransitionEnd otransitionend', transition : 'transitionend' }; // 循环遍历上面那个对象,判断 CSS 属性是否存在 for (var name in transEndEventNames) { if (el.style[name] !== undefined) { return { end: transEndEventNames[name] }; } } return false; }
执行该函数可以得到一个对象 {end: 'transitionend'}
或者 false
(表示浏览器不支持 CSS 过渡),该对象的 end
属性保存着浏览器所支持的 transitionend
事件对应的名称:
var transition = transitionEnd();
比如我使用低版本的 Chrome 浏览器的话,那么得到的对象就是 {end: 'webkitTransitionEnd'}
这样;如果使用 IE 8 则是 false
,然后就可以添加该事件的回调函数了:
// 如果 transition 为 false 则不添加事件回调 transition && $('.circle').one(transition.end, function() {});
为了与 jQuery 保持一致,将该返回结果赋值到 $.support.transition
上:
$.support.transition = transitionEnd(); // 使用方式是类似的 $.support.transition && $('.circle').one($.support.transition.end, function() {});
EmulateTransitionEnd
事件名称的问题基本上解决了,但是这个事件有个问题就是有时根本不会触发,这是因为属性值没有发生变化或没有绘制行为发生。要确保每次回调都会被调用,我们增加一个定时器即可:
$.fn.emulateTransitionEnd = function(duration) { var called = false; // transitionend 事件是否已触发标识 var $el = this; $(this).one($.support.transition.end, function () { called = true; // 表示已触发 }); var callback = function() { if (!called) { $($el).trigger($.support.transition.end); // 未触发,强制其触发 } }; setTimeout(callback, duration); // 一段时间后检测是否触发 return this; };
该方法的作用是一段时间(就是过渡持续的时间 transition-duration
)过后如果 transitionend
事件没有发生则强制在该元素上触发这个事件。
$('.circle').one($.support.transition.end, function() {}); $('.circle').emulateTransitionEnd(1000); // 这个时间是过渡持续的时间
这样确保过渡之后一定会有回调。到这里,基本上就差不多了,不过 $.support.transition.end
好恶心啊!能不能像添加其它事件回调一样使用事件名称字符串的形式,比如 'click'
,当然可以。
自定义事件
$(function () { $.support.transition = transitionEnd(); // 支持过渡的时候才执行后面的代码 if (!$.support.transition) {return;} $.event.special.bsTransitionEnd = { bindType: $.support.transition.end, delegateType: $.support.transition.end, handle: function (e) { if ($(e.target).is(this)) { return e.handleObj.handler.apply(this, arguments); } } }; });
添加事件回调的时候就可以像这样:
$('.circle').one('bsTransitionEnd', function() {}) .emulateTransitionEnd(1000);
其它
CSS 动画同样也有一个 animationend
事件,同时还有 animationstart
和 animationiteration
事件,可以参考这种方式自己写一个。