Bootstrap.js (1)-- modal

来源:互联网 发布:美国 二战 知乎 编辑:程序博客网 时间:2024/05/16 09:08

使用

<button type="button" class="btn" data-toggle="modal" data-target="#myModal">    触发</button>  <div class="modal" id="myModal">    <div class="modal-content">      ...  </div>  </div>  

Tips

if (typeof jQuery === 'undefined') {// 判断是否引入jq  throw new Error('Bootstrap\'s JavaScript requires jQuery')}/*  ‘+function’ ‘!function’ ‘-function’ 等价于(function(){})(), 自执行函数。把函数声明式变成了函数表达式 */+function ($) {  // 判断jq版本号  'use strict';  var version = $.fn.jquery.split(' ')[0].split('.')  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) {    throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4')  }}(jQuery);

原理 参考引用

  • 页面完成加载后,为所有含有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
    • 在过渡效果的完成后用$el.hide()隐藏模态框
    • 调用dropback函数,变量shown为false,所以移除遮罩层
    • 在出给dropback的回调里(即完成遮罩层的隐藏后),清除”modal-open”类

我先看的后面的参数设置,以及获得并处理参数

// MODAL PLUGIN DEFINITION  // =======================  // 有一些看不懂,猜测主要是对设置的参数进行处理  function Plugin(option, _relatedTarget) {    return this.each(function () {      var $this   = $(this)      var data    = $this.data('bs.modal')      // 合并配置参数$.extend,用自定义参数合并Modal.DEFAULTS      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)      // 如果没有参数则创建新的modal对象,使用默认模态框      if (!data) $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  $.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事件  // - on(events,[selector],[data],fn) ,(绑定的事件,过滤触发事件的元素,事件触发时传入的值,回调),这里就是为拥有'[data-toggle="modal"]'的元素绑定'click.bs.modal.data-api'事件  $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {     /*    - href||$target : data-target="#foo"或者 href="#foo" 属性,用于指向被控制的模态框。    - href.replace(/.*(?=#[^\s]+$)/, ''),匹配#开始,非空白字符结尾之前的位置,用' '替换   */    var $this   = $(this)    var href    = $this.attr('href')    // $target是需要打开的modal层的id值    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7     // option是指一些属性,比如是否开启鼠标控制等    var option  = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())    /*    - $target.one('show.bs.modal'),绑定一次性的函数    - showEvent.isDefaultPrevented()事件对象中是否调用过 event.preventDefault() 方法来返回一个布尔值。     */    if ($this.is('a')) e.preventDefault() // 阻止a标签的默认跳转    $target.one('show.bs.modal', function (showEvent) {      // 这个可能为了防止多次点击      if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown      // 为modal层绑定一次性关闭事件,如果当前modal层可见,那就触发modal层的focus事件      $target.one('hidden.bs.modal', function () {        $this.is(':visible') && $this.trigger('focus')      })    })    // 需要显示的modal层对象,相关设置,以及当前点击的按钮对象    Plugin.call($target, option, this)  })}(jQuery);

Modal构造函数

this.options             = options    this.$body               = $(document.body)     this.$element            = $(element) // modal层对象    this.$dialog             = this.$element.find('.modal-dialog') // modal层对象子元素中的.modal-dialog,表示弹出的内容框    this.$backdrop           = null // 静态的背景,当用户点击模态框外部时不会关闭模态框。    this.isShown             = null // 是否展示状态    this.originalBodyPad     = null // body原本的paddingRight,用于重置    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))    }  }  Modal.VERSION  = '3.3.7'  // 动画持续时间,但是具体是什么时候的动画,我不是很清楚  Modal.TRANSITION_DURATION = 300  Modal.BACKDROP_TRANSITION_DURATION = 150  Modal.DEFAULTS = { // 默认属性    backdrop: true, // 开启黑色遮罩,可以点击黑色部分关闭模态框    keyboard: true, // 可以通过esc控制模态框的退出    show: true // 初始化时显示模态框,false为需要点击按钮触发才显示  }  // toggle,如果是展示状态则关闭,如果是关闭状态则开启  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事件    this.$element.trigger(e) // 触发show.bs.modal事件,模态框打开    if (this.isShown || e.isDefaultPrevented()) return // 已经显示了就不处理,防止多次点击    this.isShown = true // 更改显示状态为显示    this.checkScrollbar() // 检查是否有滚动条出现,并且用插入div的方式模拟出如果有滚动条,那么滚动条的宽度是多少    this.setScrollbar() // 如果有滚动条出现,则设置body的padding-right为原本的值+滚动条的值    this.$body.addClass('modal-open') // 隐藏body的滚动条,模态框设置overflow:auto    this.escape() // 绑定esc退出事件    this.resize() // 判断modal层是否overflow,需要改变padding值    // data-dismiss="modal"给按钮添加,则可以关闭modal    // 给data-dismiss="modal"属性的按钮绑定hide方法,关闭modal    this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))    // modal层内容中鼠标按下的时候,绑定一次性的鼠标松开事件,ignoreBackdropClick = true,即表示正在执行动画    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       })    })    // modal层的展示逻辑,以及transition效果实现    this.backdrop(function () {      // 在完成遮罩层的展示后,调用如下方法调用遮罩层中的内容曾的展示      // $.support.transition 浏览器支持transition特效,并且modal层具有fade类      var transition = $.support.transition && that.$element.hasClass('fade')      // 务必将模态框的 HTML 代码放在文档的最高层级内(也就是说,尽量作为 body 标签的直接子元素),以避免其他组件影响模态框的展现和/或功能。(官网)      if (!that.$element.parent().length) { // 如果不在最外层,也要把它放到最外层去        that.$element.appendTo(that.$body) // don't move modals dom position      }      that.$element        .show()        .scrollTop(0) // jq方法,模态框出现以及设置相对滚动条顶部的偏移为0      that.adjustDialog() // 这里判断modal有overflow,上面明明判断了啊...不造了      if (transition) {        that.$element[0].offsetWidth // force reflow       }      that.$element.addClass('in') // 添加in类,filter: alpha(opacity=50);opacity: .5; 产生动画效果      that.enforceFocus() // enforceFocus就是强制获取焦点的意思,我的理解是内容框内有input输入框,那么给他获取焦点      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })      // 动画存在,就完成后续的focus 调用transition插件的动画特效,如果不存在,就直接调用focus      transition ?        that.$dialog // wait for modal to slide in          // 给模态框内容框绑定一次性,transition动画结束事件          .one('bsTransitionEnd', function () {            that.$element.trigger('focus').trigger(e)          })          // .emulateTransitionEnd(Modal.TRANSITION_DURATION) 是transition插件里面的方法          .emulateTransitionEnd(Modal.TRANSITION_DURATION) :        that.$element.trigger('focus').trigger(e)    })  }  Modal.prototype.hide = function (e) {    if (e) e.preventDefault()    e = $.Event('hide.bs.modal') // 注册hide.bs.modal事件    this.$element.trigger(e) // 激活事件    if (!this.isShown || e.isDefaultPrevented()) return // 如果modal没有展示,则不处理    this.isShown = false // 关闭modal,设置isShown为false    // 取消在show的时候的事件绑定    this.escape()     this.resize()     $(document).off('focusin.bs.modal') // 取消'focusin.bs.modal'事件绑定,应该是不再聚焦modal    // 移除in类,取消.off('click.dismiss.bs.modal').off('mouseup.dismiss.bs.modal')绑定    this.$element      .removeClass('in')       .off('click.dismiss.bs.modal')      .off('mouseup.dismiss.bs.modal')    this.$dialog.off('mousedown.dismiss.bs.modal')    // 如果支持transition属性,并且modal层有fade类执行动画隐藏效果,否则直接隐藏    $.support.transition && this.$element.hasClass('fade') ?      this.$element        .one('bsTransitionEnd', $.proxy(this.hideModal, this))        .emulateTransitionEnd(Modal.TRANSITION_DURATION) :      this.hideModal()  }  // 强制对内容框聚焦,比如内容框内有input输入框,则聚焦(猜测)  Modal.prototype.enforceFocus = function () {    $(document)      // 解除了当前文档的focusin事件,原因作者也进行了解释,防止无线聚焦循环      .off('focusin.bs.modal') // guard against infinite focus loop      .on('focusin.bs.modal', $.proxy(function (e) {        // 使用proxy,改变this,否则指向document        if (document !== e.target &&            this.$element[0] !== e.target &&            !this.$element.has(e.target).length) {          this.$element.trigger('focus')        }      }, this))  }  // esc键盘事件  Modal.prototype.escape = function () {    if (this.isShown && this.options.keyboard) {// modal层已经展示,并且设置了keyboard参数      // 绑定keydown.dismiss.bs.modal事件,      // e.which表示按住的是哪一个按键      // 如果按了esc键,那么调用this.hide() 方法      this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {        e.which == 27 && this.hide()      }, this))    } else if (!this.isShown) {      this.$element.off('keydown.dismiss.bs.modal')    }  }  // 判断modal层是否overflow  Modal.prototype.resize = function () {    if (this.isShown) { //  如果modal展示,那就触发resize.bs.modal事件,并且判断modal层是否overflow,改变padding值      $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))    } else { // 如果没有展示那么取消resize.bs.modal绑定      $(window).off('resize.bs.modal')    }  }  // 隐藏模态框  Modal.prototype.hideModal = function () {    var that = this    this.$element.hide()    this.backdrop(function () { // backdrop回调      // 完成遮罩层的隐藏之后,调用如下的方法,重置      that.$body.removeClass('modal-open')      // 重置body,modal层的padding值      that.resetAdjustments()      that.resetScrollbar()      that.$element.trigger('hidden.bs.modal')    })  }  // 移除黑色遮罩层  Modal.prototype.removeBackdrop = function () {    this.$backdrop && this.$backdrop.remove()    this.$backdrop = null  }  // 展示 隐藏modal回调函数 包含了动画的逻辑效果  Modal.prototype.backdrop = function (callback) {    var that = this    var animate = this.$element.hasClass('fade') ? 'fade' : ''    // 当前弹出框显示,并且有黑色遮罩层的时候    // 此事是需要弹出内容层    if (this.isShown && this.options.backdrop) {      // 支持transition 和animate动画效果      var doAnimate = $.support.transition && animate      // 插入有mmodal-backdrop-animate类的div,我猜测应该是黑的遮罩层      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) { // 不需要点击背景modal消失,直接返回          this.ignoreBackdropClick = false // 标志动画结束          return        }        // e.target !== e.currentTarget        // target返回的是触发事件的元素,currentTarget返回的是绑定事件的元素,比如事件绑定在ul上,点击ul的li标签,target返回li,二current返回ul        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      // 添加in类 .fade.in { opacity: 1;}      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') // opacity = 0      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层是否有滚动条,设置modal层的padding值  Modal.prototype.adjustDialog = function () {    var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight // modal层是否有滚动区     this.$element.css({      paddingLeft:  !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',      paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''    })  }  // 重置modal层的padding值  Modal.prototype.resetAdjustments = function () {    this.$element.css({      paddingLeft: '',      paddingRight: ''    })  }  Modal.prototype.checkScrollbar = function () {    var fullWindowWidth = window.innerWidth // 窗口大小,包含滚动条的整个窗口 ie8以下不兼容     if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8      // documentElement 属性可返回文档的根节点      // getBoundingClientRect()返回一个元素到页面上下左右的边距      // 用右边的距离-左边的距离虽然我理解意思,可是不是很理解为什么这个值是这个值      var documentElementRect = document.documentElement.getBoundingClientRect()       fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)    }    // clientWidth不包括滚动条,如果可视区的大小 < 整个窗口,说明出现了滚动条    // scrollbarWidth获得滚动条的宽度    this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth    this.scrollbarWidth = this.measureScrollbar()   }  // 修改body的右内边距为原本的值,加上滚动条的值  Modal.prototype.setScrollbar = function () {    var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)    this.originalBodyPad = document.body.style.paddingRight || ''    // 如果有滚动条,那么设置body的padding-right的值为原来的值加上滚动条的值    if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)  }  Modal.prototype.resetScrollbar = function () {    this.$body.css('padding-right', this.originalBodyPad) // 重置body的paddingRight的值  }  // 测出滚动条的宽度  Modal.prototype.measureScrollbar = function () { // thx walsh    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  }

总结

  1. 总的来说,我觉得bootstrap属于一个事件驱动的框架,比如e = $.Event(‘hide.bs.modal’)注册一个事件,然后给很多情况下都绑定这个事件,比如点击按钮就绑定这个事件,就像js原生里的自定义函数,
    我觉得这样可以让代码很有条理性吧,同样是点击事件,但是就区分了不同情况下的点击事件

  2. 代码很复杂,但是他暴露的接口或者说使用方式很简单,不需要用户写任何js基本就可以实现效果,这里也就用到了用户控制类,我们控制逻辑实现

  3. 这个组件,还有一个优点就是没有特别大面积的使用css的属性,就是没有直接操作css,基本都是通过类,还有纯js逻辑来控制,做到了样式和逻辑的分离这个很棒

  4. 缺点,就是我觉得似乎写的有点复杂了,当然了,目前,或者一年之内,感觉自己是想复杂都没有办法。哈哈哈哈哈哈 其实这个代码读一遍基本是无法理解到他的还有90&的精髓,只能大概读懂意思,对他的构架其实还是不是很清楚,但是我能感觉到,他写的有点好。哈哈哈

0 0
原创粉丝点击