Bootstrap外掛modal原始碼的學習
使用說明
兩部分需要定義:
- 模態框的觸發元素,需要
data-toggle="modal"
,以便被初始化;需要data-target="#example"
或href
指定模態框 - 模態框,模態框容器需要與
data-target
對應的類或id
,其內容部分可以用"modal-content
"的類來美化樣式
<button type="button" class="btn" data-toggle="modal" data-target="#myModal"> 觸發按鈕 </button>
<div class="modal" id="myModal">
<div class="modal-content">
<div class="modal-header">
title
</div>
<div class="modal-body">
body
</div>
</div>
</div>
核心思想
-
頁面完成載入後,為所有含有
data-toggle="modal"
的元素繫結點選事件 -
模態框出現的邏輯:先出現遮罩層,再出現模態框
- 為
body
增加"modal-open
"類,作用是給body
增加overflow:hidden
隱藏頁面的滾動條。給模態框增加overflow-x:hidden;overflow-y:auto
美化樣式 - 呼叫
dropback
函式,變數shown
為true
,所以插入出現遮罩層(若遮罩層需要過渡,過渡的邏輯也在這裡) - 在傳給
dropback
的回撥裡(即完成遮罩層出現後),呼叫$el.show()
方法使模態框出現(display:block
),再增加"in
"類使他過渡的出現
- 為
-
模態框的隱藏邏輯:先隱藏模態框,再隱藏遮罩層
- 先去除"
in
“類,使模態框opacity
變為0
先去除”in
"類,使模態框opacity
變為0
- 在過渡效果的完成後用
$el.hide()
隱藏模態框 呼叫dropback
函式,變數shown
為false
,所以移除遮罩層 - 在出給
dropback
的回撥裡(即完成遮罩層的隱藏後),清除"modal-open
"類
- 先去除"
初始化
為觸發元素繫結點選事件
// 在初始化時為含data-toggle屬性的元素繫結點選事件
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
var $this = $(this)
var href = $this.attr('href')
var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
var option = $target.data('bs.modal') ? 'toggle' : $.extend({
remote: !/#/.test(href) && href
}, $target.data(), $this.data())
if ($this.is('a')) e.preventDefault()
$target.one('show.bs.modal', function (showEvent) {
if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
$target.one('hidden.bs.modal', function () {
$this.is(':visible') && $this.trigger('focus')
})
})
// 把對應的模態框的juqery物件作為this,傳入Plugin中
// 把引數和觸發元素作為引數引數Plugin中
Plugin.call($target, option, this)
})
結構分析
模態框的核心是
show()
檢查並繫結鍵盤、調整大小的事件,然後呼叫backdrop()
使遮罩層和模態框順序出現hide()
接觸事件繫結,然後呼叫hideModal()
隱藏模態框,移除遮罩層
但是為了考慮全面。Bootstrap
做了太多情況的考慮,導致程式碼變得很複雜。。。
Plugin入口分析
預設在這裡呼叫了show
方法
/**
* 取觸發元素上的data(),獲得對應引數並例項化Modal
* @param {object} option 觸發元素的快取資料物件,或字串直接呼叫方法
* @param {object} _relatedTarget 觸發元素的dom物件
*/
function Plugin(option, _relatedTarget) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.modal')
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
if (typeof option == 'string') data[option](_relatedTarget)
else if (options.show) data.show(_relatedTarget)
})
}
具體分析
- 建構函式
/**
* 初始化例項屬性,如有有remote則從遠端載入內容
* @param {object} element 模態框的dom物件
* @param {object} options 引數
*/
var Modal = function (element, options) {
this.options = options
this.$body = $(document.body)
this.$element = $(element) // 模態框的jquery物件
this.$dialog = this.$element.find('.modal-dialog')
this.$backdrop = null
this.isShown = null
this.originalBodyPad = null
this.scrollbarWidth = 0
this.ignoreBackdropClick = false
// 從指定url中load內容
if (this.options.remote) {
this.$element
.find('.modal-content')
.load(this.options.remote, $.proxy(function () {
this.$element.trigger('loaded.bs.modal')
}, this))
}
}
show
方法
/**
* 完成滾動條的檢查,鍵盤事件,視窗大小事件的繫結。在backdrop遮罩層出現後,才在回撥裡顯示模態框
* 順序是checkScrollbar->setScrollbar->escape->resize->backdrop->模態框顯示
* @param {object} _relatedTarget 觸發元素的dom物件
* @return {undefined} 無返回值
*/
Modal.prototype.show = function (_relatedTarget) {
var that = this
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
this.$element.trigger(e)
if (this.isShown || e.isDefaultPrevented()) return
this.isShown = true
this.checkScrollbar()
this.setScrollbar()
this.$body.addClass('modal-open')
this.escape()
this.resize()
this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
// 這裡不知道幹嘛
this.$dialog.on('mousedown.dismiss.bs.modal', function () {
that.$element.one('mouseup.dismiss.bs.modal', function (e) {
if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true
})
})
// 呼叫遮罩層出現和消失的邏輯,並傳入回撥
// 在回撥裡模態框出現,所以是先出現遮罩層再出現模態框。
this.backdrop(function () {
var transition = $.support.transition && that.$element.hasClass('fade')
if (!that.$element.parent().length) {
that.$element.appendTo(that.$body) // don't move modals dom position
}
// 這裡scrollTop(0)幹嘛?
that.$element
.show()
.scrollTop(0)
that.adjustDialog()
if (transition) {
that.$element[0].offsetWidth // force reflow
}
that.$element.addClass('in')
that.enforceFocus()
var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
transition ?
that.$dialog // wait for modal to slide in
.one('bsTransitionEnd', function () {
that.$element.trigger('focus').trigger(e)
})
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
that.$element.trigger('focus').trigger(e)
})
}
hide
方法
/** 與show相反,先取消一系列事件,在呼叫hideModal隱藏模態框,然後移除遮罩層 */
Modal.prototype.hide = function (e) {
if (e) e.preventDefault()
e = $.Event('hide.bs.modal')
this.$element.trigger(e)
if (!this.isShown || e.isDefaultPrevented()) return
this.isShown = false
this.escape()
this.resize()
$(document).off('focusin.bs.modal')
this.$element
.removeClass('in')
.off('click.dismiss.bs.modal')
.off('mouseup.dismiss.bs.modal')
this.$dialog.off('mousedown.dismiss.bs.modal')
$.support.transition && this.$element.hasClass('fade') ?
this.$element
.one('bsTransitionEnd', $.proxy(this.hideModal, this))
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
this.hideModal()
}
backdrop
方法:負責遮罩層的出現與消失邏輯
/**
* 負責模態框出現後,增加遮罩層,及模態框消失時取出遮罩層的邏輯
* @param {Function} callback 遮罩層出現後,及消失後的回撥
* @return {undefined} 無
*/
Modal.prototype.backdrop = function (callback) {
var that = this
var animate = this.$element.hasClass('fade') ? 'fade' : ''
// backdrop預設為true
// 如果已經show了
if (this.isShown && this.options.backdrop) {
var doAnimate = $.support.transition && animate
// 插入遮罩層
this.$backdrop = $(document.createElement('div'))
.addClass('modal-backdrop ' + animate)
.appendTo(this.$body)
this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
if (this.ignoreBackdropClick) {
this.ignoreBackdropClick = false
return
}
if (e.target !== e.currentTarget) return
this.options.backdrop == 'static'
? this.$element[0].focus()
: this.hide()
}, this))
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
this.$backdrop.addClass('in')
if (!callback) return
doAnimate ?
this.$backdrop
.one('bsTransitionEnd', callback)
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
callback()
} else if (!this.isShown && this.$backdrop) {
this.$backdrop.removeClass('in')
var callbackRemove = function () {
that.removeBackdrop()
callback && callback()
}
$.support.transition && this.$element.hasClass('fade') ?
this.$backdrop
.one('bsTransitionEnd', callbackRemove)
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
callbackRemove()
} else if (callback) {
callback()
}
}
- 與滾動條相關的
checkScrollbar
和setScrollbar
方法
/**
1. 檢查當前是否overflow,並獲取滾動條寬度
2. @return {[type]} [description]
*/
Modal.prototype.checkScrollbar = function () {
var fullWindowWidth = window.innerWidth // 視窗大小,包含滾動條的整個視窗
if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
var documentElementRect = document.documentElement.getBoundingClientRect()
// IE8沒有window.innerWidth,所以需要用左邊減右邊來獲得視窗大小
fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
}
// 視窗大小是否大於可視視窗大小,來判斷body寬度是否overflow。如果大於,則overflow
// 視窗大小比可視視窗大小大的值,其實就是滾動條的寬度(不用這個方法獲取滾動條寬度是,萬一頁面沒有滾動條,那就無法獲取滾動條寬度)
this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
this.scrollbarWidth = this.measureScrollbar()
}
/**
3. 如果有滾動條,則body需要padding-right,滾動條寬度的值
*/
Modal.prototype.setScrollbar = function () {
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
this.originalBodyPad = document.body.style.paddingRight || ''
if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
}
總結
-
window.innerWidth
可以獲得包含滾動條的整個視窗的大小(但IE8需用document.documentElement.getBoundingClientRect().left
減去right
) -
window.innerWidth
>document.body.clientWidth
來判斷是否有滾動條 -
也可以利用插入
div
,然後用el.offsetWidth
-el.clientWidth
來獲得當前瀏覽器中滾動條的寬度 -
"
fade
“類定義了opacity: 0
和transition:opacity .15s linear
而”.fade.in
"類定義了opacity:1
。這樣能很好的有解耦動畫效果和無效果 -
在利用
addClass('in')
做過度效果前,需要強制重排。BS常用el.offsetWidth
來做強制重排- 獲取
offsetTop、offsetLeft、offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle()
(currentStyle in IE)這些屬性時,都會引起重排
- 獲取
相關文章
- bootstrap外掛學習-bootstrap.modal.jsbootJS
- bootstrap-modal.js學習筆記(原始碼註釋)bootJS筆記原始碼
- 從bootstrap原始碼中學習Sass(一)boot原始碼
- octobercms 外掛學習 驗證碼
- Bootstrap模態框(Modal)boot
- mybatis原始碼學習:外掛定義+執行流程責任鏈MyBatis原始碼
- Qt Creator 原始碼學習筆記04,多外掛實現原理分析QT原始碼筆記
- vue 原始碼學習 - 例項掛載Vue原始碼
- Flutter外掛SharedPreferences原始碼分析Flutter原始碼
- 前端外掛之Bootstrap Dual Listbox使用前端boot
- bootstrap中的模態框(modal,彈出層)boot
- 學習bootstrap的整理。boot
- Egg 學習筆記 - 外掛的使用筆記
- 利用Bootstrap Paginator外掛和knockout.jsbootJS
- immutability-helper 外掛的基本使用(附原始碼)原始碼
- PG14:adminpack 外掛原始碼分析原始碼
- mybaits原始碼分析--自定義外掛(七)AI原始碼
- 滴滴外掛化方案 VirtualApk 原始碼解析APK原始碼
- Android 外掛化框架 DynamicLoadApk 原始碼分析Android框架APK原始碼
- 基於Bootstrap的jQuery使用者嚮導外掛bootjQuery
- 基於Bootstrap的Material Design風格表單外掛bootMaterial Design
- 急速 debug 實戰三(Node - webpack外掛,babel外掛,vue原始碼篇)WebBabelVue原始碼
- 淺談bootstrap表單驗證外掛BootstrapValidatorboot
- 【django學習-24】自定義外掛Django
- OctoberCMS 外掛學習 側邊欄
- Gradle外掛學習筆記(一)Gradle筆記
- Gradle外掛學習筆記(四)Gradle筆記
- Cordova學習--iOS自定義外掛iOS
- 前端學習之Bootstrap學習前端boot
- Mybatis原始碼分析(六)外掛的建立代理過程MyBatis原始碼
- 基於Bootstrap的強大jQuery表單驗證外掛bootjQuery
- 精盡MyBatis原始碼分析 - 外掛機制MyBatis原始碼
- ChatGPT 開源了第一款外掛,都來學習一下原始碼吧!ChatGPT原始碼
- webpack bootstrap原始碼解讀Webboot原始碼
- 原始碼學習原始碼
- Flutter學習指南:封裝 API 外掛Flutter封裝API
- 設定bootstrap modal模態框的寬度和寬度boot
- 格式化Java原始碼的Vscode等IDE外掛 - RedditJava原始碼VSCodeIDE