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);
|
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>
|