-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbasic-trackpad-direction.html
More file actions
197 lines (163 loc) · 6.28 KB
/
basic-trackpad-direction.html
File metadata and controls
197 lines (163 loc) · 6.28 KB
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
<!--
Aspect which maps a horizontal trackpad swipe gesture (or horizontal mouse wheel
action) to direction semantics.
To respond to the trackpad, we can listen to the DOM's "wheel" events. These
events are fired as the user drags their fingers across a trackpad.
Unfortunately, this scheme is missing a critical event — there is no event when
the user *stops* a gestured on the trackpad.
To complicate matters, the mainstream browsers continue to generate wheel events
even after the user has stopped dragging their fingers. These fake events
simulate the user gradually slowing down the drag until they come to a smooth
stop. In some contexts, these fake wheel events might be helpful, but in trying
to supply typical trackpad swipe navigation, these fake events get in the way.
This component uses some heuristics to work around these problems, but the
complex nature of the problem make it extremely difficult to achieve the same
degree of trackpad responsiveness possible with native applications.
@element basic-trackpad-direction
-->
<link rel="import" href="../basic-aspect/basic-aspect.html">
<script>
Polymer({
behaviors: [Basic.Aspect],
contribute: {
// Default implementations. These will typically be handled by other aspects
// in the collective.
goLeft: Basic.Collective.defaultMethod,
goRight: Basic.Collective.defaultMethod,
showTransition: Basic.Collective.defaultMethod
},
is: 'basic-trackpad-direction',
ready: function() {
this.addEventListener('wheel', function(event) {
var handled = this._wheel(event);
if (handled) {
event.preventDefault();
}
}.bind(this));
this._resetWheelTracking();
},
// Following a navigation, partially reset our wheel tracking.
_postNavigate: function() {
this.collective.position = 0;
this._wheelDistance = 0;
this._postNavigateDelayComplete = true;
this._absorbDeceleration = true;
setTimeout(function() {
this._postNavigateDelayComplete = false;
}.bind(this), this._postNavigateTime);
},
// Time we wait following a navigation before paying attention to wheel
// events again.
_postNavigateTime: 250,
// Reset all state related to the tracking of the wheel.
_resetWheelTracking: function() {
this.collective.position = 0;
this._wheelDistance = 0;
this._lastDeltaX = 0;
this._absorbDeceleration = false;
this._postNavigateDelayComplete = false;
if (this._lastWheelTimeout) {
clearTimeout(this._lastWheelTimeout);
this._lastWheelTimeout = null;
}
},
// Define our own sign function, since (as of May 2015), Safari and IE don't
// supply Math.sign().
_sign: function(x) {
return (x === 0) ?
0 :
(x > 0) ?
1 :
-1;
},
// TODO: Damping, or some other treatment for going past the ends.
/*
* A wheel event has been generated. This could be a real wheel event, or it
* could be fake (see notes in the header).
*
* This handler uses several strategies to try to approximate native trackpad
* swipe navigation.
*
* If the user has dragged enough to cause a navigation, then for a short
* delay following that navigation, subsequent wheel events will be ignored.
*
* Furthermore, follwowing a navigation, we ignore all wheel events until we
* receive at least one event where the event's deltaX (distance traveled) is
* *greater* than the previous event's deltaX. This helps us filter out the
* fake wheel events generated by the browser to simulate deceleration.
*
*/
_wheel: function(event) {
// Since we have a new wheel event, reset our timer waiting for the last
// wheel event to pass.
if (this._lastWheelTimeout) {
clearTimeout(this._lastWheelTimeout);
}
this._lastWheelTimeout = setTimeout(this._wheelTimedOut.bind(this), this._wheelTime);
var deltaX = event.deltaX;
var deltaY = event.deltaY;
// See if this event represents acceleration or deceleration.
var acceleration = this._sign(deltaX) * (deltaX - this._lastDeltaX);
this._lastDeltaX = deltaX;
// console.log(deltaX + " " + acceleration + " " + this._absorbDeceleration + " " + this._postNavigateDelayComplete);
if (Math.abs(deltaX) < Math.abs(deltaY)) {
// Move was mostly vertical. The user may be trying scroll with the
// trackpad/wheel. To be on the safe, we ignore such events.
return false;
}
if (this._postNavigateDelayComplete) {
// It's too soon after a navigation; ignore the event.
return true;
}
if (acceleration > 0) {
// The events are not (or are no longer) decelerating, so we can start
// paying attention to them again.
this._absorbDeceleration = false;
} else if (this._absorbDeceleration) {
// The wheel event was likely faked to simulate deceleration; ignore it.
return true;
}
this._wheelDistance += deltaX;
// Update the position of the items being navigated.
var width = this.offsetWidth;
var position = width > 0 ?
this._wheelDistance / width :
0;
this.collective.showTransition(false);
position = this._sign(position) * Math.min(Math.abs(position), 1);
this.collective.position = position;
// If the user has dragged enough to reach the previous/next item, then
// complete a navigation to that item.
if (position === 1) {
// console.log("goRight");
this.collective.showTransition(true);
this.collective.goRight();
this._postNavigate();
} else if (position === -1) {
// console.log("goLeft");
this.collective.showTransition(true);
this.collective.goLeft();
this._postNavigate();
}
return true;
},
// Time we wait after the last wheel event before we reset things.
_wheelTime: 100,
// A sufficiently long period of time has passed since the last wheel event.
// We snap the selection to the closest item, then reset our state.
_wheelTimedOut: function() {
// console.log("timeout");
// Snap to the closest item.
this.collective.showTransition(true);
var position = this.collective.position;
if (position >= 0.5) {
// console.log("snap");
this.collective.goRight();
} else if (position <= -0.5) {
// console.log("snap");
this.collective.goLeft();
}
this._resetWheelTracking();
}
});
</script>