UNPKG

15.6 kBPlain TextView Raw
1<style>
2 @component-namespace mint {
3 @component swipe {
4 overflow: hidden;
5 position: relative;
6 height: 100%;
7
8 @descendent items-wrap {
9 position: relative;
10 overflow: hidden;
11 height: 100%;
12
13 > div {
14 position: absolute;
15 transform: translateX(-100%);
16 size: 100% 100%;
17 display: none;
18
19 @when active {
20 display: block;
21 transform: none;
22 }
23 }
24 }
25
26 @descendent indicators {
27 position: absolute;
28 bottom: 10px;
29 left: 50%;
30 transform: translateX(-50%);
31 }
32
33 @descendent indicator {
34 size: 8px 8px;
35 display: inline-block;
36 border-radius: 100%;
37 background: #000;
38 opacity: 0.2;
39 margin: 0 3px;
40
41 @when active {
42 background: #fff;
43 }
44 }
45 }
46 }
47</style>
48
49<template>
50 <div class="mint-swipe">
51 <div class="mint-swipe-items-wrap" ref="wrap">
52 <slot></slot>
53 </div>
54 <div class="mint-swipe-indicators" v-show="showIndicators">
55 <div class="mint-swipe-indicator"
56 v-for="(page, $index) in pages"
57 :class="{ 'is-active': $index === index }"></div>
58 </div>
59 </div>
60</template>
61
62<script>
63 import { once } from 'mint-ui/src/utils/dom';
64 import { addClass, removeClass } from 'mint-ui/src/utils/dom';
65
66 export default {
67 name: 'mt-swipe',
68
69 created() {
70 this.dragState = {};
71 },
72
73 data() {
74 return {
75 ready: false,
76 dragging: false,
77 userScrolling: false,
78 animating: false,
79 index: 0,
80 pages: [],
81 timer: null,
82 reInitTimer: null,
83 noDrag: false,
84 isDone: false
85 };
86 },
87
88 props: {
89 speed: {
90 type: Number,
91 default: 300
92 },
93
94 defaultIndex: {
95 type: Number,
96 default: 0
97 },
98
99 auto: {
100 type: Number,
101 default: 3000
102 },
103
104 continuous: {
105 type: Boolean,
106 default: true
107 },
108
109 showIndicators: {
110 type: Boolean,
111 default: true
112 },
113
114 noDragWhenSingle: {
115 type: Boolean,
116 default: true
117 },
118
119 prevent: {
120 type: Boolean,
121 default: false
122 },
123
124 stopPropagation: {
125 type: Boolean,
126 default: false
127 }
128 },
129
130 watch: {
131 index(newIndex) {
132 this.$emit('change', newIndex);
133 }
134 },
135
136 methods: {
137 swipeItemCreated() {
138 if (!this.ready) return;
139
140 clearTimeout(this.reInitTimer);
141 this.reInitTimer = setTimeout(() => {
142 this.reInitPages();
143 }, 100);
144 },
145
146 swipeItemDestroyed() {
147 if (!this.ready) return;
148
149 clearTimeout(this.reInitTimer);
150 this.reInitTimer = setTimeout(() => {
151 this.reInitPages();
152 }, 100);
153 },
154
155 rafTranslate(element, initOffset, offset, callback, nextElement) {
156 let ALPHA = 0.88;
157 this.animating = true;
158 var _offset = initOffset;
159 var raf = 0;
160
161 function animationLoop() {
162 if (Math.abs(_offset - offset) < 0.5) {
163 this.animating = false;
164 _offset = offset;
165 element.style.webkitTransform = '';
166 if (nextElement) {
167 nextElement.style.webkitTransform = '';
168 }
169 cancelAnimationFrame(raf);
170
171 if (callback) {
172 callback();
173 }
174
175 return;
176 }
177
178 _offset = ALPHA * _offset + (1.0 - ALPHA) * offset;
179 element.style.webkitTransform = `translate3d(${_offset}px, 0, 0)`;
180
181 if (nextElement) {
182 nextElement.style.webkitTransform = `translate3d(${_offset - offset}px, 0, 0)`;
183 }
184
185 raf = requestAnimationFrame(animationLoop.bind(this));
186 }
187
188 animationLoop.call(this);
189 },
190
191 translate(element, offset, speed, callback) {
192 if (speed) {
193 this.animating = true;
194 element.style.webkitTransition = '-webkit-transform ' + speed + 'ms ease-in-out';
195 setTimeout(() => {
196 element.style.webkitTransform = `translate3d(${offset}px, 0, 0)`;
197 }, 50);
198
199 var called = false;
200
201 var transitionEndCallback = () => {
202 if (called) return;
203 called = true;
204 this.animating = false;
205 element.style.webkitTransition = '';
206 element.style.webkitTransform = '';
207 if (callback) {
208 callback.apply(this, arguments);
209 }
210 };
211
212 once(element, 'webkitTransitionEnd', transitionEndCallback);
213 setTimeout(transitionEndCallback, speed + 100); // webkitTransitionEnd maybe not fire on lower version android.
214 } else {
215 element.style.webkitTransition = '';
216 element.style.webkitTransform = `translate3d(${offset}px, 0, 0)`;
217 }
218 },
219
220 reInitPages() {
221 var children = this.$children;
222 this.noDrag = children.length === 1 && this.noDragWhenSingle;
223
224 var pages = [];
225 var intDefaultIndex = Math.floor(this.defaultIndex);
226 var defaultIndex = (intDefaultIndex >= 0 && intDefaultIndex < children.length) ? intDefaultIndex : 0;
227 this.index = defaultIndex;
228
229 children.forEach(function(child, index) {
230 pages.push(child.$el);
231
232 removeClass(child.$el, 'is-active');
233
234 if (index === defaultIndex) {
235 addClass(child.$el, 'is-active');
236 }
237 });
238
239 this.pages = pages;
240 },
241
242 doAnimate(towards, options) {
243 if (this.$children.length === 0) return;
244 if (!options && this.$children.length < 2) return;
245
246 var prevPage, nextPage, currentPage, pageWidth, offsetLeft, speedX;
247 var speed = this.speed || 300;
248 var index = this.index;
249 var pages = this.pages;
250 var pageCount = pages.length;
251
252 if (!options) {
253 pageWidth = this.$el.clientWidth;
254 currentPage = pages[index];
255 prevPage = pages[index - 1];
256 nextPage = pages[index + 1];
257 if (this.continuous && pages.length > 1) {
258 if (!prevPage) {
259 prevPage = pages[pages.length - 1];
260 }
261 if (!nextPage) {
262 nextPage = pages[0];
263 }
264 }
265 if (prevPage) {
266 prevPage.style.display = 'block';
267 this.translate(prevPage, -pageWidth);
268 }
269 if (nextPage) {
270 nextPage.style.display = 'block';
271 this.translate(nextPage, pageWidth);
272 }
273 } else {
274 prevPage = options.prevPage;
275 currentPage = options.currentPage;
276 nextPage = options.nextPage;
277 pageWidth = options.pageWidth;
278 offsetLeft = options.offsetLeft;
279 speedX = options.speedX;
280 }
281
282 var newIndex;
283
284 var oldPage = this.$children[index].$el;
285
286 if (towards === 'prev') {
287 if (index > 0) {
288 newIndex = index - 1;
289 }
290 if (this.continuous && index === 0) {
291 newIndex = pageCount - 1;
292 }
293 } else if (towards === 'next') {
294 if (index < pageCount - 1) {
295 newIndex = index + 1;
296 }
297 if (this.continuous && index === pageCount - 1) {
298 newIndex = 0;
299 }
300 }
301
302 var callback = () => {
303 if (newIndex !== undefined) {
304 var newPage = this.$children[newIndex].$el;
305 removeClass(oldPage, 'is-active');
306 addClass(newPage, 'is-active');
307
308 this.index = newIndex;
309 }
310 if (this.isDone) {
311 this.end();
312 }
313
314 if (prevPage) {
315 prevPage.style.display = '';
316 }
317
318 if (nextPage) {
319 nextPage.style.display = '';
320 }
321 };
322
323 setTimeout(() => {
324 if (towards === 'next') {
325 this.isDone = true;
326 this.before(currentPage);
327 if (speedX) {
328 this.rafTranslate(currentPage, offsetLeft, -pageWidth, callback, nextPage);
329 } else {
330 this.translate(currentPage, -pageWidth, speed, callback);
331 if (nextPage) {
332 this.translate(nextPage, 0, speed);
333 }
334 }
335 } else if (towards === 'prev') {
336 this.isDone = true;
337 this.before(currentPage);
338 if (speedX) {
339 this.rafTranslate(currentPage, offsetLeft, pageWidth, callback, prevPage);
340 } else {
341 this.translate(currentPage, pageWidth, speed, callback);
342 if (prevPage) {
343 this.translate(prevPage, 0, speed);
344 }
345 }
346 } else {
347 this.isDone = false;
348 this.translate(currentPage, 0, speed, callback);
349 if (typeof offsetLeft !== 'undefined') {
350 if (prevPage && offsetLeft > 0) {
351 this.translate(prevPage, pageWidth * -1, speed);
352 }
353 if (nextPage && offsetLeft < 0) {
354 this.translate(nextPage, pageWidth, speed);
355 }
356 } else {
357 if (prevPage) {
358 this.translate(prevPage, pageWidth * -1, speed);
359 }
360 if (nextPage) {
361 this.translate(nextPage, pageWidth, speed);
362 }
363 }
364 }
365 }, 10);
366 },
367
368 next() {
369 this.doAnimate('next');
370 },
371
372 prev() {
373 this.doAnimate('prev');
374 },
375
376 before() {
377 this.$emit('before', this.index);
378 },
379
380 end() {
381 this.$emit('end', this.index);
382 },
383
384 doOnTouchStart(event) {
385 if (this.noDrag) return;
386
387 var element = this.$el;
388 var dragState = this.dragState;
389 var touch = event.touches[0];
390
391 dragState.startTime = new Date();
392 dragState.startLeft = touch.pageX;
393 dragState.startTop = touch.pageY;
394 dragState.startTopAbsolute = touch.clientY;
395
396 dragState.pageWidth = element.offsetWidth;
397 dragState.pageHeight = element.offsetHeight;
398
399 var prevPage = this.$children[this.index - 1];
400 var dragPage = this.$children[this.index];
401 var nextPage = this.$children[this.index + 1];
402
403 if (this.continuous && this.pages.length > 1) {
404 if (!prevPage) {
405 prevPage = this.$children[this.$children.length - 1];
406 }
407 if (!nextPage) {
408 nextPage = this.$children[0];
409 }
410 }
411
412 dragState.prevPage = prevPage ? prevPage.$el : null;
413 dragState.dragPage = dragPage ? dragPage.$el : null;
414 dragState.nextPage = nextPage ? nextPage.$el : null;
415
416 if (dragState.prevPage) {
417 dragState.prevPage.style.display = 'block';
418 }
419
420 if (dragState.nextPage) {
421 dragState.nextPage.style.display = 'block';
422 }
423 },
424
425 doOnTouchMove(event) {
426 if (this.noDrag) return;
427
428 var dragState = this.dragState;
429 var touch = event.touches[0];
430
431 dragState.speedX = touch.pageX - dragState.currentLeft;
432 dragState.currentLeft = touch.pageX;
433 dragState.currentTop = touch.pageY;
434 dragState.currentTopAbsolute = touch.clientY;
435
436 var offsetLeft = dragState.currentLeft - dragState.startLeft;
437 var offsetTop = dragState.currentTopAbsolute - dragState.startTopAbsolute;
438
439 var distanceX = Math.abs(offsetLeft);
440 var distanceY = Math.abs(offsetTop);
441 if (distanceX < 5 || (distanceX >= 5 && distanceY >= 1.73 * distanceX)) {
442 this.userScrolling = true;
443 return;
444 } else {
445 this.userScrolling = false;
446 event.preventDefault();
447 }
448 offsetLeft = Math.min(Math.max(-dragState.pageWidth + 1, offsetLeft), dragState.pageWidth - 1);
449
450 var towards = offsetLeft < 0 ? 'next' : 'prev';
451
452 if (dragState.prevPage && towards === 'prev') {
453 this.translate(dragState.prevPage, offsetLeft - dragState.pageWidth);
454 }
455 this.translate(dragState.dragPage, offsetLeft);
456 if (dragState.nextPage && towards === 'next') {
457 this.translate(dragState.nextPage, offsetLeft + dragState.pageWidth);
458 }
459 },
460
461 doOnTouchEnd() {
462 if (this.noDrag) return;
463
464 var dragState = this.dragState;
465
466 var dragDuration = new Date() - dragState.startTime;
467 var towards = null;
468
469 var offsetLeft = dragState.currentLeft - dragState.startLeft;
470 var offsetTop = dragState.currentTop - dragState.startTop;
471 var pageWidth = dragState.pageWidth;
472 var index = this.index;
473 var pageCount = this.pages.length;
474
475 if (dragDuration < 300) {
476 let fireTap = Math.abs(offsetLeft) < 5 && Math.abs(offsetTop) < 5;
477 if (isNaN(offsetLeft) || isNaN(offsetTop)) {
478 fireTap = true;
479 }
480 if (fireTap) {
481 this.$children[this.index].$emit('tap');
482 }
483 }
484
485 if (dragDuration < 300 && dragState.currentLeft === undefined) return;
486
487 if (dragDuration < 300 || Math.abs(offsetLeft) > pageWidth / 2) {
488 towards = offsetLeft < 0 ? 'next' : 'prev';
489 }
490
491 if (!this.continuous) {
492 if ((index === 0 && towards === 'prev') || (index === pageCount - 1 && towards === 'next')) {
493 towards = null;
494 }
495 }
496
497 if (this.$children.length < 2) {
498 towards = null;
499 }
500
501 this.doAnimate(towards, {
502 offsetLeft: offsetLeft,
503 pageWidth: dragState.pageWidth,
504 prevPage: dragState.prevPage,
505 currentPage: dragState.dragPage,
506 nextPage: dragState.nextPage,
507 speedX: dragState.speedX
508 });
509
510 this.dragState = {};
511 },
512
513 initTimer() {
514 if (this.auto > 0 && !this.timer) {
515 this.timer = setInterval(() => {
516 if (!this.continuous && (this.index >= this.pages.length - 1)) {
517 return this.clearTimer();
518 }
519 if (!this.dragging && !this.animating) {
520 this.next();
521 }
522 }, this.auto);
523 }
524 },
525
526 clearTimer() {
527 clearInterval(this.timer);
528 this.timer = null;
529 }
530 },
531
532 destroyed() {
533 if (this.timer) {
534 this.clearTimer();
535 }
536 if (this.reInitTimer) {
537 clearTimeout(this.reInitTimer);
538 this.reInitTimer = null;
539 }
540 },
541
542 mounted() {
543 this.ready = true;
544
545 this.initTimer();
546
547 this.reInitPages();
548
549 var element = this.$el;
550
551 element.addEventListener('touchstart', (event) => {
552 if (this.prevent) event.preventDefault();
553 if (this.stopPropagation) event.stopPropagation();
554 if (this.animating) return;
555 this.dragging = true;
556 this.userScrolling = false;
557 this.doOnTouchStart(event);
558 });
559
560 element.addEventListener('touchmove', (event) => {
561 if (!this.dragging) return;
562 if (this.timer) this.clearTimer();
563 this.doOnTouchMove(event);
564 });
565
566 element.addEventListener('touchend', (event) => {
567 if (this.userScrolling) {
568 this.dragging = false;
569 this.dragState = {};
570 return;
571 }
572 if (!this.dragging) return;
573 this.initTimer();
574 this.doOnTouchEnd(event);
575 this.dragging = false;
576 });
577 }
578 };
579</script>