一、前言
這節介紹下tab(選項卡)模塊的源碼實現。
二、源碼
/* ========================================================================
* Bootstrap: tab.js v3.3.7(選項卡)
* http://getbootstrap.com/javascript/#tabs
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// TAB CLASS DEFINITION
// ====================
var Tab = function (element) {
// jscs:disable requireDollarBeforejQueryAssignment
this.element = $(element)
// jscs:enable requireDollarBeforejQueryAssignment
}
Tab.VERSION = '3.3.7'
Tab.TRANSITION_DURATION = 150
Tab.prototype.show = function () {
var $this = this.element
// tab菜單可能存在二級菜單,'ul:not(.dropdown-menu)' => 找到外層的ul元素
var $ul = $this.closest('ul:not(.dropdown-menu)')
// 讀取data-target屬性數據,如果不存在則讀取href屬性數據並格式化爲CSS選擇符
var selector = $this.data('target')
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
}
// 如果已經被選中則直接返回
if ($this.parent('li').hasClass('active')) return
// 之前被選中的最後一個tab a觸發hide事件,relatedTarget爲將要顯示的元素;將要顯示的tab a觸發show事件,relatedTarget爲之前被選中的
var $previous = $ul.find('.active:last a')
var hideEvent = $.Event('hide.bs.tab', {
relatedTarget: $this[0]
})
var showEvent = $.Event('show.bs.tab', {
relatedTarget: $previous[0]
})
$previous.trigger(hideEvent)
$this.trigger(showEvent)
if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
var $target = $(selector)
this.activate($this.closest('li'), $ul)
this.activate($target, $target.parent(), function () {
$previous.trigger({
type: 'hidden.bs.tab',
relatedTarget: $this[0]
})
$this.trigger({
type: 'shown.bs.tab',
relatedTarget: $previous[0]
})
})
}
Tab.prototype.activate = function (element, container, callback) {
var $active = container.find('> .active')
var transition = callback
&& $.support.transition
&& ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)
function next() {
// 如果是tab的下拉菜單的話,那麼$active是dropdown的li元素
$active
.removeClass('active')
.find('> .dropdown-menu > .active')
.removeClass('active')
.end()
.find('[data-toggle="tab"]')
.attr('aria-expanded', false)
element
.addClass('active')
.find('[data-toggle="tab"]')
.attr('aria-expanded', true)
if (transition) {
element[0].offsetWidth // reflow for transition
element.addClass('in')
} else {
element.removeClass('fade')
}
if (element.parent('.dropdown-menu').length) {
element
.closest('li.dropdown')
.addClass('active')
.end()
.find('[data-toggle="tab"]')
.attr('aria-expanded', true)
}
callback && callback()
}
// 如果是菜單元素直接執行next,否則等$active移除in再執行next方法
$active.length && transition ?
$active
.one('bsTransitionEnd', next)
.emulateTransitionEnd(Tab.TRANSITION_DURATION) :
next()
$active.removeClass('in')
}
// TAB PLUGIN DEFINITION
// =====================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.tab')
if (!data) $this.data('bs.tab', (data = new Tab(this)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.tab
$.fn.tab = Plugin
$.fn.tab.Constructor = Tab
// TAB NO CONFLICT
// ===============
$.fn.tab.noConflict = function () {
$.fn.tab = old
return this
}
// TAB DATA-API
// ============
var clickHandler = function (e) {
e.preventDefault()
Plugin.call($(this), 'show')
}
$(document)
.on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
.on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
}(jQuery);
三、component-animations.less源碼
//
// Component animations(組件動畫)
// --------------------------------------------------
// Heads up!
//
// We don't use the `.opacity()` mixin here since it causes a bug with text
// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552.
.fade {
opacity: 0;
.transition(opacity .15s linear);
&.in {
opacity: 1;
}
}
// 默認摺疊內容隱藏
.collapse {
display: none;
&.in { display: block; }
tr&.in { display: table-row; }
tbody&.in { display: table-row-group; }
}
.collapsing {
position: relative;
height: 0;
overflow: hidden;
.transition-property(~"height, visibility");
.transition-duration(.35s);
.transition-timing-function(ease);
}
四、應用 & 源碼分析
1、應用
<hr>
<p class="active-tab"><strong>激活的標籤頁</strong>:<span></span></p>
<p class="previous-tab"><strong>前一個激活的標籤頁</strong>:<span></span></p>
<hr>
<ul id="myTab" class="nav nav-tabs">
<li class="active">
<a href="#home" data-toggle="tab">菜鳥教程</a>
</li>
<li><a href="#ios" data-toggle="tab">iOS</a></li>
<li class="dropdown">
<a href="#" id="myTabDrop1" class="dropdown-toggle"
data-toggle="dropdown">Java<b class="caret"></b>
</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="myTabDrop1">
<li><a href="#jmeter" tabindex="-1" data-toggle="tab">jmeter</a></li>
<li><a href="#ejb" tabindex="-1" data-toggle="tab">ejb</a></li>
</ul>
</li>
</ul>
<div id="myTabContent" class="tab-content">
<div class="tab-pane fade in active" id="home">
<p>菜鳥教程是一個提供最新的web技術站點,本站免費提供了建站相關的技術文檔,幫助廣大web技術愛好者快速入門並建立自己的網站。菜鳥先飛早入行——學的不僅是技術,更是夢想。</p>
</div>
<div class="tab-pane fade" id="ios">
<p>iOS 是一個由蘋果公司開發和發佈的手機操作系統。最初是於 2007 年首次發佈 iPhone、iPod Touch 和 Apple
TV。iOS 派生自 OS X,它們共享 Darwin 基礎。OS X 操作系統是用在蘋果電腦上,iOS 是蘋果的移動版本。</p>
</div>
<div class="tab-pane fade" id="jmeter">
<p>jMeter 是一款開源的測試軟件。它是 100% 純 Java 應用程序,用於負載和性能測試。</p>
</div>
<div class="tab-pane fade" id="ejb">
<p>Enterprise Java Beans(EJB)是一個創建高度可擴展性和強大企業級應用程序的開發架構,部署在兼容應用程序服務器(比如 JBOSS、Web Logic 等)的 J2EE 上。
</p>
</div>
</div>
<script>
$(function () {
$('#myTab a:last').tab('show');
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
// 獲取已激活的標籤頁的名稱
var activeTab = $(e.target).text();
// 獲取前一個激活的標籤頁的名稱
var previousTab = $(e.relatedTarget).text();
$(".active-tab span").html(activeTab);
$(".previous-tab span").html(previousTab);
});
})
</script>
2、源碼分析
1、show
tab 插件可能內嵌下拉菜單插件 dropdown,所以需要判斷是否是 tab 插件鏈接還是存在於 dropdown
2、activate
核心方法,爲點擊相應元素添加 active 類,並移除前 active 類元素;如果具有 fade 類(見component-animations.less源碼),則在過渡執行完成後再回調