1 | import { pointerCoord } from './dom';
|
2 | var ScrollView = (function () {
|
3 | function ScrollView(_app, _plt, _dom) {
|
4 | this._app = _app;
|
5 | this._plt = _plt;
|
6 | this._dom = _dom;
|
7 | this.isScrolling = false;
|
8 | this.initialized = false;
|
9 | this._eventsEnabled = false;
|
10 | this._t = 0;
|
11 | this._l = 0;
|
12 | this.ev = {
|
13 | timeStamp: 0,
|
14 | scrollTop: 0,
|
15 | scrollLeft: 0,
|
16 | scrollHeight: 0,
|
17 | scrollWidth: 0,
|
18 | contentHeight: 0,
|
19 | contentWidth: 0,
|
20 | contentTop: 0,
|
21 | contentBottom: 0,
|
22 | startY: 0,
|
23 | startX: 0,
|
24 | deltaY: 0,
|
25 | deltaX: 0,
|
26 | velocityY: 0,
|
27 | velocityX: 0,
|
28 | directionY: 'down',
|
29 | directionX: null,
|
30 | domWrite: _dom.write.bind(_dom)
|
31 | };
|
32 | }
|
33 | ScrollView.prototype.init = function (ele, contentTop, contentBottom) {
|
34 | (void 0) ;
|
35 | this._el = ele;
|
36 | if (!this.initialized) {
|
37 | this.initialized = true;
|
38 | if (this._js) {
|
39 | this.enableJsScroll(contentTop, contentBottom);
|
40 | }
|
41 | else {
|
42 | this.enableNativeScrolling();
|
43 | }
|
44 | }
|
45 | };
|
46 | ScrollView.prototype.enableEvents = function () {
|
47 | this._eventsEnabled = true;
|
48 | };
|
49 | ScrollView.prototype.setScrolling = function (isScrolling, ev) {
|
50 | if (this.isScrolling) {
|
51 | if (isScrolling) {
|
52 | this.onScroll && this.onScroll(ev);
|
53 | }
|
54 | else {
|
55 | this.isScrolling = false;
|
56 | this.onScrollEnd && this.onScrollEnd(ev);
|
57 | }
|
58 | }
|
59 | else if (isScrolling) {
|
60 | this.isScrolling = true;
|
61 | this.onScrollStart && this.onScrollStart(ev);
|
62 | }
|
63 | };
|
64 | ScrollView.prototype.enableNativeScrolling = function () {
|
65 | (void 0) ;
|
66 | (void 0) ;
|
67 | (void 0) ;
|
68 | this._js = false;
|
69 | if (!this._el) {
|
70 | return;
|
71 | }
|
72 | (void 0) ;
|
73 | var self = this;
|
74 | var ev = self.ev;
|
75 | var positions = [];
|
76 | function scrollCallback(scrollEvent) {
|
77 |
|
78 | self._app.setScrolling();
|
79 |
|
80 | if (!self._eventsEnabled) {
|
81 | return;
|
82 | }
|
83 | ev.timeStamp = scrollEvent.timeStamp;
|
84 |
|
85 | if (!ev.timeStamp) {
|
86 | ev.timeStamp = Date.now();
|
87 | }
|
88 |
|
89 |
|
90 | ev.scrollTop = self.getTop();
|
91 |
|
92 |
|
93 | ev.scrollLeft = self.getLeft();
|
94 | if (!self.isScrolling) {
|
95 |
|
96 | ev.startY = ev.scrollTop;
|
97 | ev.startX = ev.scrollLeft;
|
98 |
|
99 | ev.velocityY = ev.velocityX = 0;
|
100 | ev.deltaY = ev.deltaX = 0;
|
101 | positions.length = 0;
|
102 | }
|
103 |
|
104 | positions.push(ev.scrollTop, ev.scrollLeft, ev.timeStamp);
|
105 | if (positions.length > 3) {
|
106 |
|
107 | ev.deltaY = (ev.scrollTop - ev.startY);
|
108 | ev.deltaX = (ev.scrollLeft - ev.startX);
|
109 | var endPos = (positions.length - 1);
|
110 | var startPos = endPos;
|
111 | var timeRange = (ev.timeStamp - 100);
|
112 |
|
113 | for (var i = endPos; i > 0 && positions[i] > timeRange; i -= 3) {
|
114 | startPos = i;
|
115 | }
|
116 | if (startPos !== endPos) {
|
117 |
|
118 | var movedTop = (positions[startPos - 2] - positions[endPos - 2]);
|
119 | var movedLeft = (positions[startPos - 1] - positions[endPos - 1]);
|
120 | var factor = FRAME_MS / (positions[endPos] - positions[startPos]);
|
121 |
|
122 | ev.velocityY = movedTop * factor;
|
123 | ev.velocityX = movedLeft * factor;
|
124 |
|
125 | ev.directionY = (movedTop > 0 ? 'up' : 'down');
|
126 | ev.directionX = (movedLeft > 0 ? 'left' : 'right');
|
127 | }
|
128 | }
|
129 | function scrollEnd() {
|
130 |
|
131 | ev.velocityY = ev.velocityX = 0;
|
132 |
|
133 | self.setScrolling(false, ev);
|
134 | self._endTmr = null;
|
135 | }
|
136 |
|
137 | self.setScrolling(true, ev);
|
138 |
|
139 | self._dom.cancel(self._endTmr);
|
140 | self._endTmr = self._dom.read(scrollEnd, SCROLL_END_DEBOUNCE_MS);
|
141 | }
|
142 |
|
143 | self._lsn && self._lsn();
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 | self._lsn = self._plt.registerListener(self._el, 'scroll', scrollCallback, EVENT_OPTS);
|
150 | };
|
151 | |
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 | ScrollView.prototype.enableJsScroll = function (contentTop, contentBottom) {
|
161 | var self = this;
|
162 | self._js = true;
|
163 | var ele = self._el;
|
164 | if (!ele) {
|
165 | return;
|
166 | }
|
167 | (void 0) ;
|
168 | var ev = self.ev;
|
169 | var positions = [];
|
170 | var rafCancel;
|
171 | var max;
|
172 | function setMax() {
|
173 | if (!max) {
|
174 |
|
175 | max = ele.scrollHeight - ele.parentElement.offsetHeight + contentTop + contentBottom;
|
176 | }
|
177 | }
|
178 | function jsScrollDecelerate(timeStamp) {
|
179 | ev.timeStamp = timeStamp;
|
180 | (void 0) ;
|
181 | if (ev.velocityY) {
|
182 | ev.velocityY *= DECELERATION_FRICTION;
|
183 |
|
184 |
|
185 |
|
186 | setMax();
|
187 | self._t = Math.min(Math.max(self._t + ev.velocityY, 0), max);
|
188 | ev.scrollTop = self._t;
|
189 |
|
190 | self.onScroll(ev);
|
191 | self._dom.write(function () {
|
192 |
|
193 | self.setTop(self._t);
|
194 | if (self._t > 0 && self._t < max && Math.abs(ev.velocityY) > MIN_VELOCITY_CONTINUE_DECELERATION) {
|
195 | rafCancel = self._dom.read(function (rafTimeStamp) {
|
196 | jsScrollDecelerate(rafTimeStamp);
|
197 | });
|
198 | }
|
199 | else {
|
200 |
|
201 | self.isScrolling = false;
|
202 |
|
203 | ev.velocityY = ev.velocityX = 0;
|
204 |
|
205 | self.onScrollEnd(ev);
|
206 | }
|
207 | });
|
208 | }
|
209 | }
|
210 | function jsScrollTouchStart(touchEvent) {
|
211 | positions.length = 0;
|
212 | max = null;
|
213 | self._dom.cancel(rafCancel);
|
214 | positions.push(pointerCoord(touchEvent).y, touchEvent.timeStamp);
|
215 | }
|
216 | function jsScrollTouchMove(touchEvent) {
|
217 | if (!positions.length) {
|
218 | return;
|
219 | }
|
220 | ev.timeStamp = touchEvent.timeStamp;
|
221 | var y = pointerCoord(touchEvent).y;
|
222 |
|
223 | setMax();
|
224 | self._t -= (y - positions[positions.length - 2]);
|
225 | self._t = Math.min(Math.max(self._t, 0), max);
|
226 | positions.push(y, ev.timeStamp);
|
227 | if (!self.isScrolling) {
|
228 |
|
229 | ev.startY = self._t;
|
230 |
|
231 | ev.velocityY = ev.deltaY = 0;
|
232 | self.isScrolling = true;
|
233 |
|
234 | self.onScrollStart(ev);
|
235 | }
|
236 | self._dom.write(function () {
|
237 |
|
238 | self.setTop(self._t);
|
239 | });
|
240 | }
|
241 | function jsScrollTouchEnd(touchEvent) {
|
242 |
|
243 | self._dom.cancel(rafCancel);
|
244 | if (!positions.length && self.isScrolling) {
|
245 | self.isScrolling = false;
|
246 | ev.velocityY = ev.velocityX = 0;
|
247 | self.onScrollEnd(ev);
|
248 | return;
|
249 | }
|
250 | var y = pointerCoord(touchEvent).y;
|
251 | positions.push(y, touchEvent.timeStamp);
|
252 | var endPos = (positions.length - 1);
|
253 | var startPos = endPos;
|
254 | var timeRange = (touchEvent.timeStamp - 100);
|
255 |
|
256 | for (var i = endPos; i > 0 && positions[i] > timeRange; i -= 2) {
|
257 | startPos = i;
|
258 | }
|
259 | if (startPos !== endPos) {
|
260 |
|
261 | var timeOffset = (positions[endPos] - positions[startPos]);
|
262 | var movedTop = (positions[startPos - 1] - positions[endPos - 1]);
|
263 |
|
264 | ev.velocityY = ((movedTop / timeOffset) * FRAME_MS);
|
265 |
|
266 | if (Math.abs(ev.velocityY) > MIN_VELOCITY_START_DECELERATION) {
|
267 |
|
268 | setMax();
|
269 | rafCancel = self._dom.read(function (rafTimeStamp) {
|
270 | jsScrollDecelerate(rafTimeStamp);
|
271 | });
|
272 | }
|
273 | }
|
274 | else {
|
275 | self.isScrolling = false;
|
276 | ev.velocityY = 0;
|
277 | self.onScrollEnd(ev);
|
278 | }
|
279 | positions.length = 0;
|
280 | }
|
281 | var plt = self._plt;
|
282 | var unRegStart = plt.registerListener(ele, 'touchstart', jsScrollTouchStart, EVENT_OPTS);
|
283 | var unRegMove = plt.registerListener(ele, 'touchmove', jsScrollTouchMove, EVENT_OPTS);
|
284 | var unRegEnd = plt.registerListener(ele, 'touchend', jsScrollTouchEnd, EVENT_OPTS);
|
285 | ele.parentElement.classList.add('js-scroll');
|
286 |
|
287 | self._lsn && self._lsn();
|
288 |
|
289 | self._lsn = function () {
|
290 | unRegStart();
|
291 | unRegMove();
|
292 | unRegEnd();
|
293 | ele.parentElement.classList.remove('js-scroll');
|
294 | };
|
295 | };
|
296 | |
297 |
|
298 |
|
299 | ScrollView.prototype.getTop = function () {
|
300 | if (this._js) {
|
301 | return this._t;
|
302 | }
|
303 | return this._t = this._el.scrollTop;
|
304 | };
|
305 | |
306 |
|
307 |
|
308 | ScrollView.prototype.getLeft = function () {
|
309 | if (this._js) {
|
310 | return 0;
|
311 | }
|
312 | return this._l = this._el.scrollLeft;
|
313 | };
|
314 | |
315 |
|
316 |
|
317 | ScrollView.prototype.setTop = function (top) {
|
318 | this._t = top;
|
319 | if (this._js) {
|
320 | this._el.style[this._plt.Css.transform] = "translate3d(" + this._l * -1 + "px," + top * -1 + "px,0px)";
|
321 | }
|
322 | else {
|
323 | this._el.scrollTop = top;
|
324 | }
|
325 | };
|
326 | |
327 |
|
328 |
|
329 | ScrollView.prototype.setLeft = function (left) {
|
330 | this._l = left;
|
331 | if (this._js) {
|
332 | this._el.style[this._plt.Css.transform] = "translate3d(" + left * -1 + "px," + this._t * -1 + "px,0px)";
|
333 | }
|
334 | else {
|
335 | this._el.scrollLeft = left;
|
336 | }
|
337 | };
|
338 | ScrollView.prototype.scrollTo = function (x, y, duration, done) {
|
339 |
|
340 |
|
341 | var promise;
|
342 | if (done === undefined) {
|
343 |
|
344 |
|
345 | promise = new Promise(function (resolve) {
|
346 | done = resolve;
|
347 | });
|
348 | }
|
349 | var self = this;
|
350 | var el = self._el;
|
351 | if (!el) {
|
352 |
|
353 | done();
|
354 | return promise;
|
355 | }
|
356 | if (duration < 32) {
|
357 | self.setTop(y);
|
358 | self.setLeft(x);
|
359 | done();
|
360 | return promise;
|
361 | }
|
362 | var fromY = el.scrollTop;
|
363 | var fromX = el.scrollLeft;
|
364 | var maxAttempts = (duration / 16) + 100;
|
365 | var transform = self._plt.Css.transform;
|
366 | var startTime;
|
367 | var attempts = 0;
|
368 | var stopScroll = false;
|
369 |
|
370 | function step(timeStamp) {
|
371 | attempts++;
|
372 | if (!self._el || stopScroll || attempts > maxAttempts) {
|
373 | self.setScrolling(false, null);
|
374 | el.style[transform] = '';
|
375 | done();
|
376 | return;
|
377 | }
|
378 | var time = Math.min(1, ((timeStamp - startTime) / duration));
|
379 |
|
380 |
|
381 | var easedT = (--time) * time * time + 1;
|
382 | if (fromY !== y) {
|
383 | self.setTop((easedT * (y - fromY)) + fromY);
|
384 | }
|
385 | if (fromX !== x) {
|
386 | self.setLeft(Math.floor((easedT * (x - fromX)) + fromX));
|
387 | }
|
388 | if (easedT < 1) {
|
389 |
|
390 |
|
391 | self._plt.raf(step);
|
392 | }
|
393 | else {
|
394 | stopScroll = true;
|
395 | self.setScrolling(false, null);
|
396 | el.style[transform] = '';
|
397 | done();
|
398 | }
|
399 | }
|
400 |
|
401 | self.setScrolling(true, null);
|
402 | self.isScrolling = true;
|
403 |
|
404 | self._dom.write(function (timeStamp) {
|
405 | startTime = timeStamp;
|
406 | step(timeStamp);
|
407 | }, 16);
|
408 | return promise;
|
409 | };
|
410 | ScrollView.prototype.scrollToTop = function (duration) {
|
411 | return this.scrollTo(0, 0, duration);
|
412 | };
|
413 | ScrollView.prototype.scrollToBottom = function (duration) {
|
414 | var y = 0;
|
415 | if (this._el) {
|
416 | y = this._el.scrollHeight - this._el.clientHeight;
|
417 | }
|
418 | return this.scrollTo(0, y, duration);
|
419 | };
|
420 | ScrollView.prototype.stop = function () {
|
421 | this.setScrolling(false, null);
|
422 | };
|
423 | |
424 |
|
425 |
|
426 | ScrollView.prototype.destroy = function () {
|
427 | this.stop();
|
428 | this._endTmr && this._dom.cancel(this._endTmr);
|
429 | this._lsn && this._lsn();
|
430 | var ev = this.ev;
|
431 | ev.domWrite = ev.contentElement = ev.fixedElement = ev.scrollElement = ev.headerElement = null;
|
432 | this._lsn = this._el = this._dom = this.ev = ev = null;
|
433 | this.onScrollStart = this.onScroll = this.onScrollEnd = null;
|
434 | };
|
435 | return ScrollView;
|
436 | }());
|
437 | export { ScrollView };
|
438 | var SCROLL_END_DEBOUNCE_MS = 80;
|
439 | var MIN_VELOCITY_START_DECELERATION = 4;
|
440 | var MIN_VELOCITY_CONTINUE_DECELERATION = 0.12;
|
441 | var DECELERATION_FRICTION = 0.97;
|
442 | var FRAME_MS = (1000 / 60);
|
443 | var EVENT_OPTS = {
|
444 | passive: true,
|
445 | zone: false
|
446 | };
|
447 |
|
\ | No newline at end of file |