Chris@5: /*! Chris@5: * hoverIntent v1.9.0 // 2017.09.01 // jQuery v1.7.0+ Chris@5: * http://briancherne.github.io/jquery-hoverIntent/ Chris@5: * Chris@5: * You may use hoverIntent under the terms of the MIT license. Basically that Chris@5: * means you are free to use hoverIntent as long as this header is left intact. Chris@5: * Copyright 2007-2017 Brian Cherne Chris@5: */ Chris@5: Chris@5: /* hoverIntent is similar to jQuery's built-in "hover" method except that Chris@5: * instead of firing the handlerIn function immediately, hoverIntent checks Chris@5: * to see if the user's mouse has slowed down (beneath the sensitivity Chris@5: * threshold) before firing the event. The handlerOut function is only Chris@5: * called after a matching handlerIn. Chris@5: * Chris@5: * // basic usage ... just like .hover() Chris@5: * .hoverIntent( handlerIn, handlerOut ) Chris@5: * .hoverIntent( handlerInOut ) Chris@5: * Chris@5: * // basic usage ... with event delegation! Chris@5: * .hoverIntent( handlerIn, handlerOut, selector ) Chris@5: * .hoverIntent( handlerInOut, selector ) Chris@5: * Chris@5: * // using a basic configuration object Chris@5: * .hoverIntent( config ) Chris@5: * Chris@5: * @param handlerIn function OR configuration object Chris@5: * @param handlerOut function OR selector for delegation OR undefined Chris@5: * @param selector selector OR undefined Chris@5: * @author Brian Cherne Chris@5: */ Chris@5: Chris@5: ;(function(factory) { Chris@5: 'use strict'; Chris@5: if (typeof define === 'function' && define.amd) { Chris@5: define(['jquery'], factory); Chris@5: } else if (jQuery && !jQuery.fn.hoverIntent) { Chris@5: factory(jQuery); Chris@5: } Chris@5: })(function($) { Chris@5: 'use strict'; Chris@5: Chris@5: // default configuration values Chris@5: var _cfg = { Chris@5: interval: 100, Chris@5: sensitivity: 6, Chris@5: timeout: 0 Chris@5: }; Chris@5: Chris@5: // counter used to generate an ID for each instance Chris@5: var INSTANCE_COUNT = 0; Chris@5: Chris@5: // current X and Y position of mouse, updated during mousemove tracking (shared across instances) Chris@5: var cX, cY; Chris@5: Chris@5: // saves the current pointer position coordinates based on the given mousemove event Chris@5: var track = function(ev) { Chris@5: cX = ev.pageX; Chris@5: cY = ev.pageY; Chris@5: }; Chris@5: Chris@5: // compares current and previous mouse positions Chris@5: var compare = function(ev,$el,s,cfg) { Chris@5: // compare mouse positions to see if pointer has slowed enough to trigger `over` function Chris@5: if ( Math.sqrt( (s.pX-cX)*(s.pX-cX) + (s.pY-cY)*(s.pY-cY) ) < cfg.sensitivity ) { Chris@5: $el.off(s.event,track); Chris@5: delete s.timeoutId; Chris@5: // set hoverIntent state as active for this element (permits `out` handler to trigger) Chris@5: s.isActive = true; Chris@5: // overwrite old mouseenter event coordinates with most recent pointer position Chris@5: ev.pageX = cX; ev.pageY = cY; Chris@5: // clear coordinate data from state object Chris@5: delete s.pX; delete s.pY; Chris@5: return cfg.over.apply($el[0],[ev]); Chris@5: } else { Chris@5: // set previous coordinates for next comparison Chris@5: s.pX = cX; s.pY = cY; Chris@5: // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs) Chris@5: s.timeoutId = setTimeout( function(){compare(ev, $el, s, cfg);} , cfg.interval ); Chris@5: } Chris@5: }; Chris@5: Chris@5: // triggers given `out` function at configured `timeout` after a mouseleave and clears state Chris@5: var delay = function(ev,$el,s,out) { Chris@5: delete $el.data('hoverIntent')[s.id]; Chris@5: return out.apply($el[0],[ev]); Chris@5: }; Chris@5: Chris@5: $.fn.hoverIntent = function(handlerIn,handlerOut,selector) { Chris@5: // instance ID, used as a key to store and retrieve state information on an element Chris@5: var instanceId = INSTANCE_COUNT++; Chris@5: Chris@5: // extend the default configuration and parse parameters Chris@5: var cfg = $.extend({}, _cfg); Chris@5: if ( $.isPlainObject(handlerIn) ) { Chris@5: cfg = $.extend(cfg, handlerIn); Chris@5: if ( !$.isFunction(cfg.out) ) { Chris@5: cfg.out = cfg.over; Chris@5: } Chris@5: } else if ( $.isFunction(handlerOut) ) { Chris@5: cfg = $.extend(cfg, { over: handlerIn, out: handlerOut, selector: selector } ); Chris@5: } else { Chris@5: cfg = $.extend(cfg, { over: handlerIn, out: handlerIn, selector: handlerOut } ); Chris@5: } Chris@5: Chris@5: // A private function for handling mouse 'hovering' Chris@5: var handleHover = function(e) { Chris@5: // cloned event to pass to handlers (copy required for event object to be passed in IE) Chris@5: var ev = $.extend({},e); Chris@5: Chris@5: // the current target of the mouse event, wrapped in a jQuery object Chris@5: var $el = $(this); Chris@5: Chris@5: // read hoverIntent data from element (or initialize if not present) Chris@5: var hoverIntentData = $el.data('hoverIntent'); Chris@5: if (!hoverIntentData) { $el.data('hoverIntent', (hoverIntentData = {})); } Chris@5: Chris@5: // read per-instance state from element (or initialize if not present) Chris@5: var state = hoverIntentData[instanceId]; Chris@5: if (!state) { hoverIntentData[instanceId] = state = { id: instanceId }; } Chris@5: Chris@5: // state properties: Chris@5: // id = instance ID, used to clean up data Chris@5: // timeoutId = timeout ID, reused for tracking mouse position and delaying "out" handler Chris@5: // isActive = plugin state, true after `over` is called just until `out` is called Chris@5: // pX, pY = previously-measured pointer coordinates, updated at each polling interval Chris@5: // event = string representing the namespaced event used for mouse tracking Chris@5: Chris@5: // clear any existing timeout Chris@5: if (state.timeoutId) { state.timeoutId = clearTimeout(state.timeoutId); } Chris@5: Chris@5: // namespaced event used to register and unregister mousemove tracking Chris@5: var mousemove = state.event = 'mousemove.hoverIntent.hoverIntent'+instanceId; Chris@5: Chris@5: // handle the event, based on its type Chris@5: if (e.type === 'mouseenter') { Chris@5: // do nothing if already active Chris@5: if (state.isActive) { return; } Chris@5: // set "previous" X and Y position based on initial entry point Chris@5: state.pX = ev.pageX; state.pY = ev.pageY; Chris@5: // update "current" X and Y position based on mousemove Chris@5: $el.off(mousemove,track).on(mousemove,track); Chris@5: // start polling interval (self-calling timeout) to compare mouse coordinates over time Chris@5: state.timeoutId = setTimeout( function(){compare(ev,$el,state,cfg);} , cfg.interval ); Chris@5: } else { // "mouseleave" Chris@5: // do nothing if not already active Chris@5: if (!state.isActive) { return; } Chris@5: // unbind expensive mousemove event Chris@5: $el.off(mousemove,track); Chris@5: // if hoverIntent state is true, then call the mouseOut function after the specified delay Chris@5: state.timeoutId = setTimeout( function(){delay(ev,$el,state,cfg.out);} , cfg.timeout ); Chris@5: } Chris@5: }; Chris@5: Chris@5: // listen for mouseenter and mouseleave Chris@5: return this.on({'mouseenter.hoverIntent':handleHover,'mouseleave.hoverIntent':handleHover}, cfg.selector); Chris@5: }; Chris@5: });