/*! Overthrow. An overflow:auto polyfill for responsive design. (c) 2012: Scott Jehl, Filament Group, Inc. http://filamentgroup.github.com/Overthrow/license.txt */ (function( w, o, undefined ){ // o is overthrow reference from overthrow-polyfill.js if( o === undefined ){ return; } o.scrollIndicatorClassName = "overthrow"; var doc = w.document, docElem = doc.documentElement, // o api nativeOverflow = o.support === "native", canBeFilledWithPoly = o.canBeFilledWithPoly, configure = o.configure, set = o.set, forget = o.forget, scrollIndicatorClassName = o.scrollIndicatorClassName; // find closest overthrow (elem or a parent) o.closest = function( target, ascend ){ return !ascend && target.className && target.className.indexOf( scrollIndicatorClassName ) > -1 && target || o.closest( target.parentNode ); }; // polyfill overflow var enabled = false; o.set = function(){ set(); // If nativeOverflow or it doesn't look like the browser canBeFilledWithPoly, our job is done here. Exit viewport left. if( enabled || nativeOverflow || !canBeFilledWithPoly ){ return; } w.overthrow.addClass(); enabled = true; o.support = "polyfilled"; o.forget = function(){ forget(); enabled = false; // Remove touch binding (check for method support since this part isn't qualified by touch support like the rest) if( doc.removeEventListener ){ doc.removeEventListener( "touchstart", start, false ); } }; // Fill 'er up! // From here down, all logic is associated with touch scroll handling // elem references the overthrow element in use var elem, // The last several Y values are kept here lastTops = [], // The last several X values are kept here lastLefts = [], // lastDown will be true if the last scroll direction was down, false if it was up lastDown, // lastRight will be true if the last scroll direction was right, false if it was left lastRight, // For a new gesture, or change in direction, reset the values from last scroll resetVertTracking = function(){ lastTops = []; lastDown = null; }, resetHorTracking = function(){ lastLefts = []; lastRight = null; }, // On webkit, touch events hardly trickle through textareas and inputs // Disabling CSS pointer events makes sure they do, but it also makes the controls innaccessible // Toggling pointer events at the right moments seems to do the trick // Thanks Thomas Bachem http://stackoverflow.com/a/5798681 for the following inputs, setPointers = function( val ){ inputs = elem.querySelectorAll( "textarea, input" ); for( var i = 0, il = inputs.length; i < il; i++ ) { inputs[ i ].style.pointerEvents = val; } }, // For nested overthrows, changeScrollTarget restarts a touch event cycle on a parent or child overthrow changeScrollTarget = function( startEvent, ascend ){ if( doc.createEvent ){ var newTarget = ( !ascend || ascend === undefined ) && elem.parentNode || elem.touchchild || elem, tEnd; if( newTarget !== elem ){ tEnd = doc.createEvent( "HTMLEvents" ); tEnd.initEvent( "touchend", true, true ); elem.dispatchEvent( tEnd ); newTarget.touchchild = elem; elem = newTarget; newTarget.dispatchEvent( startEvent ); } } }, // Touchstart handler // On touchstart, touchmove and touchend are freshly bound, and all three share a bunch of vars set by touchstart // Touchend unbinds them again, until next time start = function( e ){ // Stop any throw in progress if( o.intercept ){ o.intercept(); } // Reset the distance and direction tracking resetVertTracking(); resetHorTracking(); elem = o.closest( e.target ); if( !elem || elem === docElem || e.touches.length > 1 ){ return; } setPointers( "none" ); var touchStartE = e, scrollT = elem.scrollTop, scrollL = elem.scrollLeft, height = elem.offsetHeight, width = elem.offsetWidth, startY = e.touches[ 0 ].pageY, startX = e.touches[ 0 ].pageX, scrollHeight = elem.scrollHeight, scrollWidth = elem.scrollWidth, // Touchmove handler move = function( e ){ var ty = scrollT + startY - e.touches[ 0 ].pageY, tx = scrollL + startX - e.touches[ 0 ].pageX, down = ty >= ( lastTops.length ? lastTops[ 0 ] : 0 ), right = tx >= ( lastLefts.length ? lastLefts[ 0 ] : 0 ); // If there's room to scroll the current container, prevent the default window scroll if( ( ty > 0 && ty < scrollHeight - height ) || ( tx > 0 && tx < scrollWidth - width ) ){ e.preventDefault(); } // This bubbling is dumb. Needs a rethink. else { changeScrollTarget( touchStartE ); } // If down and lastDown are inequal, the y scroll has changed direction. Reset tracking. if( lastDown && down !== lastDown ){ resetVertTracking(); } // If right and lastRight are inequal, the x scroll has changed direction. Reset tracking. if( lastRight && right !== lastRight ){ resetHorTracking(); } // remember the last direction in which we were headed lastDown = down; lastRight = right; // set the container's scroll elem.scrollTop = ty; elem.scrollLeft = tx; lastTops.unshift( ty ); lastLefts.unshift( tx ); if( lastTops.length > 3 ){ lastTops.pop(); } if( lastLefts.length > 3 ){ lastLefts.pop(); } }, // Touchend handler end = function( e ){ // Bring the pointers back setPointers( "auto" ); setTimeout( function(){ setPointers( "none" ); }, 450 ); elem.removeEventListener( "touchmove", move, false ); elem.removeEventListener( "touchend", end, false ); }; elem.addEventListener( "touchmove", move, false ); elem.addEventListener( "touchend", end, false ); }; // Bind to touch, handle move and end within doc.addEventListener( "touchstart", start, false ); }; })( this, this.overthrow );