zoukankan      html  css  js  c++  java
  • Bootstrap Tooltip源码分析

    /* ========================================================================
     * Bootstrap: tooltip.js v3.3.7
     * http://getbootstrap.com/javascript/#tooltip
     * Inspired by the original jQuery.tipsy by Jason Frame
     * ========================================================================
     * Copyright 2011-2016 Twitter, Inc.
     * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
     * ======================================================================== */
    
    
    +function ($) {
      'use strict';
    
      // TOOLTIP PUBLIC CLASS DEFINITION
      // ===============================
    
      var Tooltip = function (element, options) {//tooltip 构造函数         element:当前dom元素    options:一般是false(除非option是对象)
        this.type       = null
        this.options    = null
        this.enabled    = null
        this.timeout    = null
        this.hoverState = null
        this.$element   = null
        this.inState    = null
    
        this.init('tooltip', element, options)//在构造函数中直接初始化          这里传入tooltip 可能是为了扩展
      }
    
      Tooltip.VERSION  = '3.3.7'
    
      Tooltip.TRANSITION_DURATION = 150 //过度时间
    
      Tooltip.DEFAULTS = {//默认配置
        animation: true,
        placement: 'top',
        selector: false,
        template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
        trigger: 'hover focus',
        title: '',
        delay: 0,
        html: false,
        container: false,
        viewport: {
          selector: 'body',
          padding: 0
        }
      }
    
      Tooltip.prototype.init = function (type, element, options) {//初始化
        this.enabled   = true
        this.type      = type            //tooltip      
        this.$element  = $(element)
        this.options   = this.getOptions(options)//合并参数:DEFAULTS ,data-   he options  然后返回,噢还有daly
        //function: 传入elemlent,object:传入sleector或   paddding          二元表达式是在$()里进行的   $viewport为jquery元素    
        this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
        this.inState   = { click: false, hover: false, focus: false }//默认是这样的,????往下看
    
        if (this.$element[0] instanceof document.constructor && !this.options.selector) {//不能是document的实例,即不能是document对象,那么手动出错
          throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
        }
    
        var triggers = this.options.trigger.split(' ')//把触发条件弄成数组   ['hover','foxus'] 这是默认的配置
    
        for (var i = triggers.length; i--;) {//注册事件:默认的话是注册hover focus 对应的事件  监听函数为this.enter和this.leave   ,也可以注册click:监听函数为this.toggle
          var trigger = triggers[i]
    
          if (trigger == 'click') {
            this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
          } else if (trigger != 'manual') {
            var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'
            var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
    
            this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
            this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
          }
        }
    
        this.options.selector ?
          (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
          this.fixTitle()
      }
    
      Tooltip.prototype.getDefaults = function () {
        return Tooltip.DEFAULTS
      }
    
      Tooltip.prototype.getOptions = function (options) {
        options = $.extend({}, this.getDefaults(), this.$element.data(), options)//主要是参数合并
    
        if (options.delay && typeof options.delay == 'number') {//延迟的话,把delay初始化为对象
          options.delay = {
            show: options.delay,
            hide: options.delay
          }
        }
    
        return options
      }
    
      Tooltip.prototype.getDelegateOptions = function () {
        var options  = {}
        var defaults = this.getDefaults()
    
        this._options && $.each(this._options, function (key, value) {
          if (defaults[key] != value) options[key] = value
        })
    
        return options
      }
    
      Tooltip.prototype.enter = function (obj) {//tooltip触发时候的执行方法
        var self = obj instanceof this.constructor ?
          obj : $(obj.currentTarget).data('bs.' + this.type)
    
        if (!self) {//什么时候为null先不管,先往下看
          self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
          $(obj.currentTarget).data('bs.' + this.type, self)
        }
    
        if (obj instanceof $.Event) {//obj是$.Event的实例的话执行,就是obj是不是事件对象
          self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true   //把触发的状态搞成true,(默认是false)
        }
    
        if (self.tip().hasClass('in') || self.hoverState == 'in') {//有in class 或 hoverState 为in     即已经显示了所以返回            但是无论怎么样都会初始化self.$tip
          self.hoverState = 'in'
          return
        }
    
        clearTimeout(self.timeout)//清除定时器
    
        self.hoverState = 'in'//改为in   因为已经开始让tooltip显示了
    
        if (!self.options.delay || !self.options.delay.show) return self.show()//不延迟的话直接调用show方法,否则执行下面的定时器进行延迟
    
        self.timeout = setTimeout(function () {//进行延迟
          if (self.hoverState == 'in') self.show()
        }, self.options.delay.show)
      }
    
      Tooltip.prototype.isInStateTrue = function () {
        for (var key in this.inState) {
          if (this.inState[key]) return true
        }
    
        return false
      }
    
      Tooltip.prototype.leave = function (obj) {
        var self = obj instanceof this.constructor ?
          obj : $(obj.currentTarget).data('bs.' + this.type)
    
        if (!self) {
          self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
          $(obj.currentTarget).data('bs.' + this.type, self)
        }
    
        if (obj instanceof $.Event) {
          self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
        }
    
        if (self.isInStateTrue()) return
    
        clearTimeout(self.timeout)
    
        self.hoverState = 'out'
    
        if (!self.options.delay || !self.options.delay.hide) return self.hide()
    
        self.timeout = setTimeout(function () {
          if (self.hoverState == 'out') self.hide()
        }, self.options.delay.hide)
      }
    
      Tooltip.prototype.show = function () {
        var e = $.Event('show.bs.' + this.type)
    
        if (this.hasContent() && this.enabled) {//有title数据的时候才执行显示工作
          this.$element.trigger(e)//触发show事件,但还没插入文档所以不会显示,往下看
    
          var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])//判断是否在文档中   ?不太懂,先放下
          if (e.isDefaultPrevented() || !inDom) return//默认已经取消 或者是不在dom中
          var that = this
    
          var $tip = this.tip()//得到tootle的jquery对象
    
          var tipId = this.getUID(this.type)//为tip创建一个id(内部集成了去重处理),
    
          this.setContent()//设置 为$tip里填充title数据
          $tip.attr('id', tipId)//设置id(因为都是一个对象引用,从哪里设置都一样)
          this.$element.attr('aria-describedby', tipId)//设置一个标记
    
          if (this.options.animation) $tip.addClass('fade')
    
          var placement = typeof this.options.placement == 'function' ?//提取显示方向
            this.options.placement.call(this, $tip[0], this.$element[0]) :
            this.options.placement
    
          var autoToken = /s?auto?s?/i
          var autoPlace = autoToken.test(placement)//判断方向是否是auto
          if (autoPlace) placement = placement.replace(autoToken, '') || 'top'//有auto的话为top
    
          $tip
            .detach()//从dom结构中删除,但是不冲jquery对象中删除
            .css({ top: 0, left: 0, display: 'block' })//设置基本的css属性,先让定位在左上角
            .addClass(placement)
            .data('bs.' + this.type, this)
    
          this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)//默认加在触发元素的后面,到目前,在$element后已经可以看到dom元素(按f12看)现在在左上角上,但是透明度为0(即看不到)
          this.$element.trigger('inserted.bs.' + this.type)//这是一个不确定的事件
    
          var pos          = this.getPosition()//
          var actualWidth  = $tip[0].offsetWidth//
          var actualHeight = $tip[0].offsetHeight//
    
          if (autoPlace) {//auto 的情况
            var orgPlacement = placement
            var viewportDim = this.getPosition(this.$viewport)
    
            placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top'    :
                        placement == 'top'    && pos.top    - actualHeight < viewportDim.top    ? 'bottom' :
                        placement == 'right'  && pos.right  + actualWidth  > viewportDim.width  ? 'left'   :
                        placement == 'left'   && pos.left   - actualWidth  < viewportDim.left   ? 'right'  :
                        placement
    
            $tip
              .removeClass(orgPlacement)
              .addClass(placement)
          }
    
          var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)//计算toop该在哪个位置显示
    
          this.applyPlacement(calculatedOffset, placement)//把计算后的状态应用到实际
    
          var complete = function () {
            var prevHoverState = that.hoverState
            that.$element.trigger('shown.bs.' + that.type)
            that.hoverState = null
    
            if (prevHoverState == 'out') that.leave(that)
          }
    
          $.support.transition && this.$tip.hasClass('fade') ?
            $tip
              .one('bsTransitionEnd', complete)
              .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
            complete()
        }
      }
    
      Tooltip.prototype.applyPlacement = function (offset, placement) {
        var $tip   = this.tip()
        var width  = $tip[0].offsetWidth
        var height = $tip[0].offsetHeight
    
        // manually read margins because getBoundingClientRect includes difference
        var marginTop = parseInt($tip.css('margin-top'), 10)
        var marginLeft = parseInt($tip.css('margin-left'), 10)
    
        // we must check for NaN for ie 8/9
        if (isNaN(marginTop))  marginTop  = 0
        if (isNaN(marginLeft)) marginLeft = 0
    
        offset.top  += marginTop//     加上toop自己的margin  因为位置不包过外边框
        offset.left += marginLeft
    
        // $.fn.offset doesn't round pixel values
        // so we use setOffset directly with our own function B-0  用jquery内部的方法来指定top和left,是对jquery有多熟啊,   这段还真是不好理解,回头看
        $.offset.setOffset($tip[0], $.extend({
          using: function (props) {
            $tip.css({
              top: Math.round(props.top),
              left: Math.round(props.left)
            })
          }
        }, offset), 0)
    
        $tip.addClass('in')//目前就显示了
    
        // check to see if placing tip in new offset caused the tip to resize itself
        var actualWidth  = $tip[0].offsetWidth
        var actualHeight = $tip[0].offsetHeight
    
        if (placement == 'top' && actualHeight != height) {
          offset.top = offset.top + height - actualHeight
        }
    
        var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)//得到视口调整三角,什么意思
    
        if (delta.left) offset.left += delta.left
        else offset.top += delta.top
    
        var isVertical          = /top|bottom/.test(placement)
        var arrowDelta          = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
        var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
    
        $tip.offset(offset)
        this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
      }
    
      Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
        this.arrow()
          .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
          .css(isVertical ? 'top' : 'left', '')
      }
    
      Tooltip.prototype.setContent = function () {
        var $tip  = this.tip()//得到dom节点
        var title = this.getTitle()//得到数据
    
        $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)//这里是判断是用$().html(),形式还是$().text()方式填入title数据
        $tip.removeClass('fade in top bottom left right')//移除这些可能有的class
      }
    
      Tooltip.prototype.hide = function (callback) {
        var that = this
        var $tip = $(this.$tip)
        var e    = $.Event('hide.bs.' + this.type)
    
        function complete() {
          if (that.hoverState != 'in') $tip.detach()
          if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary.
            that.$element
              .removeAttr('aria-describedby')
              .trigger('hidden.bs.' + that.type)
          }
          callback && callback()
        }
    
        this.$element.trigger(e)
    
        if (e.isDefaultPrevented()) return
    
        $tip.removeClass('in')
    
        $.support.transition && $tip.hasClass('fade') ?
          $tip
            .one('bsTransitionEnd', complete)
            .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
          complete()
    
        this.hoverState = null
    
        return this
      }
    
      Tooltip.prototype.fixTitle = function () {
        var $e = this.$element
        if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
          $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
        }
      }
    
      Tooltip.prototype.hasContent = function () {
        return this.getTitle()
      }
    
      Tooltip.prototype.getPosition = function ($element) {//得到任意元素的位置(默认为触发元素)
        $element   = $element || this.$element
    
        var el     = $element[0]
        var isBody = el.tagName == 'BODY'
    
        var elRect    = el.getBoundingClientRect()// 这个方法返回一个矩形对象,包含四个属性:left、top、right和bottom。分别表示元素各边与页面上边和左边的距离。(包括宽高)  但是right,bottom和css中的理解有点不一样
        if (elRect.width == null) {//兼容ie8,没有height和with的情况
          // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
          elRect = $.extend({}, elRect, {  elRect.right - elRect.left, height: elRect.bottom - elRect.top })
        }
        var isSvg = window.SVGElement && el instanceof window.SVGElement//是否为svg
        // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3.
        // See https://github.com/twbs/bootstrap/issues/20280
        var elOffset  = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset())//获取匹配元素在当前视口的相对偏移。
        var scroll    = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }//获取滚动条高度
        var outerDims = isBody ? {  $(window).width(), height: $(window).height() } : null//is body时要做怎么多处理
     
        return $.extend({}, elRect, scroll, outerDims, elOffset)//都掺和一下,为了保险      主要还是 el.getBoundingClientRect()    $element.offset()  
      }
    
      Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {//获取计算后的样式,即:tooptip 应该在哪个位置显示    这是一套固定算法,算出如何在其中间位置显示
        return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2 } :
               placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
               placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
            /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
    
      }
    
      Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
        var delta = { top: 0, left: 0 }
        if (!this.$viewport) return delta// $viewport 为body的jquery对象
    
        var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
        var viewportDimensions = this.getPosition(this.$viewport)//获取body的rect矩阵位置
    
        if (/right|left/.test(placement)) {
          var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll
          var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
          if (topEdgeOffset < viewportDimensions.top) { // top overflow
            delta.top = viewportDimensions.top - topEdgeOffset
          } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
            delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
          }
        } else {//top 或bootem时
          var leftEdgeOffset  = pos.left - viewportPadding
          var rightEdgeOffset = pos.left + viewportPadding + actualWidth
          if (leftEdgeOffset < viewportDimensions.left) { // left overflow
            delta.left = viewportDimensions.left - leftEdgeOffset
          } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
            delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
          }
        }
    
        return delta
      }
    
      Tooltip.prototype.getTitle = function () {
        var title
        var $e = this.$element
        var o  = this.options
    
        title = $e.attr('data-original-title')//优先提取   html属性里面的title
          || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)//也可以在配置参数时指定title的回调函数
    
        return title
      }
    
      Tooltip.prototype.getUID = function (prefix) {
        do prefix += ~~(Math.random() * 1000000)//~~    位操作符一直没研究透,汗颜。。
        while (document.getElementById(prefix))//去重处理
        return prefix
      }
    
      Tooltip.prototype.tip = function () {
        if (!this.$tip) {
          this.$tip = $(this.options.template)
          if (this.$tip.length != 1) {
            throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
          }
        }
        return this.$tip
      }
    
      Tooltip.prototype.arrow = function () {
        return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
      }
    
      Tooltip.prototype.enable = function () {
        this.enabled = true
      }
    
      Tooltip.prototype.disable = function () {
        this.enabled = false
      }
    
      Tooltip.prototype.toggleEnabled = function () {
        this.enabled = !this.enabled
      }
    
      Tooltip.prototype.toggle = function (e) {
        var self = this
        if (e) {
          self = $(e.currentTarget).data('bs.' + this.type)
          if (!self) {
            self = new this.constructor(e.currentTarget, this.getDelegateOptions())
            $(e.currentTarget).data('bs.' + this.type, self)
          }
        }
    
        if (e) {
          self.inState.click = !self.inState.click
          if (self.isInStateTrue()) self.enter(self)
          else self.leave(self)
        } else {
          self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
        }
      }
    
      Tooltip.prototype.destroy = function () {
        var that = this
        clearTimeout(this.timeout)
        this.hide(function () {
          that.$element.off('.' + that.type).removeData('bs.' + that.type)
          if (that.$tip) {
            that.$tip.detach()
          }
          that.$tip = null
          that.$arrow = null
          that.$viewport = null
          that.$element = null
        })
      }
    
    
      // TOOLTIP PLUGIN DEFINITION
      // =========================
    
      function Plugin(option) {//jquery 插件的入口方法
        return this.each(function () {//each 在这里就很有意义了,如果选择器选到n个元素,就可以挨个初始化了
          var $this   = $(this)
          var data    = $this.data('bs.tooltip')
          var options = typeof option == 'object' && option          //option 是object时才付给option否则为false
    
          if (!data && /destroy|hide/.test(option)) return   //有data时 return          或者有destroy或hide时返回
          if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))//初始化data并数据缓存,下次直接使用     new Tooltip(this, options))这里已经执行了大部分功能,可以跟过去看看
          if (typeof option == 'string') data[option]()
        })
      }
    
      var old = $.fn.tooltip
    
      $.fn.tooltip             = Plugin
      $.fn.tooltip.Constructor = Tooltip
    
    
      // TOOLTIP NO CONFLICT
      // ===================
    
      $.fn.tooltip.noConflict = function () {
        $.fn.tooltip = old
        return this
      }
    
    }(jQuery);
    
    
    //显示:校正状态为true self.inState,
  • 相关阅读:
    (转发)storm 入门原理介绍
    shell :将标准输出及标准错误输出写到指定文件
    shell循环(两个日期比较,改变某个特定日期来改变当前比较值)
    MongoDB基本操作
    (转)cenntos 安装mongodb
    通过spark sql 将 hdfs上文件导入到mongodb
    股票实战--线性回归
    Python PIL 的image类和numpy array之间的互换
    根据关键点的脸型的计算
    用反卷积(Deconvnet)可视化理解卷积神经网络还有使用tensorboard
  • 原文地址:https://www.cnblogs.com/xiaobie123/p/5997610.html
Copyright © 2011-2022 走看看