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
事件,可以參考這種方式自己寫一個。