Skip to content

Commit f7c7c27

Browse files
committed
Added logic to handle desynced tooltips.
1 parent d23861f commit f7c7c27

File tree

1 file changed

+85
-17
lines changed

1 file changed

+85
-17
lines changed

jquery.powertip.js

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* @fileoverview jQuery plugin that creates hover tooltips.
55
* @link https://github.com/stevenbenner/jquery-powertip
66
* @author Steven Benner
7-
* @version 1.0
7+
* @version 1.0.1
88
* @requires jQuery 1.7 or later
99
* @license jQuery PowerTip Plugin
1010
* <https://github.com/stevenbenner/jquery-powertip>
@@ -28,14 +28,14 @@
2828
var session = {
2929
isPopOpen: false,
3030
isFixedPopOpen: false,
31-
isMouseConstatnlyTracked: false,
3231
popOpenImminent: false,
3332
activeHover: null,
3433
mouseTarget: null,
3534
currentX: 0,
3635
currentY: 0,
3736
previousX: 0,
38-
previousY: 0
37+
previousY: 0,
38+
desyncTimeout: null
3939
};
4040

4141
/**
@@ -53,21 +53,22 @@
5353
// extend options
5454
var options = $.extend({}, $.fn.powerTip.defaults, opts);
5555

56+
// hook mouse tracking, once
57+
hookOnMoveOnce();
58+
5659
// build and append popup div if it does not already exist
5760
var tipElement = $('#' + options.popupId);
5861
if (tipElement.length === 0) {
5962
tipElement = $('<div></div>', { id: options.popupId });
6063
$body.append(tipElement);
6164
}
6265

63-
// because of the intent delay we need to constantly track the cursor
64-
// position for mouse-follow powertips
66+
// hook mousemove for cursor follow tooltips
6567
if (options.followMouse) {
6668
// only one movePop hook per popup element, please
6769
if (!tipElement.data('hasMouseMove')) {
6870
$window.on('mousemove', movePop);
6971
}
70-
session.isMouseConstatnlyTracked = true;
7172
tipElement.data('hasMouseMove', true);
7273
}
7374

@@ -116,9 +117,6 @@
116117
session.mouseTarget = element;
117118
session.previousX = event.pageX;
118119
session.previousY = event.pageY;
119-
if (!session.isMouseConstatnlyTracked) {
120-
element.on('mousemove', trackMouse);
121-
}
122120
if (!element.data('hasActiveHover')) {
123121
session.popOpenImminent = true;
124122
setHoverTimer(element, 'show');
@@ -128,9 +126,6 @@
128126
var element = $(this);
129127
cancelHoverTimer(element);
130128
session.mouseTarget = null;
131-
if (!session.isMouseConstatnlyTracked) {
132-
element.off('mousemove', trackMouse);
133-
}
134129
session.popOpenImminent = false;
135130
if (element.data('hasActiveHover')) {
136131
setHoverTimer(element, 'hide');
@@ -155,9 +150,6 @@
155150

156151
// check if difference has passed the sensitivity threshold
157152
if (totalDifference < options.intentSensitivity) {
158-
if (!session.isMouseConstatnlyTracked) {
159-
element.off('mousemove', trackMouse);
160-
}
161153
element.data('hasActiveHover', true);
162154
// show popup, asap
163155
showTip(element);
@@ -215,7 +207,12 @@
215207
tipElement.data('mouseOnToPopup', options.mouseOnToPopup);
216208

217209
// fadein
218-
tipElement.stop(true, true).fadeIn(options.fadeInTime);
210+
tipElement.stop(true, true).fadeIn(options.fadeInTime, function() {
211+
// start desync polling
212+
if (!session.desyncTimeout) {
213+
session.desyncTimeout = setInterval(closeDesyncedTip, 500);
214+
}
215+
});
219216
}
220217

221218
/**
@@ -234,9 +231,54 @@
234231
// after it is hidden
235232
tipElement.css('left', session.currentX + options.offset + 'px');
236233
tipElement.css('top', session.currentY + options.offset + 'px');
234+
// stop desync polling
235+
session.desyncTimeout = clearInterval(session.desyncTimeout);
237236
});
238237
}
239238

239+
/**
240+
* Checks for a tooltip desync and closes the tooltip if one occurs.
241+
* @private
242+
*/
243+
function closeDesyncedTip() {
244+
// It is possible for the mouse cursor to leave an element without
245+
// firing the mouseleave event. This seems to happen (in FF) if the
246+
// element is disabled under mouse cursor, the element is moved out
247+
// from under the mouse cursor (such as a slideDown() occurring
248+
// above it), or if the browser is resized by code moving the
249+
// element from under the mouse cursor. If this happens it will
250+
// result in a desynced tooltip because we wait for any exiting
251+
// open tooltips to close before opening a new one. So we should
252+
// periodically check for a desync situation and close the tip if
253+
// such a situation arises.
254+
if (session.isPopOpen) {
255+
var isDesynced = false;
256+
257+
// case 1: user already moused onto another tip - easy test
258+
if (session.activeHover.data('hasActiveHover') === false) {
259+
isDesynced = true;
260+
} else {
261+
// case 2: hanging tip - have to test if mouse position is
262+
// not over the active hover and not over a tooltip set to
263+
// let the user interact with it
264+
if (!isMouseOver(session.activeHover)) {
265+
if (tipElement.data('mouseOnToPopup')) {
266+
if (!isMouseOver(tipElement)) {
267+
isDesynced = true;
268+
}
269+
} else {
270+
isDesynced = true;
271+
}
272+
}
273+
}
274+
275+
if (isDesynced) {
276+
// close the desynced tip
277+
hideTip(session.activeHover);
278+
}
279+
}
280+
}
281+
240282
/**
241283
* Moves the tooltip popup to the users mouse cursor.
242284
* @private
@@ -249,7 +291,6 @@
249291
// but we should only set the pop location if a fixed pop is not
250292
// currently open, a pop open is imminent or active, and the popup
251293
// element in question does have a mouse-follow using it.
252-
trackMouse(event);
253294
if ((session.isPopOpen && !session.isFixedPopOpen) || (session.popOpenImminent && !session.isFixedPopOpen && tipElement.data('hasMouseMove'))) {
254295
// grab measurements
255296
var scrollTop = $window.scrollTop(),
@@ -364,6 +405,19 @@
364405
mouseOnToPopup: false
365406
};
366407

408+
var onMoveHooked = false;
409+
/**
410+
* Hooks the trackMouse() function to the window's mousemove event.
411+
* Prevents attaching the event more than once.
412+
* @private
413+
*/
414+
function hookOnMoveOnce() {
415+
if (!onMoveHooked) {
416+
onMoveHooked = true;
417+
$window.on('mousemove', trackMouse);
418+
}
419+
}
420+
367421
/**
368422
* Saves the current mouse coordinates to the powerTip session object.
369423
* @private
@@ -386,4 +440,18 @@
386440
}
387441
}
388442

443+
/**
444+
* Tests if the mouse is currently over the specified element.
445+
* @private
446+
* @param {Object} element The element to check for hover.
447+
* @return {Boolean}
448+
*/
449+
function isMouseOver(element) {
450+
var elementPosition = element.offset();
451+
return session.currentX >= elementPosition.left &&
452+
session.currentX <= elementPosition.left + element.outerWidth() &&
453+
session.currentY >= elementPosition.top &&
454+
session.currentY <= elementPosition.top + element.outerHeight();
455+
}
456+
389457
}(jQuery));

0 commit comments

Comments
 (0)