bootstrap tooltip.js解析

+(function($){
    var Tooltip = function(element,options){
        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.VERSION  = '3.3.7'
    Tooltip.DEFAULTS = {
        animation: true,        //是否动画显示
        placement: "top",   //默认tip在上方
        selector: false,
        template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',   //tip插入代码
        trigger: "hover focus", //tip触发条件 click| hover | focus | manual
        title: "",      //如果未指定 title 属性,则 title 选项是默认的 title 值
        delay: 0,   //延迟显示时间
        html: false,        //tip里面的内容是否解析为html代码 否则按一般字符串进行解析
        container: false,
        viewport: {
            selector: 'body',
            padding: 0
        }
    }

    Tooltip.prototype.init = function(type,element,options){
        this.enabled = true;
        this.type = type;
        this.$element = $(element);
        this.options = this.getOptions(options);
        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){
            return new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
        }
        var triggers = this.options.trigger.split(" ");
        for (var i = 0; i < triggers.length; i++) {
            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.toggleEnabled = function () {
        this.enabled = !this.enabled
    }
    Tooltip.prototype.toggle = function(){
        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.enter = 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 == 'focusin' ? 'focus' : 'hover'] = true;
        }
        if (self.tip().hasClass('in') || self.hoverState == 'in') {
            self.hoverState = 'in';
            return
        }
        clearTimeout(self.timeout);
        self.hoverState = 'in';
        if (!self.options.delay || !self.options.delay.show) return self.show();
        self.timeout = setTimeout(function () {
            if (self.hoverState == 'in') self.show();
        }, self.options.delay.show)
    }
    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.isInStateTrue = function(){
        for (key in this.inState) {
            if(this.inState[key]) return true;
        }
        return false;
    }
    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();
            that.$element && 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.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.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"){
            options.delay = {
                show: options.delay,
                hide: options.delay
            }
        }
        return options;
    }
    Tooltip.prototype.show = function(){
        var e = $.Event("show.bs."+this.type);
        if(this.hasContent() && this.enabled){
            this.$element.trigger(e);
            // this.$element是否包含在根节点中
            var inDom = $.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);
            if(e.isDefaultPrevented() || !inDom) return;
            var that = this;
            var $tip = this.tip(); //设置tooltip dom
            var tipId = this.getUID(this.type); //随机生成tipId且做去重处理 例: tooltip992065
            this.setContent();  //往tooltip中填入title数据 html()或者text()方式填入
            $tip.attr("id",tipId); //每个tooltip设置唯一的id
            this.$element.attr("aria-describedby",tipId);
            if(this.options.animation) $tip.addClass("fade");      //是否支持动画显示
            var placement = typeof this.options.placement == "function" ? //默认显示方向 "top" "left" "right" "bottom"
                               this.options.placement.call(this,$tip[0],this.$element[0]) : 
                               this.options.placement;
            var autoToken = /\s?auto?\s?/i; //匹配placement是否含有 auto
            var autoPlace = autoToken.test(placement);
            if(autoPlace) placement = placement.replace(autoToken,"") || "top"; //有auto的话为top
            //detach() 从dom结构中删除,但是不删除其事件
            $tip.detach().css({ top: 0, left: 0, display: 'block' }).addClass(placement).data('bs.' + this.type, this);

            //如果指定插入tooltip的容器则插入容器中 反之 则放在$element后面 (元素已经插入 只是透明度为0)
            this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element);
            this.$element.trigger("inserted.bs."+this.type);

            var pos = this.getPosition();   //获取element的坐标 width height top left ...
            var actualWidth = $tip[0].offsetWidth; //128
            var actualHeight = $tip[0].offsetHeight;   //33

            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' : //top
                               placement == 'right'  && pos.right  + actualWidth  > viewportDim.width  ? 'left'   : //top
                               placement == 'left'   && pos.left   - actualWidth  < viewportDim.left   ? 'right'  :
                               placement
                $tip.removeClass(orgPlacement).addClass(placement);
            }
            var calculatedOffset = this.getCalculatedOffset(placement,pos,actualWidth,actualHeight);
            //console.log(calculatedOffset)   // { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 }
            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") ? 
                this.$tip.one("bsTransitionEnd",complete).emulateTransitionEnd(150) : complete();
        }
    }
    Tooltip.prototype.applyPlacement = function(offset, placement){
        // console.log(offset);
        var $tip = this.tip();
        var width = $tip[0].offsetWidth;       //128
        var height = $tip[0].offsetHeight; //33

        var marginTop = parseInt($tip.css('margin-top'), 10);  //去掉margin-top值的px单位  -3px -> -3
        var marginLeft = parseInt($tip.css('margin-left'), 10);

        if(isNaN(marginTop))    marginTop = 0;      //若marginTop为非数值 则设置为0
        if(isNaN(marginLeft))   marginLeft = 0; 

        offset.top  += marginTop;
        offset.left += marginLeft;

        $.offset.setOffset($tip[0],$.extend({		//用jquery内部的方法来指定top和left   $tip.css({top: offset.top,left: offset.left})
            using: function(props){
                $tip.css({
                    top: Math.round(props.top),
                    left: Math.round(props.left)
                });
            }
        },offset),0);
        $tip.addClass("in");
        var actualWidth  = $tip[0].offsetWidth;        //128
        var actualHeight = $tip[0].offsetHeight;       //33
        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.arrow = function(){
        return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'));
        // return this.$arrow?this.$arrow:this.tip.find(".tooltip-arrow");
    }
    Tooltip.prototype.getViewportAdjustedDelta = function(placement, pos, actualWidth, actualHeight){
        var delta = { top: 0, left: 0 }
        if (!this.$viewport) return delta

        var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
        var viewportDimensions = this.getPosition(this.$viewport)

        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 {
            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.getCalculatedOffset = function(placement,pos,actualWidth,actualHeight){
        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 } : { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }

        // return placement == 'bottom' ? { top: "bottom", left: "bottom" } :
        //      placement == 'top' ? { top: "top", left: "top" } :
        //      placement == 'left' ? { top: "left", left: "left"} :  { top: "right", left: "right" }
    }
    Tooltip.prototype.getPosition = function($element){
        $element = $element || this.$element;
        var el = $element[0];
        //el.tagName为 tooltip的触发元素 A、P、DIV...之类的元素
        var isBody = el.tagName == "BODY";
        var elRect = el.getBoundingClientRect();//在IE中,默认坐标从(2,2)开始计算,导致最终距离比其他浏览器多出两个像素
        // document.documentElement.clientTop;  // 非IE为0,IE为2
        // document.documentElement.clientLeft; // 非IE为0,IE为2
        if(elRect.width == null){   //兼容IE8 没有height和width
            elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
        }
        var isSvg = window.SVGElement && el instanceof window.SVGElement;   //el是否为svg元素
        var elOffset = isBody?{left: 0,top: 0}:(isSvg?null:$element.offset()); //获取匹配元素在当前视口的相对偏移
        var scroll = {scroll: isBody?(document.documentElement.scrollTop || document.body.scrollTop):$element.scrollTop()} //获取滚动条高度
        var outerDims = isBody?{width: $(window).width(),height: $(window).height()}:null; //body时兼容IE多出的两个像素坐标

        return $.extend({}, elRect, scroll, outerDims, elOffset);
    }
    Tooltip.prototype.setContent = function(){
        var $tip = this.tip();
        var title = this.getTitle();
        //判断用$().html()形式 还是$().text()方式填入title数据
        //$tip.find('.tooltip-inner').html(title) 或者	$tip.find('.tooltip-inner').text(title)
        $tip.find(".tooltip-inner")[this.options.html ? "html" : "text"](title);
        $tip.removeClass("fade in top bottom left right");
    }
    Tooltip.prototype.getUID = function(prefix){
        var ram = Math.random() * 1000000;
        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.fixTitle = function(){
        var $e = this.$element;
        if($e.attr("title") || typeof $e.attr("data-original-title") != "string"){    //删除默认的title及样式
            $e.attr("data-original-title",$e.attr("title")||"").removeAttr("title");
        }
    }
    Tooltip.prototype.hasContent = function(){
        return this.getTitle();
    }
    Tooltip.prototype.getTitle = function(){
        var title;
        var $e = this.$element;
        var o = this.options;
        title = $e.attr("data-original-title") || (typeof o.title == "function" ? o.title.call($e[0]) : o.title);
        return title;
    }


    function Plugin(option){
        return this.each(function(){
            var $this = $(this);   // this指调用此插件的元素 <a href="#" data-toggle="tooltip" title="Example tooltip">tooltip</a>
            var data = $this.data("bs.tooltip");
            var options = typeof option == "object" && option;  //options为传入object或者false

            if(!data && /hide|destroy/.test(option)) return;     //存在data或者参数为hide/destory不执行
            if(!data) $this.data("bs.tooltip",(data = new Tooltip(this,options)))  //data为Tooltip
            if(typeof option == "string") data[option](); //option为show toggle 相当于data.show()
        });
    }
    var old = $.fn.tooltip;
    $.fn.tooltip = Plugin;
    $.fn.Constructor = Tooltip;

    $.fn.noConflict = function(){
        $.fn.tooltip = old
        return this
    }
})(jQuery)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值