Search code examples
javascriptjquerytooltipmousemovetipsy

Make jQuery Tipsy tooltip move with mouse


I'm sure this is possible -- and probably very easy -- but I can't figure out how to make it work. How can I make a Tipsy tooltip move horizontally along with the mouse?

When hovering over a Tipsy-enabled element, I want the tooltip to appear at its normal Y position (directly above the element), but instead of having the X position fixed at the center of the element, I want it to be equal to the mouse's X position . To complicate things a bit, I'm trying to have the mousemove function enabled ONLY for elements that have a gravity of 2 characters ('nw', 'ne', 'sw', 'se').

No code to post, as everything I've tried hasn't worked and the plugin is publicly available.


Solution

  • Note, tip won't follow mouse completely. It''ll follow on the respective edge of the element to which tip is applied.

    If you want tip to follow mouse where it's actually is on the display, then you should consider using different plugin.


    How to use:

    $('.element').tipsy({follow: 'x'});
    
    $('.element').tipsy({follow: 'y'});
    

    Plugin itself (needs more testing)

    // tipsy, facebook style tooltips for jquery
    // version 1.0.0a
    // (c) 2008-2010 jason frame [[email protected]]
    // releated under the MIT license
    
    (function($) {
    
        function fixTitle($ele) {
            if ($ele.attr('title') || typeof($ele.attr('original-title')) != 'string') {
                $ele.attr('original-title', $ele.attr('title') || '').removeAttr('title');
            }
        }
    
        function Tipsy(element, options) {
            this.$element = $(element);
            this.options = options;
            this.enabled = true;
            fixTitle(this.$element);
        }
    
        Tipsy.prototype = {
            show: function() {
                var title = this.getTitle();
                if (title && this.enabled) {
                    var $tip = this.tip();
    
                    $tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
                    $tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity
                    $tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).appendTo(document.body);
    
                    var pos = $.extend({}, this.$element.offset(), {
                        width: this.$element[0].offsetWidth,
                        height: this.$element[0].offsetHeight
                    });
    
                    var actualWidth = $tip[0].offsetWidth, actualHeight = $tip[0].offsetHeight;
                    var gravity = (typeof this.options.gravity == 'function')
                                    ? this.options.gravity.call(this.$element[0])
                                    : this.options.gravity;
    
                    var tp;
                    switch (gravity.charAt(0)) {
                        case 'n':
                            tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
                            break;
                        case 's':
                            tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
                            break;
                        case 'e':
                            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset};
                            break;
                        case 'w':
                            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset};
                            break;
                    }
    
                    if (gravity.length == 2) {
                        if (gravity.charAt(1) == 'w') {
                            tp.left = pos.left + pos.width / 2 - 15;
                        } else {
                            tp.left = pos.left + pos.width / 2 - actualWidth + 15;
                        }
                    }
    
                    $tip.css(tp).addClass('tipsy-' + gravity);
    
                    if (this.options.fade) {
                        $tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity});
                    } else {
                        $tip.css({visibility: 'visible', opacity: this.options.opacity});
                    }
                }
            },
    
            hide: function() {
                if (this.options.fade) {
                    this.tip().stop().fadeOut(function() { $(this).remove(); });
                } else {
                    this.tip().remove();
                }
            },
    
            getTitle: function() {
                var title, $e = this.$element, o = this.options;
                fixTitle($e);
                var title, o = this.options;
                if (typeof o.title == 'string') {
                    title = $e.attr(o.title == 'title' ? 'original-title' : o.title);
                } else if (typeof o.title == 'function') {
                    title = o.title.call($e[0]);
                }
                title = ('' + title).replace(/(^\s*|\s*$)/, "");
                return title || o.fallback;
            },
    
            tip: function() {
                if (!this.$tip) {
                    this.$tip = $('<div class="tipsy"></div>').html('<div class="tipsy-arrow"></div><div class="tipsy-inner"/></div>');
                }
                return this.$tip;
            },
    
            validate: function() {
                if (!this.$element[0].parentNode) {
                    this.hide();
                    this.$element = null;
                    this.options = null;
                }
            },
    
            enable: function() { this.enabled = true; },
            disable: function() { this.enabled = false; },
            toggleEnabled: function() { this.enabled = !this.enabled; }
        };
    
        $.fn.tipsy = function(options) {
    
            if (options === true) {
                return this.data('tipsy');
            } else if (typeof options == 'string') {
                return this.data('tipsy')[options]();
            }
    
            options = $.extend({}, $.fn.tipsy.defaults, options);
    
            function get(ele) {
                var tipsy = $.data(ele, 'tipsy');
                if (!tipsy) {
                    tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options));
                    $.data(ele, 'tipsy', tipsy);
                }
                return tipsy;
            }
    
            function enter() {
                var tipsy = get(this);
                tipsy.hoverState = 'in';
                if (options.delayIn == 0) {
                    tipsy.show();
                } else {
                    setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn);
                }
            };
    
                    function move(event) {
                            var tipsy = get(this);
                            tipsy.hoverState = 'in';
                            if (options.follow == 'x') {
                                var arrow = $(tipsy.$tip).children('.tipsy-arrow');
                                if (/^[^w]w$/.test(options.gravity) && arrow.position() != null) {
                                    var x = event.pageX - ($(arrow).position().left+($(arrow).outerWidth()/2));
                                } else if (/^[^e]e$/.test(options.gravity) && arrow.position() != null) {
                                    var x = event.pageX - ($(arrow).position().left+($(arrow).outerWidth()/2));
                                } else {
                                    var x = event.pageX - ($(tipsy.$tip).outerWidth()/2);
                                }
                                $(tipsy.$tip).css('left', x);
                            } else if (options.follow == 'y') {
                                if (/^w|^e/.test(options.gravity) ) {
                                    $(tipsy.$tip).css('top', event.pageY-($(tipsy.$tip).outerHeight()/2));
                                }
                            }
    
                    }
    
            function leave() {
                var tipsy = get(this);
                tipsy.hoverState = 'out';
                if (options.delayOut == 0) {
                    tipsy.hide();
                } else {
                    setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut);
                }
            };
    
            if (!options.live) this.each(function() { get(this); });
    
            if (options.trigger != 'manual') {
                var binder   = options.live ? 'live' : 'bind',
                    eventIn  = options.trigger == 'hover' ? 'mouseenter' : 'focus',
                    eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur',
                                    eventMove = 'mousemove';
                this[binder](eventIn, enter)[binder](eventOut, leave)[binder](eventMove, move);
    
            }
    
            return this;
    
        };
    
        $.fn.tipsy.defaults = {
            delayIn: 0,
            delayOut: 0,
            fade: false,
            fallback: '',
            gravity: 'n',
            html: false,
            live: false,
            offset: 0,
            opacity: 0.8,
            title: 'title',
            trigger: 'hover',
                    follow: false,
        };
    
        // Overwrite this method to provide options on a per-element basis.
        // For example, you could store the gravity in a 'tipsy-gravity' attribute:
        // return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
        // (remember - do not modify 'options' in place!)
        $.fn.tipsy.elementOptions = function(ele, options) {
            return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
        };
    
        $.fn.tipsy.autoNS = function() {
            return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
        };
    
        $.fn.tipsy.autoWE = function() {
            return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
        };
    
    })(jQuery);