Bootstrap的Modal源码学习

新的项目中使用了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 = 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);


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值