您的位置:首页 > Web前端 > BootStrap

Bootstrap的Modal源码学习

2015-12-02 12:13 686 查看
新的项目中使用了Bootstrap,在开发中要对Modal进行重构,所以对其源码进行分析~~

/* ========================================================================
* Bootstrap: modal.js v3.3.5
* http://getbootstrap.com/javascript/#modals * ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */

+function ($) {
'use strict';

// MODAL CLASS DEFINITION
// ======================

var Modal = function(element, options){
this.options = options;
this.$body = $(document.body);
this.$element = $(element);
this.$dialog = this.$element.find('.modal-dialog');
//背景遮罩层
this.$backdrop = null;
//弹出框的显示状态(true为显示,false为隐藏)
this.isShown = null;
//最原始的右边距大小(未加滚动条之前)
this.originalBodyPad = null;
//滚动条的宽度
this.scrollbarWidth = 0;
//是否忽略背景遮罩的单击事件
this.ignoreBackdropClick = false;
//如果是远程加载,则加载之,并派发loaded.bs.modal事件
if(this.options.remote){
this.$element.find('.modal-content').load(this.options.remote, $.proxy(function(){
this.$element.trigger('loaded.bs.modal');
}, this));
}
}

Modal.VERSION = '3.3.5'

Modal.TRANSITION_DURATION = 300
Modal.BACKDROP_TRANSITION_DURATION = 150

Modal.DEFAULTS = {
//背景遮罩层(单击会出发hide方法)
backdrop: true,
//ESC键的支持
keyboard: true,
//状态框初始化之后就立即显示
show: true
}

//如果是显示状态则隐藏,反之则显示
Modal.prototype.toggle = function(_relatedTarget){
return this.isShown ? this.hide() : this.show(_relatedTarget)
}

//显示弹出框
Modal.prototype.show = function(_relatedTarget){
var that = this

//创建show.bs.modal事件,并触发之
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget });
this.$element.trigger(e);

//如果当前弹出框已经显示,或者说先前的事件中已经调用了preventDefault()方法,则直接返回
if(this.isShown || e.isDefaultPrevented()){
return ;
}

//改变显示状态为显示中
this.isShown = true;

//检查滚动条,如果存在滚动条则设置滚动条的宽度
this.checkScrollbar();
this.setScrollbar();
//overflow: hidden;
this.$body.addClass('modal-open');

//设置ESC键和浏览器缩放的处理函数
this.escape()
this.resize()

//给包含data-dismiss="modal"属性的元素注册click.dismiss.bs.modal事件处理函数(隐藏当前弹出框)
this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this));

//注册mousedown.dismiss.bs.modal事件的监听
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(){
//浏览器是否支持动画,并且当前结点中存在fade的Class值
var transition = $.support.transition && that.$element.hasClass('fade')
//如果没有父元素(例如:还未append的$('<div><div>')),则将其添加到body上
if(!that.$element.parent().length){
that.$element.appendTo(that.$body);
}

//将当前的弹出框显示,并将其移动到最上面
that.$element.show().scrollTop(0);

//调整弹出框的样式
that.adjustDialog();

//准备动画效果
if(transition){
that.$element[0].offsetWidth;
}

//设置进入时的Class
that.$element.addClass('in');

//重新给document绑定focusin.bs.modal事件
that.enforceFocus();

var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget });

transition ?
//如果在Modal.TRANSITION_DURATION规定的时间内,当前节点没有监听到bsTransitionEnd,则强制执行该事件的回掉函数
//emulateTransitionEnd(transition-duration):在过渡持续的时间(transition-duration)过后如果transitionend事件没有发生则强制在该元素上触发这个事件
that.$dialog.one('bsTransitionEnd', function(){
that.$element.trigger('focus').trigger(e);
}).emulateTransitionEnd(Modal.TRANSITION_DURATION) :
//触发focus和shown.bs.modal事件
that.$element.trigger('focus').trigger(e)
})
}

//隐藏弹出框
Modal.prototype.hide = function(e){
if(e){
e.preventDefault();
}

//触发hide.bs.modal事件
e = $.Event('hide.bs.modal');
this.$element.trigger(e);

//如果当前弹出框已经隐藏,或者说先前的事件中已经调用了preventDefault()方法,则直接返回
if(!this.isShown || e.isDefaultPrevented()){
return ;
}

//改变显示状态为隐藏
this.isShown = false;

//设置ESC键和浏览器缩放的处理函数
this.escape();
this.resize();

//取消在document节点上注册的focusin.bs.modal事件
$(document).off('focusin.bs.modal');

//移除in样式,并且移除click.dismiss.bs.modal和mouseup.dismiss.bs.modal事件
this.$element.removeClass('in').off('click.dismiss.bs.modal').off('mouseup.dismiss.bs.modal');

//移除mousedown.dismiss.bs.modal事件
this.$dialog.off('mousedown.dismiss.bs.modal');

//
$.support.transition && this.$element.hasClass('fade') ?
//如果在Modal.TRANSITION_DURATION,当前节点没有监听到bsTransitionEnd,则强制执行该事件的回掉函数
//emulateTransitionEnd(transition-duration):在过渡持续的时间(transition-duration)过后如果transitionend事件没有发生则强制在该元素上触发这个事件
this.$element.one('bsTransitionEnd', $.proxy(this.hideModal, this)).emulateTransitionEnd(Modal.TRANSITION_DURATION) :
this.hideModal();
}

//重新为document对象绑定focusin.bs.modal事件处理函数
Modal.prototype.enforceFocus = function(){
$(document).off('focusin.bs.modal').on('focusin.bs.modal', $.proxy(function(e){
if(this.$element[0] !== e.target && !this.$element.has(e.target).length){
//派发focus事件
this.$element.trigger('focus');
}
}, this));
}

//当按下ESC键时,隐藏该弹出框
Modal.prototype.escape = function(){
//如果当前弹出框处于显示状态并且keyboard参数为true时执行
if(this.isShown && this.options.keyboard){
//给当前的弹出框添加keydown.dismiss.bs.modal事件代理
this.$element.on('keydown.dismiss.bs.modal', $.proxy(function(e){
//如果按下的是ESC键则隐藏当前弹出框
e.which == 27 && this.hide();
}, this));
}else if(!this.isShown){ //如果当前弹出框处于隐藏状态,则移除keydown.dismiss.bs.modal事件
this.$element.off('keydown.dismiss.bs.modal');
}
}

//根据当前弹出框的显示(隐藏)状态给window对象添加或移除resize.bs.modal事件
Modal.prototype.resize = function(){
if(this.isShown){
$(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this));
}else{
$(window).off('resize.bs.modal');
}
}

//隐藏
Modal.prototype.hideModal = function(){
var that = this;
this.$element.hide();
//隐藏背景的逻辑
this.backdrop(function(){
that.$body.removeClass('modal-open');
that.resetAdjustments();
that.resetScrollbar();
that.$element.trigger('hidden.bs.modal');
})
}

//移除背景遮罩层
Modal.prototype.removeBackdrop = function(){
this.$backdrop && this.$backdrop.remove();
this.$backdrop = null;
}

//遮罩层的处理
Modal.prototype.backdrop =
4000
function(callback){
var that = this;
//fade动画
var animate = this.$element.hasClass('fade') ? 'fade' : '';

//如果当前弹出框处于显示状态并且backdrop参数为true时执行
if(this.isShown && this.options.backdrop){
//浏览器是否支持动画,并且包含fade动画
var doAnimate = $.support.transition && animate;

//添加一个div的背景遮罩
this.$backdrop = $(document.createElement('div')).addClass('modal-backdrop ' + animate).appendTo(this.$body);

//事件click.dismiss.bs.modal的处理函数
this.$element.on('click.dismiss.bs.modal', $.proxy(function(e){
//如果需要忽略背景层的单击事件则直接返回
if(this.ignoreBackdropClick){
this.ignoreBackdropClick = false;
return ;
}
//冒泡来的事件此处直接返回
if(e.target !== e.currentTarget){
return ;
}
//如果backdrop配置参数为static,则获取焦点,否则隐藏
this.options.backdrop == 'static' ? this.$element[0].focus() : this.hide();
}, this));

//准备动画效果
if(doAnimate){
this.$backdrop[0].offsetWidth;
}

this.$backdrop.addClass('in');

//如果不存在回调函数则直接返回
if(!callback){
return ;
}

//如果在Modal.BACKDROP_TRANSITION_DURATION,当前节点没有监听到bsTransitionEnd,则强制执行该事件的回掉函数
//emulateTransitionEnd(transition-duration):在过渡持续的时间(transition-duration)过后如果transitionend事件没有发生则强制在该元素上触发这个事件
doAnimate ? this.$backdrop.one('bsTransitionEnd', callback).emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : callback();

}else if(!this.isShown && this.$backdrop){ //处于非显示状态,且存在背景遮罩
//移除in样式
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();
}
}

// these following methods are used to handle overflowing modals
//调整弹出框的样式
Modal.prototype.handleUpdate = function(){
this.adjustDialog();
}

//调整弹出框的样式
Modal.prototype.adjustDialog = function(){
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight;

this.$element.css({
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
});
}

//重置弹出框的样式
Modal.prototype.resetAdjustments = function(){
this.$element.css({
paddingLeft: '',
paddingRight: ''
});
}

//检查是否存在滚动条,并计算设置滚动条的宽度
Modal.prototype.checkScrollbar = function () {
var fullWindowWidth = window.innerWidth;
if(!fullWindowWidth){ //该死的IE8中丢失了innerWidth这个属性
var documentElementRect = document.documentElement.getBoundingClientRect();
fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left);
}
this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth;
this.scrollbarWidth = this.measureScrollbar();
}

//设置右边距
Modal.prototype.setScrollbar = function(){
//获取padding-right的css值,其实下面的这句个人觉得可这么写parseInt(this.$body.css('padding-right') || 0)
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10);
//保留下body最原始的右边距并且重新设置右边距
this.originalBodyPad = document.body.style.paddingRight || '';
if(this.bodyIsOverflowing){
this.$body.css('padding-right', bodyPad + this.scrollbarWidth);
}
}

//将body的右边距复位到原始值
Modal.prototype.resetScrollbar = function(){
this.$body.css('padding-right', this.originalBodyPad);
}

//计算最合适的滚动条宽度
Modal.prototype.measureScrollbar = function(){
//下面的逻辑就是先尝试添加一个有modal-scrollbar-measure样式的div,然后计算滚动条宽度,计算完后再移除此div
var scrollDiv = document.createElement('div');
scrollDiv.className = 'modal-scrollbar-measure';
this.$body.append(scrollDiv);
var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
this.$body[0].removeChild(scrollDiv);
return scrollbarWidth;
}

// MODAL PLUGIN DEFINITION
// =======================
//对Modal的包装,支持批量
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){ //如果缓存不存在则创建之(也就是再创建一个Modal对象)
$this.data('bs.modal', (data = new Modal(this, options)));
}
//如果参数为字符串(一般情况下应该是:'toggle','show','hide')
if(typeof option == 'string'){
data[option](_relatedTarget);
}else if(options.show){ //如果初始化参数中需要显示,则显示之
data.show(_relatedTarget);
}
})
}

var old = $.fn.modal

//定义jQuery的插件以及插件的构造函数
$.fn.modal = Plugin
$.fn.modal.Constructor = Modal

// MODAL NO CONFLICT
// =================

//插件的别名支持
$.fn.modal.noConflict = function(){
$.fn.modal = old;
return this;
}

// MODAL DATA-API
// ==============

//为页面中的data-toggle="modal"属性添加click.bs.modal.data-api事件监听
$(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()){ // only register focus restorer if modal will actually get shown
return ;
}

$target.one('hidden.bs.modal', function(){
$this.is(':visible') && $this.trigger('focus');
});
})
Plugin.call($target, option, this);
})

}(jQuery);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  bootstrap Modal