/*! copyright (c) 2013 brandon aaron (http://brandon.aaron.sh) * licensed under the mit license (license.txt). * * requires: jquery 1.7+ */ (function (factory) { if ( typeof define === 'function' && define.amd ) { // amd. register as an anonymous module. define(['jquery'], factory); } else if (typeof exports === 'object') { // node/commonjs style for browserify module.exports = factory; } else { // browser globals factory(jquery); } }(function ($) { // events that need to be added to fixhooks var tofix = ['wheel', 'mousewheel', 'dommousescroll', 'mozmousepixelscroll'], // events that will be listened for // the wheel event is most modern // the dommousescroll and mozmousepixelscroll are for older firefoxs tobind = ( 'onwheel' in document || document.documentmode >= 9 ) ? ['wheel'] : ['mousewheel', 'dommousescroll', 'mozmousepixelscroll'], slice = array.prototype.slice, nulllowestdeltatimeout, lowestdelta; // make sure we register the tofix events as mouse related // events so jquery will apply standard mouse fixes // for ( var i = tofix.length; i; ) { // $.event.fixhooks[ tofix[--i] ] = $.event.mousehooks; // } if($.event.fixhooks){for(var i=tofix.length;i;){$.event.fixhooks[tofix[--i]]=$.event.mousehooks;}} // the mousewheel special event var special = $.event.special.mousewheel = { version: '4.0.0-pre', // runs once per an element // tell jquery we'll handle how the event is added setup: function() { if ( this.addeventlistener ) { for ( var i = tobind.length; i; ) { this.addeventlistener( tobind[--i], handler, false ); } } else { this.onmousewheel = handler; } // store the line height and page height for this particular element $.data(this, 'mousewheel-line-height', special._getlineheight(this)); $.data(this, 'mousewheel-page-height', special._getpageheight(this)); }, // runs once per an event handler // use this to modify the handler function // based on any "settings" that are passed add: function(handleobj) { // settings are stored in mousewheel namespace on the data object var data = handleobj.data, settings = data && data.mousewheel; if ( settings ) { // throttle and debounce get applied first if ( 'throttle' in settings || 'debounce' in settings ) { special._delayhandler.call(this, handleobj); } // intent gets applied last so that it will be called // first since it deals with the initial interaction if ( 'intent' in settings ) { special._intenthandler.call(this, handleobj); } } }, // runs when $().trigger() is called // used to make sure the handler gets appropriately called trigger: function(data, event) { if (!event) { event = data; data = null; } handler.call(this, event); // let jquery know we fully handled the trigger call return false; }, // runs once per an element // tell jquery we'll handle how the event is removed teardown: function() { if ( this.removeeventlistener ) { for ( var i = tobind.length; i; ) { this.removeeventlistener( tobind[--i], handler, false ); } } else { this.onmousewheel = null; } // clean up the data we added to the element $.removedata(this, 'mousewheel-line-height'); $.removedata(this, 'mousewheel-page-height'); }, settings: { adjustolddeltas: true, // see shouldadjustolddeltas() below normalizeoffset: true // calls getboundingclientrect for each event }, // used to get the page height multiplier when deltamode is 2 _getpageheight: function(elem) { return $(elem).height(); }, // used to get the line height multiplier when deltamode is 1 _getlineheight: function(elem) { var $elem = $(elem), $parent = $elem['offsetparent' in $.fn ? 'offsetparent' : 'parent'](); if (!$parent.length) { $parent = $('body'); } return parseint($parent.css('fontsize'), 10) || parseint($elem.css('fontsize'), 10) || 16; }, // all the related delta fixing logic _fix: function(orgevent) { var deltax = 0, deltay = 0, absdelta = 0, offsetx = 0, offsety = 0; event = $.event.fix(orgevent); // old school scrollwheel delta if ( 'detail' in orgevent ) { deltay = orgevent.detail; } if ( 'wheeldelta' in orgevent ) { deltay = orgevent.wheeldelta * -1; } if ( 'wheeldeltay' in orgevent ) { deltay = orgevent.wheeldeltay * -1; } if ( 'wheeldeltax' in orgevent ) { deltax = orgevent.wheeldeltax * -1; } // firefox < 17 horizontal scrolling related to dommousescroll event if ( 'axis' in orgevent && orgevent.axis === orgevent.horizontal_axis ) { deltax = deltay; deltay = 0; } // new school wheel delta (wheel event) if ( 'deltay' in orgevent ) { deltay = orgevent.deltay; } if ( 'deltax' in orgevent ) { deltax = orgevent.deltax; } // no change actually happened, no reason to go any further if ( deltay === 0 && deltax === 0 ) { return; } // need to convert lines and pages to pixels if we aren't already in pixels // there are three delta modes: // * deltamode 0 is by pixels, nothing to do // * deltamode 1 is by lines // * deltamode 2 is by pages if ( orgevent.deltamode === 1 ) { var lineheight = $.data(this, 'mousewheel-line-height'); deltay *= lineheight; deltax *= lineheight; } else if ( orgevent.deltamode === 2 ) { var pageheight = $.data(this, 'mousewheel-page-height'); deltay *= pageheight; deltax *= pageheight; } // store lowest absolute delta to normalize the delta values absdelta = math.max( math.abs(deltay), math.abs(deltax) ); if ( !lowestdelta || absdelta < lowestdelta ) { lowestdelta = absdelta; // adjust older deltas if necessary if ( shouldadjustolddeltas(orgevent, absdelta) ) { lowestdelta /= 40; } } // adjust older deltas if necessary if ( shouldadjustolddeltas(orgevent, absdelta) ) { // divide all the things by 40! deltax /= 40; deltay /= 40; } // get a whole, normalized value for the deltas deltax = math[ deltax >= 1 ? 'floor' : 'ceil' ](deltax / lowestdelta); deltay = math[ deltay >= 1 ? 'floor' : 'ceil' ](deltay / lowestdelta); // normalise offsetx and offsety properties if ( special.settings.normalizeoffset && this.getboundingclientrect ) { var boundingrect = this.getboundingclientrect(); offsetx = event.clientx - boundingrect.left; offsety = event.clienty - boundingrect.top; } // add information to the event object event.deltax = deltax; event.deltay = deltay; event.deltafactor = lowestdelta; event.offsetx = offsetx; event.offsety = offsety; // go ahead and set deltamode to 0 since we converted to pixels // although this is a little odd since we overwrite the deltax/y // properties with normalized deltas. event.deltamode = 0; event.type = 'mousewheel'; return event; }, // returns a new handler that checks for users intent // by monitoring the mouse movement // can use as: // { mousewheel: { intent: true } } // or customize the default settings: // { mousewheel: { intent { interval: 300, sensitivity: 2 } } // can also pass preventdefault and stoppropagation which will // be called for all events that aren't passed to the original // event handler. _intenthandler: function(handleobj) { var timeout, px, py, cx, cy, hasintent = false, elem = this, settings = handleobj.data.mousewheel.intent, interval = settings.interval || 100, sensitivity = settings.sensitivity || 7, oldhandler = handleobj.handler, track = function(event) { cx = event.pagex; cy = event.pagey; }, compare = function() { if ( (math.abs(px-cx) + math.abs(py-cy)) < sensitivity ) { $(elem).off('mousemove', track); hasintent = true; } else { px = cx; py = cy; timeout = settimeout(compare, interval); } }, newhandler = function(event) { if (hasintent) { return oldhandler.apply(elem, arguments); } else { preventandstopifset(settings, event); } }; $(elem).on('mouseenter', function() { px = event.pagex; py = event.pagey; $(elem).on('mousemove', track); timeout = settimeout(compare, interval); }).on('mouseleave', function() { if (timeout) { cleartimeout(timeout); } $(elem).off('mousemove', track); hasintent = false; }); handleobj.handler = newhandler; }, // returns a new handler that uses either throttling or debouncing // can be used as: // { mousewheel: { debounce: true } } // { mousewheel: { throttle: true } } // or customize the default settings // { mousewheel: { debounce: { delay: 500, maxdelay: 2000 } } // can also pass preventdefault and stoppropagation which will // be called for all events. _delayhandler: function(handleobj) { var delaytimeout, maxtimeout, lastrun, elem = this, method = 'throttle' in handleobj.data.mousewheel ? 'throttle' : 'debounce', settings = handleobj.data.mousewheel[method], leading = 'leading' in settings ? settings.leading : method === 'debounce' ? false : true, trailing = 'trailing' in settings ? settings.trailing : true, delay = settings.delay || 100, maxdelay = method === 'throttle' ? delay : settings.maxdelay, oldhandler = handleobj.handler, newhandler = function(event) { var args = arguments, clear = function() { if ( maxtimeout ) { cleartimeout(maxtimeout); } delaytimeout = null; maxtimeout = null; lastrun = null; }, run = function() { lastrun = +new date(); return oldhandler.apply(elem, args); }, maxdelayed = function() { maxtimeout = null; return run(); }, delayed = function() { clear(); if ( trailing ) { return run(); } }, result; if ( delaytimeout ) { cleartimeout(delaytimeout); } else { if ( leading ) { result = run(); } } delaytimeout = settimeout(delayed, delay); if ( method === 'throttle' ) { if ( maxdelay && (+new date() - lastrun) >= maxdelay ) { result = maxdelayed(); } } else if ( maxdelay && !maxtimeout ) { maxtimeout = settimeout(maxdelayed, maxdelay); } preventandstopifset(settings, event); return result; }; handleobj.handler = newhandler; } }; // what is actually bound to the element function handler(event) { // might be trigged event, so check for the originalevent first var orgevent = event ? event.originalevent || event : window.event, args = slice.call(arguments, 1); event = special._fix(orgevent); // add event to the front of the arguments args.unshift(event); // clearout lowestdelta after sometime to better // handle multiple device types that give different // a different lowestdelta // ex: trackpad = 3 and mouse wheel = 120 if (nulllowestdeltatimeout) { cleartimeout(nulllowestdeltatimeout); } nulllowestdeltatimeout = settimeout(nulllowestdelta, 200); return $.event.dispatch.apply(this, args); } // used to clear out the last lowest delta value in a delayed fashion function nulllowestdelta() { lowestdelta = null; } function shouldadjustolddeltas(orgevent, absdelta) { // if this is an older event and the delta is divisable by 120, // then we are assuming that the browser is treating this as an // older mouse wheel event and that we should divide the deltas // by 40 to try and get a more usable deltafactor. // side note, this actually impacts the reported scroll distance // in older browsers and can cause scrolling to be slower than native. // turn this off by setting $.event.special.mousewheel.settings.adjustolddeltas to false. return special.settings.adjustolddeltas && orgevent.type === 'mousewheel' && absdelta % 120 === 0; } // used by intent and delay handlers function preventandstopifset(settings, event) { if (settings.preventdefault === true) { event.preventdefault(); } if (settings.stoppropagation === true) { event.stoppropagation(); } } }));