UNPKG

8.13 kBJavaScriptView Raw
1import { now } from '../../shared/utils.js';
2export default function freeMode({
3 swiper,
4 extendParams,
5 emit,
6 once
7}) {
8 extendParams({
9 freeMode: {
10 enabled: false,
11 momentum: true,
12 momentumRatio: 1,
13 momentumBounce: true,
14 momentumBounceRatio: 1,
15 momentumVelocityRatio: 1,
16 sticky: false,
17 minimumVelocity: 0.02
18 }
19 });
20
21 function onTouchStart() {
22 const translate = swiper.getTranslate();
23 swiper.setTranslate(translate);
24 swiper.setTransition(0);
25 swiper.touchEventsData.velocities.length = 0;
26 swiper.freeMode.onTouchEnd({
27 currentPos: swiper.rtl ? swiper.translate : -swiper.translate
28 });
29 }
30
31 function onTouchMove() {
32 const {
33 touchEventsData: data,
34 touches
35 } = swiper; // Velocity
36
37 if (data.velocities.length === 0) {
38 data.velocities.push({
39 position: touches[swiper.isHorizontal() ? 'startX' : 'startY'],
40 time: data.touchStartTime
41 });
42 }
43
44 data.velocities.push({
45 position: touches[swiper.isHorizontal() ? 'currentX' : 'currentY'],
46 time: now()
47 });
48 }
49
50 function onTouchEnd({
51 currentPos
52 }) {
53 const {
54 params,
55 $wrapperEl,
56 rtlTranslate: rtl,
57 snapGrid,
58 touchEventsData: data
59 } = swiper; // Time diff
60
61 const touchEndTime = now();
62 const timeDiff = touchEndTime - data.touchStartTime;
63
64 if (currentPos < -swiper.minTranslate()) {
65 swiper.slideTo(swiper.activeIndex);
66 return;
67 }
68
69 if (currentPos > -swiper.maxTranslate()) {
70 if (swiper.slides.length < snapGrid.length) {
71 swiper.slideTo(snapGrid.length - 1);
72 } else {
73 swiper.slideTo(swiper.slides.length - 1);
74 }
75
76 return;
77 }
78
79 if (params.freeMode.momentum) {
80 if (data.velocities.length > 1) {
81 const lastMoveEvent = data.velocities.pop();
82 const velocityEvent = data.velocities.pop();
83 const distance = lastMoveEvent.position - velocityEvent.position;
84 const time = lastMoveEvent.time - velocityEvent.time;
85 swiper.velocity = distance / time;
86 swiper.velocity /= 2;
87
88 if (Math.abs(swiper.velocity) < params.freeMode.minimumVelocity) {
89 swiper.velocity = 0;
90 } // this implies that the user stopped moving a finger then released.
91 // There would be no events with distance zero, so the last event is stale.
92
93
94 if (time > 150 || now() - lastMoveEvent.time > 300) {
95 swiper.velocity = 0;
96 }
97 } else {
98 swiper.velocity = 0;
99 }
100
101 swiper.velocity *= params.freeMode.momentumVelocityRatio;
102 data.velocities.length = 0;
103 let momentumDuration = 1000 * params.freeMode.momentumRatio;
104 const momentumDistance = swiper.velocity * momentumDuration;
105 let newPosition = swiper.translate + momentumDistance;
106 if (rtl) newPosition = -newPosition;
107 let doBounce = false;
108 let afterBouncePosition;
109 const bounceAmount = Math.abs(swiper.velocity) * 20 * params.freeMode.momentumBounceRatio;
110 let needsLoopFix;
111
112 if (newPosition < swiper.maxTranslate()) {
113 if (params.freeMode.momentumBounce) {
114 if (newPosition + swiper.maxTranslate() < -bounceAmount) {
115 newPosition = swiper.maxTranslate() - bounceAmount;
116 }
117
118 afterBouncePosition = swiper.maxTranslate();
119 doBounce = true;
120 data.allowMomentumBounce = true;
121 } else {
122 newPosition = swiper.maxTranslate();
123 }
124
125 if (params.loop && params.centeredSlides) needsLoopFix = true;
126 } else if (newPosition > swiper.minTranslate()) {
127 if (params.freeMode.momentumBounce) {
128 if (newPosition - swiper.minTranslate() > bounceAmount) {
129 newPosition = swiper.minTranslate() + bounceAmount;
130 }
131
132 afterBouncePosition = swiper.minTranslate();
133 doBounce = true;
134 data.allowMomentumBounce = true;
135 } else {
136 newPosition = swiper.minTranslate();
137 }
138
139 if (params.loop && params.centeredSlides) needsLoopFix = true;
140 } else if (params.freeMode.sticky) {
141 let nextSlide;
142
143 for (let j = 0; j < snapGrid.length; j += 1) {
144 if (snapGrid[j] > -newPosition) {
145 nextSlide = j;
146 break;
147 }
148 }
149
150 if (Math.abs(snapGrid[nextSlide] - newPosition) < Math.abs(snapGrid[nextSlide - 1] - newPosition) || swiper.swipeDirection === 'next') {
151 newPosition = snapGrid[nextSlide];
152 } else {
153 newPosition = snapGrid[nextSlide - 1];
154 }
155
156 newPosition = -newPosition;
157 }
158
159 if (needsLoopFix) {
160 once('transitionEnd', () => {
161 swiper.loopFix();
162 });
163 } // Fix duration
164
165
166 if (swiper.velocity !== 0) {
167 if (rtl) {
168 momentumDuration = Math.abs((-newPosition - swiper.translate) / swiper.velocity);
169 } else {
170 momentumDuration = Math.abs((newPosition - swiper.translate) / swiper.velocity);
171 }
172
173 if (params.freeMode.sticky) {
174 // If freeMode.sticky is active and the user ends a swipe with a slow-velocity
175 // event, then durations can be 20+ seconds to slide one (or zero!) slides.
176 // It's easy to see this when simulating touch with mouse events. To fix this,
177 // limit single-slide swipes to the default slide duration. This also has the
178 // nice side effect of matching slide speed if the user stopped moving before
179 // lifting finger or mouse vs. moving slowly before lifting the finger/mouse.
180 // For faster swipes, also apply limits (albeit higher ones).
181 const moveDistance = Math.abs((rtl ? -newPosition : newPosition) - swiper.translate);
182 const currentSlideSize = swiper.slidesSizesGrid[swiper.activeIndex];
183
184 if (moveDistance < currentSlideSize) {
185 momentumDuration = params.speed;
186 } else if (moveDistance < 2 * currentSlideSize) {
187 momentumDuration = params.speed * 1.5;
188 } else {
189 momentumDuration = params.speed * 2.5;
190 }
191 }
192 } else if (params.freeMode.sticky) {
193 swiper.slideToClosest();
194 return;
195 }
196
197 if (params.freeMode.momentumBounce && doBounce) {
198 swiper.updateProgress(afterBouncePosition);
199 swiper.setTransition(momentumDuration);
200 swiper.setTranslate(newPosition);
201 swiper.transitionStart(true, swiper.swipeDirection);
202 swiper.animating = true;
203 $wrapperEl.transitionEnd(() => {
204 if (!swiper || swiper.destroyed || !data.allowMomentumBounce) return;
205 emit('momentumBounce');
206 swiper.setTransition(params.speed);
207 setTimeout(() => {
208 swiper.setTranslate(afterBouncePosition);
209 $wrapperEl.transitionEnd(() => {
210 if (!swiper || swiper.destroyed) return;
211 swiper.transitionEnd();
212 });
213 }, 0);
214 });
215 } else if (swiper.velocity) {
216 emit('_freeModeNoMomentumRelease');
217 swiper.updateProgress(newPosition);
218 swiper.setTransition(momentumDuration);
219 swiper.setTranslate(newPosition);
220 swiper.transitionStart(true, swiper.swipeDirection);
221
222 if (!swiper.animating) {
223 swiper.animating = true;
224 $wrapperEl.transitionEnd(() => {
225 if (!swiper || swiper.destroyed) return;
226 swiper.transitionEnd();
227 });
228 }
229 } else {
230 swiper.updateProgress(newPosition);
231 }
232
233 swiper.updateActiveIndex();
234 swiper.updateSlidesClasses();
235 } else if (params.freeMode.sticky) {
236 swiper.slideToClosest();
237 return;
238 } else if (params.freeMode) {
239 emit('_freeModeNoMomentumRelease');
240 }
241
242 if (!params.freeMode.momentum || timeDiff >= params.longSwipesMs) {
243 swiper.updateProgress();
244 swiper.updateActiveIndex();
245 swiper.updateSlidesClasses();
246 }
247 }
248
249 Object.assign(swiper, {
250 freeMode: {
251 onTouchStart,
252 onTouchMove,
253 onTouchEnd
254 }
255 });
256}
\No newline at end of file