Bootstrap模态窗口源码解析


Posted in Javascript onFebruary 08, 2017

前言:
bootstrap的 js插件的源码写的非常好,也算是编写jquery插件的模范写法,本来还想大篇详细的分析一下呢,唉,没时间啊,很早之前看过的源码了,现在贴在了博客上,

300来行的代码,其中有很多jquery的高级用法,建议,从github上下载一下源码,然后把本篇的代码复制过去,然后,边运行,边阅读,如果有不明白的地方,可以给我留言,我给解答。

下面是基本每行都加了注释,供大家参考,具体内容如下

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


+function ($) {
 'use strict';

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

 var Modal = function (element, options) {//modal类:首先是Modal的构造函数,里面声明了需要用到的变量,随后又设置了一些常量。
 this.options    = options
 this.$body    = $(document.body)
 this.$element   = $(element)
 this.$dialog    = this.$element.find('.modal-dialog')
 this.$backdrop   = null
 this.isShown    = null
 this.originalBodyPad  = null
 this.scrollbarWidth  = 0
 this.ignoreBackdropClick = false//忽略遮罩成点击吗,不忽略,即:点击遮罩层退出模态

 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.7'

 Modal.TRANSITION_DURATION = 300 //transition duration 过度时间
 Modal.BACKDROP_TRANSITION_DURATION = 150  //背景过度时间

 Modal.DEFAULTS = {//defaults 默认值
 backdrop: true,//有无遮罩层
 keyboard: true,//键盘上的 esc 键被按下时关闭模态框。
 show: true//模态框初始化之后就立即显示出来。
 }
//变量设置完毕,接着就该上函数了。Modal的扩展函数有这么几个:
//toggel,show,hide,enforceFocus,escape,resize,hideModal,removeBackdrop,
//backdrop,handleUpdate,adjustDialog,resetAdjustments,checkScrollbar,setScrollbar,resetScrollbar,
//measureScrollbar终于列完了,恩一共是16个。toggel函数比较简单,是一个显示和隐藏的切换函数。代码如下
 Modal.prototype.toggle = function (_relatedTarget) {
 return this.isShown ? this.hide() : this.show(_relatedTarget)
 }

 Modal.prototype.show = function (_relatedTarget) {
 var that = this
 var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })//触发 尾行注册的show.bs.modal事件,并给relatedTarget赋值 $.Event 创建事件对象的目的就是可以给他任意赋值

 this.$element.trigger(e)//

 if (this.isShown || e.isDefaultPrevented()) return//如果已经显示就返回

 this.isShown = true//标记

 this.checkScrollbar()//核对是否有滚动条,并且测量滚动条宽度(非x轴)
 this.setScrollbar()//如果有滚动条,就给body一个padding-right 是一个滚动条的宽度,这一步的目的是为了呼应,下面的去掉y轴滚动条
 this.$body.addClass('modal-open')//接着为body元素添加modal-open类、即去掉y轴滚动条,页面就会往右一个滚动条的宽度,

 this.escape()//按esc退出模态
 this.resize()//窗口大小调整,窗口大小改变,模态框也跟着变

 this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))//注册右上角关闭事件

 this.$dialog.on('mousedown.dismiss.bs.modal', function () {//在dialog中按下鼠标,在父元素中抬起   忽略: 在模态中按下鼠标,在遮罩层中抬起鼠标
  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
  }

  that.$element
  .show()
  .scrollTop(0)//show 并且 弄到顶部

  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)//模态过度完成后,触发foucus 和shown.bs.modal
   })
   .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
  that.$element.trigger('focus').trigger(e)//否则直接进行
 })
 }

 Modal.prototype.hide = function (e) {//他的存在就是一个事件监听函数,所以可以加e 下面是模态窗口关闭处理函数
 if (e) e.preventDefault()//取消默认行为

 e = $.Event('hide.bs.modal')//无论什么事件进入这里都换成 'hide.bs.modal' 妈的这样就通用了,,,无论是点击“x”,还是取消,确定,都这么处理,

 this.$element.trigger(e)//处发hide

 if (!this.isShown || e.isDefaultPrevented()) return

 this.isShown = false//恢复初始的false

 this.escape()//移除esc事件
 this.resize()//移除为window绑定的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))//到了这里,虽然模态已经没有了,但仅仅是把透明度改为0了,this.hideModal才是真正移除
  .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
  this.hideModal()
 }

 Modal.prototype.enforceFocus = function () {//模态框获得焦点
 $(document)
  .off('focusin.bs.modal') // guard against infinite focus loop
  .on('focusin.bs.modal', $.proxy(function (e) {
  if (document !== e.target &&
   this.$element[0] !== e.target &&
   !this.$element.has(e.target).length) {
   this.$element.trigger('focus')
  }
  }, this))
 }

 Modal.prototype.escape = function () {//键盘上的 esc 键被按下时关闭模态框。
 if (this.isShown && this.options.keyboard) {//仅在模态窗显示的时候才注册这个事件
  this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {//不仅可以把事件穿过来,。。。牛
  e.which == 27 && this.hide()//27 时调用hide方法(ps:比if省事多了,高手就是高手)
  }, this))
 } else if (!this.isShown) {//否则移除他,感觉怪怪的,不管了
  this.$element.off('keydown.dismiss.bs.modal')
 }
 }

 Modal.prototype.resize = function () {//为你window resize注册事件
 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 = function (callback) {
 var that = this
 var animate = this.$element.hasClass('fade') ? 'fade' : ''//是否有fade动画类

 if (this.isShown && this.options.backdrop) {//条件:正在show,并且有遮罩层
  var doAnimate = $.support.transition && animate// do的条件是支持css3过度和有fade class

  this.$backdrop = $(document.createElement('div'))//创建遮罩层div
  .addClass('modal-backdrop ' + animate)//添加 modal-backdrop 和fade class
  .appendTo(this.$body)//加到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')//添加in 0.5的透明度

  if (!callback) return

  doAnimate ?
  this.$backdrop
   .one('bsTransitionEnd', callback)
   .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) ://如果有fade动画的话 就给遮罩层绑定一个遮罩过度时间,为什么要这么写以后聊
  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()
 }
 }

 // 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) { // workaround for missing window.innerWidth in IE8
  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 () {//用来为body元素设置padding-right的值,防止body的元素被scrollbar阻挡
 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)
 }

 Modal.prototype.resetScrollbar = function () {
 this.$body.css('padding-right', this.originalBodyPad)
 }

 Modal.prototype.measureScrollbar = function () { // thx walsh 测量 Scrollbar
 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
 // =======================

 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)))//把modal对象存起来,避免第二次打开时在new对象,这点值得学习
  if (typeof option == 'string') data[option](_relatedTarget)/*这里是区分option是对象和字符串的情况*/
  else if (options.show) data.show(_relatedTarget)
 })
 }

 var old = $.fn.modal

 $.fn.modal    = Plugin
 $.fn.modal.Constructor = Modal


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

 $.fn.modal.noConflict = function () {
 $.fn.modal = old
 return this
 }


 // MODAL DATA-API 这里是不用一行js代码就实现modal的关键
 // ==============

 $(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())//这地方是区别一下第一次触发和第二次触发
  //到这里为止是为了得到被控制的modal的dom元素
 if ($this.is('a')) e.preventDefault()

 $target.one('show.bs.modal', function (showEvent) {//调用show方法后立即执行的事件
  if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
  $target.one('hidden.bs.modal', function () {//调用show后创建的事件,模态框隐藏后触发,
  $this.is(':visible') && $this.trigger('focus')//如果原来的按钮还存在的(或显示的)话,那就让他得到焦点
  })
 })
 Plugin.call($target, option, this)
 })

}(jQuery);

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
onkeydown事件解决按回车键直接提交数据的需求
Apr 11 Javascript
JS判断变量是否为空判断是否null
Jul 25 Javascript
如何编写高质量JS代码
Dec 28 Javascript
JavaScript实现下拉列表框数据增加、删除、上下排序的方法
Aug 11 Javascript
基于jQuery封装的分页组件
Jun 26 jQuery
vue-自定义组件传值的实例讲解
Sep 18 Javascript
mocha的时序规则讲解
Feb 16 Javascript
layer父页获取弹出层输入框里面的值方法
Sep 02 Javascript
Vue实现星级评价效果实例详解
Dec 30 Javascript
小程序自定义模板实现吸顶功能
Jan 08 Javascript
Vue的Options用法说明
Aug 14 Javascript
JavaScript 实现页面滚动动画
Apr 24 Javascript
Bootstrap路径导航与分页学习使用
Feb 08 #Javascript
Bootstrap缩略图与警告框学习使用
Feb 08 #Javascript
Bootstrap导航条学习使用(二)
Feb 08 #Javascript
jquery对象与DOM对象转化
Feb 08 #Javascript
vue2 如何实现div contenteditable=“true”(类似于v-model)的效果
Feb 08 #Javascript
jQuery中Datatables增加跳转到指定页功能
Feb 08 #Javascript
node.js的事件机制
Feb 08 #Javascript
You might like
Codeigniter中禁止A Database Error Occurred错误提示的方法
2014/06/12 PHP
PHP数组和explode函数示例总结
2015/05/08 PHP
php+ajax+json 详解及实例代码
2016/12/12 PHP
解决php扩展安装不生效问题
2019/10/25 PHP
PHP的重载使用魔术方法代码实例详解
2021/02/26 PHP
js处理自己不能定义二维数组的方法详解
2014/03/03 Javascript
一段非常简单的js判断浏览器的内核
2014/08/17 Javascript
Zero Clipboard实现浏览器复制到剪贴板的方法(多个复制按钮)
2016/03/24 Javascript
在JavaScript中对HTML进行反转义详解
2016/05/18 Javascript
JS产生随机数的几个用法详解
2016/06/22 Javascript
AngularJS使用自定义指令替代ng-repeat的方法
2016/09/17 Javascript
jquery  实现轮播图详解及实例代码
2016/10/12 Javascript
Bootstrap中glyphicons-halflings-regular.woff字体报404错notfound的解决方法
2017/01/19 Javascript
jQuery控制元素隐藏和显示
2017/03/03 Javascript
详解vue父子模版嵌套案例
2017/03/04 Javascript
微信小程序 页面传值详解
2017/03/10 Javascript
详解利用jsx写vue组件的方法示例
2017/07/17 Javascript
JS实现快速比较两个字符串中包含有相同数字的方法
2017/09/11 Javascript
js+html5生成自动排列对话框实例
2017/10/09 Javascript
vue router仿天猫底部导航栏功能
2017/10/18 Javascript
vue项目实现github在线预览功能
2018/06/20 Javascript
Node.js + express实现上传大文件的方法分析【图片、文本文件】
2019/03/14 Javascript
Vue Cli 3项目使用融云IM实现聊天功能的方法
2019/04/19 Javascript
python读写二进制文件的方法
2015/05/09 Python
对Python中gensim库word2vec的使用详解
2018/05/08 Python
用Python分析3天破10亿的《我不是药神》到底神在哪?
2018/07/12 Python
Python数据分析matplotlib设置多个子图的间距方法
2018/08/03 Python
在OpenCV里使用特征匹配和单映射变换的代码详解
2019/10/23 Python
Python基于template实现字符串替换
2020/11/27 Python
基于Python实现天天酷跑功能
2021/01/06 Python
欧洲最大的婴幼儿服装及内衣公司:Petit Bateau(小帆船)
2016/08/16 全球购物
美国家居装饰网上商店:Lulu & Georgia
2019/09/14 全球购物
北京捷通华声语音技术有限公司Java软件工程师笔试题
2012/04/10 面试题
幼师求职自荐信范文
2014/01/26 职场文书
2014年党员自我评议对照检查材料
2014/09/20 职场文书
动画《朋友游戏》公开佐藤友生绘制的开播纪念绘
2022/04/06 日漫