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