-
Notifications
You must be signed in to change notification settings - Fork 29
/
hammer-time.js
179 lines (159 loc) · 5.56 KB
/
hammer-time.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/* 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();
} )();