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

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM