/*! * jquery.event.linger - v 1.0.0 * Copyright (c) 2010 Three Dub Media - http://threedubmedia.com * Open Source MIT License - http://threedubmedia.com/code/license */ // Created: 2008-06-02 // Updated: 2010-09-13 // REQUIRES: jquery 1.4.2+ ;(function($){ // secure $ jQuery alias // add the jquery instance method $.fn.linger = function( str, arg, opts ){ // figure out the event type var type = typeof str == "string" ? str : "", // figure out the event handler... fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null; // fix the event type if ( type.indexOf("linger") !== 0 ) type = "linger"+ type; // were options passed opts = ( str == fn ? arg : opts ) || {}; // trigger or bind event handler return fn ? this.bind( type, opts, fn ) : this.trigger( type ); }; // local refs (increase compression) var $event = $.event, $special = $event.special, // special event configuration linger = $special.linger = { // default settings defaults: { speed: 100, // speed limit (pixels per second) delay: 100, // milliseconds per speed check persist: 400 // milliseconds after mouseleave }, // the key name for stored data datakey: "lingerdata", // count bound related events add: function( obj ){ // read the interaction data var data = $.data( this, linger.datakey ), // read any passed options opts = obj.data || {}; // count another realted event data.related += 1; // extend data options bound with this event // don't iterate "opts" in case it is a node $.each( linger.defaults, function( key, def ){ if ( opts[ key ] !== undefined ) data[ key ] = opts[ key ]; }); }, // forget unbound related events remove: function(){ $.data( this, linger.datakey ).related -= 1; }, // configure interaction setup: function(){ // check for related events if ( $.data( this, linger.datakey ) ) return; // initialize the drag data with copied defaults var data = $.extend({ related:0 }, linger.defaults ); // store the interaction data $.data( this, linger.datakey, data ); // bind the mouse events with data $event.add( this, "mouseenter mouseleave", linger.handler, data ); }, // destroy configured interaction teardown: function(){ // check for related events if ( $.data( this, linger.datakey ).related ) return; // remove the stored data $.removeData( this, linger.datakey ); // remove the mouse events $event.remove( this, "mouseenter mousemove mouseleave", linger.handler ); }, // handle mouse events handler: function( event ){ var data = event.data || {}; // initialize props for new interaction if ( event.type == "mouseenter" && !data.lingered ){ // mouse distance squared data.dist2 = 0; // ( speed * time ) squared data.limit = Math.pow( data.speed * ( data.delay/1e3 ), 2 ); // the interacted element data.elem = this; // handle the start event (handler may return false to cancel) if ( linger.hijack( event, "lingerstart", this ) ){ // store the event, to compare later data.event = event; // begin tracking the mouse movement $event.add( this, "mousemove", linger.handler, data ); // start comparing mouse speed at fixed intervals data.timer = setTimeout(function(){ // check the current speed against the limit if ( data.dist2 <= data.limit ) // handle the linger event (handler may return false to cancel) data.lingered = linger.hijack( data.event, "linger", data.elem ); // stop tracking the mouse if ( data.lingered ) $event.remove( data.elem, "mousemove", linger.handler ); // recurse else data.timer = setTimeout( arguments.callee, data.delay ); // reset distance for next comparison data.dist2 = 0; }, data.delay ); } } // stop if not properly initialized if ( !data.event ) return; // handle other events switch ( event.type ){ // mouse re-entry case data.lingered && 'mouseenter': // stop the current timer clearTimeout( data.timer ); break; // track mouse movement case 'mousemove': // distance² = x² + y² data.dist2 += Math.pow( event.pageX - data.event.pageX, 2 ) + Math.pow( event.pageY - data.event.pageY, 2 ); // store current event data.event = event; break; // handle leaving after lingering case data.lingered && 'mouseleave': // optionally delay the end event data.timer = setTimeout(function(){ // handle the end event, flip flag for lingering data.lingered = !linger.hijack( event, "lingerend", data.elem ); // if still lingering, recurse if ( data.lingered ) setTimeout( arguments.callee, data.persist ); }, data.persist ); break; // handle leaving after no lingering case !data.lingered && 'mouseleave': // stop the current timer clearTimeout( data.timer ); // stop tracking the mouse movement $event.remove( data.elem, "mousemove", linger.handler ); // clean the data for next interaction data.event = null; break; } }, // re-use event object for custom events hijack: function( event, type, elem ){ // remember the original event and type var result, orig = { event: event.originalEvent, type: event.type }; // modify the event type event.type = type; // remove the original event event.originalEvent = null; // remove any previous event result event.result = null; // handle the custom event result = $event.handle.call( elem, event ); // restore the original event & type event.type = orig.type; event.originalEvent = orig.event; // return handled result return ( result !== false ); } }; // share the same special event configuration with related events... $special.lingerstart = $special.lingerend = linger; })(jQuery); // confine scope