PageSourceSearch

https://www.artbridal.co.jp/js/tooltip/jquery.powertip.js

js artbridal.co.jp collected 2026-10-04 04:40:41 UTC 22,873 bytes, 796 lines download raw bytes

1/**
2 * PowerTip
3 *
4 * @fileoverview  jQuery plugin that creates hover tooltips.
5 * @link          http://stevenbenner.github.com/jquery-powertip/
6 * @author        Steven Benner (http://stevenbenner.com/)
7 * @version       1.1.0
8 * @requires      jQuery 1.7+
9 *
10 * @license jQuery PowerTip Plugin v1.1.0
11 * http://stevenbenner.github.com/jquery-powertip/
12 * Copyright 2012 Steven Benner (http://stevenbenner.com/)
13 * Released under the MIT license.
14 * <https://raw.github.com/stevenbenner/jquery-powertip/master/LICENSE.txt>
15 */
16
17(function($) {
18	'use strict';
19
20	// useful private variables
21	var $document = $(document),
22		$window = $(window),
23		$body = $('body');
24
25	/**
26	 * Session data
27	 * Private properties global to all powerTip instances
28	 * @type Object
29	 */
30	var session = {
31		isPopOpen: false,
32		isFixedPopOpen: false,
33		isClosing: false,
34		popOpenImminent: false,
35		activeHover: null,
36		currentX: 0,
37		currentY: 0,
38		previousX: 0,
39		previousY: 0,
40		desyncTimeout: null,
41		mouseTrackingActive: false
42	};
43
44	/**
45	 * Display hover tooltips on the matched elements.
46	 * @param {Object} opts The options object to use for the plugin.
47	 * @return {Object} jQuery object for the matched selectors.
48	 */
49	$.fn.powerTip = function(opts) {
50
51		// don't do any work if there were no matched elements
52		if (!this.length) {
53			return this;
54		}
55
56		// extend options
57		var options = $.extend({}, $.fn.powerTip.defaults, opts),
58			tipController = new TooltipController(options);
59
60		// hook mouse tracking
61		initMouseTracking();
62
63		// setup the elements
64		this.each(function() {
65			var $this = $(this),
66				dataPowertip = $this.data('powertip'),
67				dataElem = $this.data('powertipjq'),
68				dataTarget = $this.data('powertiptarget'),
69				title = $this.attr('title');
70
71
72			// attempt to use title attribute text if there is no data-powertip,
73			// data-powertipjq or data-powertiptarget. If we do use the title
74			// attribute, delete the attribute so the browser will not show it
75			if (!dataPowertip && !dataTarget && !dataElem && title) {
76				$this.data('powertip', title);
77				$this.removeAttr('title');
78			}
79
80			// create hover controllers for each element
81			$this.data(
82				'displayController',
83				new DisplayController($this, options, tipController)
84			);
85		});
86
87		// attach hover events to all matched elements
88		return this.on({
89			// mouse events
90			mouseenter: function(event) {
91				trackMouse(event);
92				session.previousX = event.pageX;
93				session.previousY = event.pageY;
94				$(this).data('displayController').show();
95			},
96			mouseleave: function() {
97				$(this).data('displayController').hide();
98			},
99
100			// keyboard events
101			focus: function() {
102				var element = $(this);
103				if (!isMouseOver(element)) {
104					element.data('displayController').show(true);
105				}
106			},
107			blur: function() {
108				$(this).data('displayController').hide(true);
109			}
110		});
111
112	};
113
114	/**
115	 * Default options for the powerTip plugin.
116	 * @type Object
117	 */
118	$.fn.powerTip.defaults = {
119		fadeInTime: 200,
120		fadeOutTime: 100,
121		followMouse: false,
122		popupId: 'powerTip',
123		intentSensitivity: 7,
124		intentPollInterval: 100,
125		closeDelay: 100,
126		placement: 'n',
127		smartPlacement: false,
128		offset: 10,
129		mouseOnToPopup: false
130	};
131
132	/**
133	 * Default smart placement priority lists.
134	 * The first item in the array is the highest priority, the last is the
135	 * lowest. The last item is also the default, which will be used if all
136	 * previous options do not fit.
137	 * @type Object
138	 */
139	$.fn.powerTip.smartPlacementLists = {
140		n: ['n', 'ne', 'nw', 's'],
141		e: ['e', 'ne', 'se', 'w', 'nw', 'sw', 'n', 's', 'e'],
142		s: ['s', 'se', 'sw', 'n'],
143		w: ['w', 'nw', 'sw', 'e', 'ne', 'se', 'n', 's', 'w'],
144		nw: ['nw', 'w', 'sw', 'n', 's', 'se', 'nw'],
145		ne: ['ne', 'e', 'se', 'n', 's', 'sw', 'ne'],
146		sw: ['sw', 'w', 'nw', 's', 'n', 'ne', 'sw'],
147		se: ['se', 'e', 'ne', 's', 'n', 'nw', 'se']
148	};
149
150	/**
151	 * Public API
152	 * @type Object
153	 */
154	$.powerTip = {
155
156		/**
157		 * Attempts to show the tooltip for the specified element.
158		 * @public
159		 * @param {Object} element The element that the tooltip should for.
160		 */
161		showTip: function(element) {
162			// close any open tooltip
163			$.powerTip.closeTip();
164			// grab only the first matched element and ask it to show its tip
165			element = element.first();
166			if (!isMouseOver(element)) {
167				element.data('displayController').show(true, true);
168			}
169		},
170
171		/**
172		 * Attempts to close any open tooltips.
173		 * @public
174		 */
175		closeTip: function() {
176			$document.triggerHandler('closePowerTip');
177		}
178
179	};
180
181	/**
182	 * Creates a new tooltip display controller.
183	 * @private
184	 * @constructor
185	 * @param {Object} element The element that this controller will handle.
186	 * @param {Object}
186 options Options object containing settings.
187	 * @param {TooltipController} tipController The TooltipController for this instance.
188	 */
189	function DisplayController(element, options, tipController) {
190		var hoverTimer = null;
191
192		/**
193		 * Begins the process of showing a tooltip.
194		 * @private
195		 * @param {Boolean=} immediate Skip intent testing (optional).
196		 * @param {Boolean=} forceOpen Ignore cursor position and force tooltip to open (optional).
197		 */
198		function openTooltip(immediate, forceOpen) {
199			cancelTimer();
200			if (!element.data('hasActiveHover')) {
201				if (!immediate) {
202					session.popOpenImminent = true;
203					hoverTimer = setTimeout(
204						function() {
205							hoverTimer = null;
206							checkForIntent(element);
207						},
208						options.intentPollInterval
209					);
210				} else {
211					if (forceOpen) {
212						element.data('forcedOpen', true);
213					}
214					tipController.showTip(element);
215				}
216			}
217		}
218
219		/**
220		 * Begins the process of closing a tooltip.
221		 * @private
222		 * @param {Boolean=} disableDelay Disable close delay (optional).
223		 */
224		function closeTooltip(disableDelay) {
225			cancelTimer();
226			if (element.data('hasActiveHover')) {
227				session.popOpenImminent = false;
228				element.data('forcedOpen', false);
229				if (!disableDelay) {
230					hoverTimer = setTimeout(
231						function() {
232							hoverTimer = null;
233							tipController.hideTip(element);
234						},
235						options.closeDelay
236					);
237				} else {
238					tipController.hideTip(element);
239				}
240			}
241		}
242
243		/**
244		 * Checks mouse position to make sure that the user intended to hover
245		 * on the specified element before showing the tooltip.
246		 * @private
247		 */
248		function checkForIntent() {
249			// calculate mouse position difference
250			var xDifference = Math.abs(session.previousX - session.currentX),
251				yDifference = Math.abs(session.previousY - session.currentY),
252				totalDifference = xDifference + yDifference;
253
254			// check if difference has passed the sensitivity threshold
255			if (totalDifference < options.intentSensitivity) {
256				tipController.showTip(element);
257			} else {
258				// try again
259				session.previousX = session.currentX;
260				session.previousY = session.currentY;
261				openTooltip();
262			}
263		}
264
265		/**
266		 * Cancels active hover timer.
267		 * @private
268		 */
269		function cancelTimer() {
270			hoverTimer = clearTimeout(hoverTimer);
271		}
272
273		// expose the methods
274		return {
275			show: openTooltip,
276			hide: closeTooltip,
277			cancel: cancelTimer
278		};
279	}
280
281	/**
282	 * Creates a new tooltip controller.
283	 * @private
284	 * @constructor
285	 * @param {Object} options Options object containing settings.
286	 */
287	function TooltipController(options) {
288
289		// build and append popup div if it does not already exist
290		var tipElement = $('#' + options.popupId);
291		if (tipElement.length === 0) {
292			tipElement = $('<div></div>', { id: options.popupId });
293			// grab body element if it was not populated when the script loaded
294			// this hack exists solely for jsfiddle support
295			if ($body.length === 0) {
296				$body = $('body');
297			}
298			$body.append(tipElement);
299		}
300
301		// hook mousemove for cursor follow tooltips
302		if (options.followMouse) {
303			// only one positionTipOnCursor hook per popup element, please
304			if (!tipElement.data('hasMouseMove')) {
305				$document.on({
306					mousemove: positionTipOnCursor,
307					scroll: positionTipOnCursor
308				});
309			}
310			tipElement.data('hasMouseMove', true);
311		}
312
313		// if we want to be able to mouse onto the popup then we need to attach
314		// hover events to the popup that will cancel a close request on hover
315		// and start a new close request on mouseleave
316		if (options.followMouse || options.mouseOnToPopup) {
317			tipElement.on({
318				mouseenter: function() {
319					if (tipElement.data('followMouse') || tipElement.data('mouseOnToPopup')) {
320						// check activeHover in case the mouse cursor entered
321						// the tooltip during the fadeOut and close cycle
322						if (session.activeHover) {
323							session.activeHover.data('displayController').cancel();
324						}
325					}
326				},
327				mouseleave: function() {
328					if (tipElement.data('mouseOnToPopup')) {
329						// check activeHover in case the mouse cursor entered
330						// the tooltip during the fadeOut and close cycle
331						if (session.activeHover) {
332							session.activeHover.data('displayController').hide();
333						}
334					}
335				}
336			});
337		}
338
339		/**
340		 * Gives the specified element the active-hover state and queues up
341		 * the showTip function.
342		 * @private
343		 * @param {Object} element The element that the tooltip should target.
344		 */
345		function beginShowTip(element) {
346			element.data('hasActiveHover', true);
347			// show popup, asap
348			tipElement.queue(function(next) {
349				showTip(element);
350				next();
351			});
352		}
353
354		/**
355		 * Shows the tooltip popup, as soon as possible.
356		 * @private
357		 * @param {Object} element The element that the popup should target.
358		 */
359		function showTip(element) {
360			// it is possible, especially with keyboard navigation, to move on
361			// to another element with a tooltip during the queue to get to
362			// this point in the code. if that happens then we need to not
363			// proceed or we may have the fadeout callback for the last tooltip
364			// execute immediately after this code runs, causing bugs.
365			if (!element.data('hasActiveHover')) {
366				return;
367			}
368
369			// if the popup is open and we got asked to open another one then
370			// the old one is still in its fadeOut cycle, so wait and try again
371			if (session.isPopOpen) {
372				if (!session.isClosing) {
373					hideTip(session.activeHover);
374				}
375				tipElement.delay(100).queue(function(next) {
376					showTip(element);
377					next();
378				});
379				return;
380			}
381
382			// trigger powerTipPreRender event
383			element.trigger('powerTipPreRender');
384
385			var tipText = element.data('powertip'),
386				tipTarget = element.data('powertiptarget'),
387				tipElem = element.data('powertipjq'),
388				tipContent = tipTarget ? $('#' + tipTarget) : [];
389
390			// set popup content
391			if (tipText) {
392				tipElement.html(tipText);
393			} else if (tipElem && tipElem.length > 0) {
394				tipElement.empty();
395				tipElem.clone(true, true).appendTo(tipElement);
396			} else if (tipContent && tipContent.length > 0) {
397				tipElement.html($('#' + tipTarget).html());
398			} else {
399				// we have no content to display, give up
400				return;
401			}
402
403			// trigger powerTipRender event
404			element.trigger('powerTipRender');
405
406			// hook close event for triggering from the api
407			$document.on('closePowerTip', function() {
408				element.data('displayController').hide(true);
409			});
410
411			session.activeHover = element;
412			session.isPopOpen = true;
413
414			tipElement.data('followMouse', options.followMouse);
415			tipElement.data('mouseOnToPopup', options.mouseOnToPopup);
416
417			// set popup position
418			if (!options.followMouse) {
419				positionTipOnElement(element);
420				session.isFixedPopOpen = true;
421			} else {
422				positionTipOnCursor();
423			}
424
425			// fadein
426			tipElement.fadeIn(options.fadeInTime, function() {
427				// start desync polling
428				if (!session.desyncTimeout) {
429					session.desyncTimeout = setInterval(closeDesyncedTip, 500);
430				}
431
432				// trigger powerTipOpen event
433				element.trigger('powerTipOpen');
434			});
435		}
436
437		/**
438		 * Hides the tooltip popup, immediately.
439		 * @private
440		 * @param {Object} element The element that the popup should target.
441		 */
442		function hideTip(element) {
443			session.isClosing = true;
444			element.data('hasActiveHover', false);
445			element.data('forcedOpen', false);
446			// reset session
447			session.activeHover = null;
448			session.isPopOpen = false;
449			// stop desync polling
450			session.desyncTimeout = clearInterval(session.desyncTimeout);
451			// unhook close event api listener
452			$document.off('closePowerTip');
453			// fade out
454			tipElement.fadeOut(options.fadeOutTime, function() {
455				session.isClosing = false;
456				session.isFixedPopOpen = false;
457				tipElement.removeClass();
458				// support mouse-follow and fixed position pops at the same
459				// time by moving the popup to the last known cursor location
460				// after it is hidden
461				setTipPosition(
462					session.currentX + options.offset,
463					session.currentY + options.offset
464				);
465
466				// trigger powerTipClose event
467				element.trigger('powerTipClose');
468			});
469		}
470
471		/**
472		 * Checks for a tooltip desync and closes the tooltip if one occurs.
473		 * @private
474		 */
475		function closeDesyncedTip() {
476			// It is possible for the mouse cursor to leave an element without
477			// firing the mouseleave event. This seems to happen (in FF) if the
478			// element is disabled under mouse cursor, the element is moved out
479			// from under the mouse cursor (such as a slideDown() occurring
480			// above it), or if the browser is resized by code moving the
481			// element from under the mouse cursor. If this happens it will
482			// result in a desynced tooltip because we wait for any exiting
483			// open tooltips to close before opening a new one. So we should
484			// periodically check for a desync situation and close the tip if
485			// such a situation arises.
486			if (session.isPopOpen && !session.isClosing) {
487				var isDesynced = false;
488
489				// case 1: user already moused onto another tip - easy test
490				if (session.activeHover.data('hasActiveHover') === false) {
491					isDesynced = true;
492				} else {
493					// case 2: hanging tip - have to test if mouse position is
494					// not over the active hover and not over a tooltip set to
495					// let the user interact with it.
496					// for keyboard navigation, this only counts if the element
497					// does not have focus.
498					// for tooltips opened via the api we need to check if it
499					// has the forcedOpen flag.
500					if (!isMouseOver(session.activeHover) && !session.activeHover.is(":focus") && !session.activeHover.data('forcedOpen')) {
501						if (tipElement.data('mouseOnToPopup')) {
502							if (!isMouseOver(tipElement)) {
503								isDesynced = true;
504							}
505						} else {
506							isDesynced = true;
507						}
508					}
509				}
510
511				if (isDesynced) {
512					// close the desynced tip
513					hideTip(session.activeHover);
514				}
515			}
516		}
517
518		/**
519		 * Moves the tooltip popup to the users mouse cursor.
520		 * @private
521		 */
522		function positionTipOnCursor() {
523			// to support having fixed powertips on the same page as cursor
524			// powertips, where both instances are referencing the same popup
525			// element, we need to keep track of the mouse position constantly,
526			// but we should only set the pop location if a fixed pop is not
527			// currently open, a pop open is imminent or active, and the popup
528			// element in question does have a mouse-follow using it.
529			if ((session.isPopOpen && !session.isFixedPopOpen) || (session.popOpenImminent && !session.isFixedPopOpen && tipElement.data('hasMouseMove'))) {
530				// grab measurements
531				var scrollTop = $window.scrollTop(),
532					windowWidth = $window.width(),
533					windowHeight = $window.height(),
534					popWidth = tipElement.outerWidth(),
535					popHeight = tipElement.outerHeight(),
536					x = 0,
537					y = 0;
538
539				// constrain pop to browser viewport
540				if ((popWidth + session.currentX + options.offset) < windowWidth) {
541					x = session.currentX + options.offset;
542				} else {
543					x = windowWidth - popWidth;
544				}
545				if ((popHeight + session.currentY + options.offset) < (scrollTop + windowHeight)) {
546					y = session.currentY + options.offset;
547				} else {
548					y = scrollTop + windowHeight - popHeight;
549				}
550
551				// position the tooltip
552				setTipPosition(x, y);
553			}
554		}
555
556		/**
557		 * Sets the tooltip popup too the correct position relative to the
558		 * specified target element. Based on options settings.
559		 * @private
560		 * @param {Object} element The element that the popup should target.
561		 */
562		function positionTipOnElement(element) {
563			var tipWidth = tipElement.outerWidth(),
564				tipHeight = tipElement.outerHeight(),
565				priorityList,
566				placementCoords,
567				finalPlacement,
568				collisions;
569
570			// with smart placement we will try a series of placement
571			// options and use the first one that does not collide with the
572			// browser view port boundaries.
573			if (options.smartPlacement) {
574
575				// grab the placement priority list
576				priorityList = $.fn.powerTip.smartPlacementLists[options.placement];
577
578				// iterate over the priority list and use the first placement
579				// option that does not collide with the viewport. if they all
580				// collide then the last placement in the list will be used.
581				$.each(priorityList, function(idx, pos) {
582					// get placement coordinates
583					placementCoords = computePlacementCoords(
584						element,
585						pos,
586						tipWidth,
587						tipHeight
588					);
589					finalPlacement = pos;
590
591					// find collisions
592					collisions = getViewportCollisions(
593						placementCoords,
594						tipWidth,
595						tipHeight
596					);
597
598					// break if there were no collisions
599					if (collisions.length === 0) {
600						return false;
601					}
602				});
603
604			} else {
605
606				// if we're not going to use the smart placement feature then
607				// just compute the coordinates and do it
608				placementCoords = computePlacementCoords(
609					element,
610					options.placement,
611					tipWidth,
612					tipHeight
613				);
614				finalPlacement = options.placement;
615
616			}
617
618			// add placement as class for CSS arrows
619			tipElement.addClass(finalPlacement);
620
621			// position the tooltip
622			setTipPosition(placementCoords.x, placementCoords.y);
623		}
624
625		/**
626		 * Compute the top/left coordinates to display the tooltip at the
627		 * specified placement relative to the specified element.
628		 * @private
629		 * @param {Object} element The element that the tooltip should target.
630		 * @param {String} placement The placement for the tooltip.
631		 * @param {Number} popWidth Width of the tooltip element in pixels.
632		 * @param {Number} popHeight Height of the tooltip element in pixels.
633		 * @retun {Object} An object with the x and y coordinates.
634		 */
635		function computePlacementCoords(element, placement, popWidth, popHeight) {
636			// grab measurements
637			var objectOffset = element.offset(),
638				objectWidth = element.outerWidth(),
639				objectHeight = element.outerHeight(),
640				x = 0,
641				y = 0;
642
643			// calculate the appropriate x and y position in the document
644			switch (placement) {
645			case 'n':
646				x = (objectOffset.left + (objectWidth / 2)) - (popWidth / 2);
647				y = objectOffset.top - popHeight - options.offset;
648				break;
649			case 'e':
650				x = objectOffset.left + objectWidth + options.offset;
651				y = (objectOffset.top + (objectHeight / 2)) - (popHeight / 2);
652				break;
653			case 's':
654				x = (objectOffset.left + (objectWidth / 2)) - (popWidth / 2);
655				y = objectOffset.top + objectHeight + options.offset;
656				break;
657			case 'w':
658				x = objectOffset.left - popWidth - options.offset;
659				y = (objectOffset.top + (objectHeight / 2)) - (popHeight / 2);
660				break;
661			case 'nw':
662				x = (objectOffset.left - popWidth) + 20;
663				y = objectOffset.top - popHeight - options.offset;
664				break;
665			case 'ne':
666				x = (objectOffset.left + objectWidth) - 20;
667				y = objectOffset.top - popHeight - options.offset;
668				break;
669			case 'sw':
670				x = (objectOffset.left - popWidth) + 20;
671				y = objectOffset.top + objectHeight + options.offset;
672				break;
673			case 'se':
674				x = (objectOffset.left + objectWidth) - 20;
675				y = objectOffset.top + objectHeight + options.offset;
676				break;
677			}
678
679			return {
680				x: Math.round(x),
681				y: Math.round(y)
682			};
683		}
684
685		/**
686		 * Sets the tooltip CSS position on the document.
687		 * @private
688		 * @param {Number} x Left position in pixels.
689		 * @param {Number} y Top position in pixels.
690		 */
691		function setTipPosition(x, y) {
692			tipElement.css('left', x + 'px');
693			tipElement.css('top', y + 'px');
694		}
695
696		// expose methods
697		return {
698			showTip: beginShowTip,
699			hideTip: hideTip
700		};
701	}
702
703	/**
704	 * Hooks mouse position tracking to mousemove and scroll events.
705	 * Prevents attaching the events more than once.
706	 * @private
707	 */
708	function initMouseTracking() {
709		var lastScrollX = 0,
710			lastScrollY = 0;
711
712		if (!session.mouseTrackingActive) {
713			session.mouseTrackingActive = true;
714
715			// grab the current scroll position on load
716			$(function() {
717				lastScrollX = $document.scrollLeft();
718				lastScrollY = $document.scrollTop();
719			});
720
721			// hook mouse position tracking
722			$document.on({
723				mousemove: trackMouse,
724				scroll: function() {
725					var x = $document.scrollLeft(),
726						y = $document.scrollTop();
727					if (x !== lastScrollX) {
728						session.currentX += x - lastScrollX;
729						lastScrollX = x;
730					}
731					if (y !== lastScrollY) {
732						session.currentY += y - lastScrollY;
733						lastScrollY = y;
734					}
735				}
736			});
737		}
738	}
739
740	/**
741	 * Saves the current mouse coordinates to the powerTip session object.
742	 * @private
743	 * @param {Object} event The mousemove event for the document.
744	 */
745	function trackMouse(event) {
746		session.currentX = event.pageX;
747		session.currentY = event.pageY;
748	}
749
750	/**
751	 * Tests if the mouse is currently over the specified element.
752	 * @private
753	 * @param {Object} element The element to check for hover.
754	 * @return {Boolean}
755	 */
756	function isMouseOver(element) {
757		var elementPosition = element.offset();
758		return session.currentX >= elementPosition.left &&
759			session.currentX <= elementPosition.left + element.outerWidth() &&
760			session.currentY >= elementPosition.top &&
761			session.currentY <= elementPosition.top + element.outerHeight();
762	}
763
764	/**
765	 * Finds any viewport collisions that an element (the tooltip) would have
766	 * if it were absolutely positioned at the specified coordinates.
767	 * @private
768	 * @param {Object} coords Coordinates for the element. (e.g. {x: 123, y: 123})
769	 * @param {Number} elementWidth Width of the element in pixels.
770	 * @param {Number} elementHeight Height of the element in pixels.
771	 * @return {Array} Array of words representing directional collisions.
772	 */
773	function getViewportCollisions(coords, elementWidth, elementHeight) {
774		var scrollLeft = $window.scrollLeft(),
775			scrollTop = $window.scrollTop(),
776			windowWidth = $window.width(),
777			windowHeight = $window.height(),
778			collisions = [];
779
780		if (coords.y < scrollTop) {
781			collisions.push('top');
782		}
783		if (coords.y + elementHeight > scrollTop + windowHeight) {
784			collisions.push('bottom');
785		}
786		if (coords.x < scrollLeft) {
787			collisions.push('left');
788		}
789		if (coords.x + elementWidth > scrollLeft + windowWidth) {
790			collisions.push('right');
791		}
792
793		return collisions;
794	}
795
796}(jQuery));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.