crossbrowdy
Version:
A Multimedia JavaScript framework to create real cross-platform and hybrid game engines, games, emulators, multimedia libraries and apps.
180 lines (159 loc) • 5.7 kB
JavaScript
/* Hammer-time - v0.3.0
* http://github.com/hammerjs/hammer-time
*
* Copyright Alexander Schmitz and other contributors
* Released under the MIT license
*
* Expiramental fastclick based on a partial polyfill of
* touch-action: none; CSS property
*/
( function() {
// Detect support for necessary features;
var MO = window.MutationObserver || window.WebKitMutationObserver;
var touchevents = ( "ontouchstart" in window ) ||
( window.DocumentTouch && document instanceof DocumentTouch );
var nativeTouchAction = document.documentElement.style[ "touch-action" ] !== undefined ||
document.documentElement.style[ "-ms-touch-action" ];
// iOS running pages from the homescreen still have the delay
var standalone = window.navigator.standalone;
// If there is native touch action bail the hammer has already dropped
if ( (nativeTouchAction && !standalone) || !touchevents || !MO ) {
return;
}
//Check if a global Hammer object already exists
window.Hammer = window.Hammer || {};
// We need to save the last touch start in iOS 8 if it is more then 150ms
// it will trigger native fast click which cant ne stoped even with all the might
// of thors hammer and a return false
var touchMatchNone = /touch-action[:][\s]*(none)[^;'"]*/;
var touchMatchManipulation = /touch-action[:][\s]*(manipulation)[^;'"]*/;
var touchMatch = /touch-action/;
var timeTouch = /(iP(ad|hone|od))/.test( navigator.userAgent ) && ( "indexedDB" in window || !!window.performance );
window.Hammer.time = {
// Check if an element has touch action none in its style attribute and thusly should
// be hammered upon
getTouchAction: function( element ) {
return this.checkStyleString( element.getAttribute( "style" ) );
},
checkStyleString: function( style ) {
if ( !touchMatch.test( style ) ) {
return undefined;
}
if ( touchMatchNone.test( style ) ) {
return "none";
}
if ( touchMatchManipulation.test( style ) ) {
return "manipulation";
}
return true;
},
shouldHammer: function( e ) {
var parentAction = e.target.hasParent;
return ( parentAction && ( !timeTouch || Date.now() - e.target.lastStart < 125 ) ) ?
parentAction : false;
},
touchHandler: function( e ) {
var hammerType = this.shouldHammer( e );
// Check both if we should trigger fast click and the time to avoid a double trigger with
// native fast click
if ( hammerType === "none" ) {
this.dropHammer( e );
} else if ( hammerType === "manipulation" ) {
var pos = e.target.getBoundingClientRect();
var scrolled = pos.top !== this.pos.top || pos.left !== this.pos.left;
!scrolled && this.dropHammer( e );
}
this.scrolled = false;
delete e.target.lastStart;
delete e.target.hasParent;
},
dropHammer: function( e ) {
if ( e.type === "touchend" ) {
e.target.focus();
// Wait for next tic so events fire in proper order
setTimeout( function() {
e.target.click();
}, 0 );
}
// Prevent the click which will come after this otherwise but with a 300ms delay
e.preventDefault();
},
touchStart: function( e ) {
this.pos = e.target.getBoundingClientRect();
e.target.hasParent = this.hasParent( e.target );
if ( timeTouch && e.target.hasParent ) {
e.target.lastStart = Date.now();
}
},
styleWatcher: function( mutations ) {
mutations.forEach( this.styleUpdater, this );
},
styleUpdater: function( mutation ) {
// We just caused this update bail
if ( mutation.target.updateNext ) {
mutation.target.updateNext = false;
return;
}
var touchAction = this.getTouchAction( mutation.target );
if ( touchAction ) {
if ( touchAction !== "none" ) {
mutation.target.hadTouchNone = false;
}
return;
}
if ( !touchAction &&
(
( mutation.oldValue && this.checkStyleString( mutation.oldValue ) ) ||
mutation.target.hadTouchNone
)
) {
// We save this because during animations which update the style in quick succession
// style tag can update quicker then the mutation observer fires so we lose the oldValue
// property which contains our refrence to the original which contained touch-action.
mutation.target.hadTouchNone = true;
// Save the fact that we caused the next update
mutation.target.updateNext = false;
// Add touch action back to the style attribute
mutation.target
.setAttribute( "style",
mutation.target.getAttribute( "style" ) + " touch-action: none;"
);
}
},
hasParent: function( node ) {
var touchAction;
for ( var cur = node; cur && cur.parentNode; cur = cur.parentNode ) {
touchAction = this.getTouchAction( cur );
if ( touchAction ) {
return touchAction;
}
}
return false;
},
installStartEvents: function() {
document.addEventListener( "touchstart", this.touchStart.bind( this ) );
document.addEventListener( "mousedown", this.touchStart.bind( this ) );
},
installEndEvents: function() {
document.addEventListener( "touchend", this.touchHandler.bind( this ), true );
document.addEventListener( "mouseup", this.touchHandler.bind( this ), true );
},
installObserver: function() {
// We need to observe changes to style attributes because if something updates the style
// attribute it will remove the touch-action property because browsers santitize the style
// attribute when its properties are updated.
this.observer = new MO( this.styleWatcher.bind( this ) ).observe( document, {
subtree: true,
attributes: true,
attributeOldValue: true,
attributeFilter: [ "style" ]
} );
},
install: function() {
this.installEndEvents();
this.installStartEvents();
this.installObserver();
}
};
window.Hammer.time.install();
} )();