UNPKG

14.2 kBJavaScriptView Raw
1/* eslint-disable consistent-return */
2import { getWindow } from 'ssr-window';
3import $ from '../../shared/dom.js';
4import { now, nextTick } from '../../shared/utils.js';
5export default function Mousewheel(_ref) {
6 let {
7 swiper,
8 extendParams,
9 on,
10 emit
11 } = _ref;
12 const window = getWindow();
13 extendParams({
14 mousewheel: {
15 enabled: false,
16 releaseOnEdges: false,
17 invert: false,
18 forceToAxis: false,
19 sensitivity: 1,
20 eventsTarget: 'container',
21 thresholdDelta: null,
22 thresholdTime: null
23 }
24 });
25 swiper.mousewheel = {
26 enabled: false
27 };
28 let timeout;
29 let lastScrollTime = now();
30 let lastEventBeforeSnap;
31 const recentWheelEvents = [];
32
33 function normalize(e) {
34 // Reasonable defaults
35 const PIXEL_STEP = 10;
36 const LINE_HEIGHT = 40;
37 const PAGE_HEIGHT = 800;
38 let sX = 0;
39 let sY = 0; // spinX, spinY
40
41 let pX = 0;
42 let pY = 0; // pixelX, pixelY
43 // Legacy
44
45 if ('detail' in e) {
46 sY = e.detail;
47 }
48
49 if ('wheelDelta' in e) {
50 sY = -e.wheelDelta / 120;
51 }
52
53 if ('wheelDeltaY' in e) {
54 sY = -e.wheelDeltaY / 120;
55 }
56
57 if ('wheelDeltaX' in e) {
58 sX = -e.wheelDeltaX / 120;
59 } // side scrolling on FF with DOMMouseScroll
60
61
62 if ('axis' in e && e.axis === e.HORIZONTAL_AXIS) {
63 sX = sY;
64 sY = 0;
65 }
66
67 pX = sX * PIXEL_STEP;
68 pY = sY * PIXEL_STEP;
69
70 if ('deltaY' in e) {
71 pY = e.deltaY;
72 }
73
74 if ('deltaX' in e) {
75 pX = e.deltaX;
76 }
77
78 if (e.shiftKey && !pX) {
79 // if user scrolls with shift he wants horizontal scroll
80 pX = pY;
81 pY = 0;
82 }
83
84 if ((pX || pY) && e.deltaMode) {
85 if (e.deltaMode === 1) {
86 // delta in LINE units
87 pX *= LINE_HEIGHT;
88 pY *= LINE_HEIGHT;
89 } else {
90 // delta in PAGE units
91 pX *= PAGE_HEIGHT;
92 pY *= PAGE_HEIGHT;
93 }
94 } // Fall-back if spin cannot be determined
95
96
97 if (pX && !sX) {
98 sX = pX < 1 ? -1 : 1;
99 }
100
101 if (pY && !sY) {
102 sY = pY < 1 ? -1 : 1;
103 }
104
105 return {
106 spinX: sX,
107 spinY: sY,
108 pixelX: pX,
109 pixelY: pY
110 };
111 }
112
113 function handleMouseEnter() {
114 if (!swiper.enabled) return;
115 swiper.mouseEntered = true;
116 }
117
118 function handleMouseLeave() {
119 if (!swiper.enabled) return;
120 swiper.mouseEntered = false;
121 }
122
123 function animateSlider(newEvent) {
124 if (swiper.params.mousewheel.thresholdDelta && newEvent.delta < swiper.params.mousewheel.thresholdDelta) {
125 // Prevent if delta of wheel scroll delta is below configured threshold
126 return false;
127 }
128
129 if (swiper.params.mousewheel.thresholdTime && now() - lastScrollTime < swiper.params.mousewheel.thresholdTime) {
130 // Prevent if time between scrolls is below configured threshold
131 return false;
132 } // If the movement is NOT big enough and
133 // if the last time the user scrolled was too close to the current one (avoid continuously triggering the slider):
134 // Don't go any further (avoid insignificant scroll movement).
135
136
137 if (newEvent.delta >= 6 && now() - lastScrollTime < 60) {
138 // Return false as a default
139 return true;
140 } // If user is scrolling towards the end:
141 // If the slider hasn't hit the latest slide or
142 // if the slider is a loop and
143 // if the slider isn't moving right now:
144 // Go to next slide and
145 // emit a scroll event.
146 // Else (the user is scrolling towards the beginning) and
147 // if the slider hasn't hit the first slide or
148 // if the slider is a loop and
149 // if the slider isn't moving right now:
150 // Go to prev slide and
151 // emit a scroll event.
152
153
154 if (newEvent.direction < 0) {
155 if ((!swiper.isEnd || swiper.params.loop) && !swiper.animating) {
156 swiper.slideNext();
157 emit('scroll', newEvent.raw);
158 }
159 } else if ((!swiper.isBeginning || swiper.params.loop) && !swiper.animating) {
160 swiper.slidePrev();
161 emit('scroll', newEvent.raw);
162 } // If you got here is because an animation has been triggered so store the current time
163
164
165 lastScrollTime = new window.Date().getTime(); // Return false as a default
166
167 return false;
168 }
169
170 function releaseScroll(newEvent) {
171 const params = swiper.params.mousewheel;
172
173 if (newEvent.direction < 0) {
174 if (swiper.isEnd && !swiper.params.loop && params.releaseOnEdges) {
175 // Return true to animate scroll on edges
176 return true;
177 }
178 } else if (swiper.isBeginning && !swiper.params.loop && params.releaseOnEdges) {
179 // Return true to animate scroll on edges
180 return true;
181 }
182
183 return false;
184 }
185
186 function handle(event) {
187 let e = event;
188 let disableParentSwiper = true;
189 if (!swiper.enabled) return;
190 const params = swiper.params.mousewheel;
191
192 if (swiper.params.cssMode) {
193 e.preventDefault();
194 }
195
196 let target = swiper.$el;
197
198 if (swiper.params.mousewheel.eventsTarget !== 'container') {
199 target = $(swiper.params.mousewheel.eventsTarget);
200 }
201
202 if (!swiper.mouseEntered && !target[0].contains(e.target) && !params.releaseOnEdges) return true;
203 if (e.originalEvent) e = e.originalEvent; // jquery fix
204
205 let delta = 0;
206 const rtlFactor = swiper.rtlTranslate ? -1 : 1;
207 const data = normalize(e);
208
209 if (params.forceToAxis) {
210 if (swiper.isHorizontal()) {
211 if (Math.abs(data.pixelX) > Math.abs(data.pixelY)) delta = -data.pixelX * rtlFactor;else return true;
212 } else if (Math.abs(data.pixelY) > Math.abs(data.pixelX)) delta = -data.pixelY;else return true;
213 } else {
214 delta = Math.abs(data.pixelX) > Math.abs(data.pixelY) ? -data.pixelX * rtlFactor : -data.pixelY;
215 }
216
217 if (delta === 0) return true;
218 if (params.invert) delta = -delta; // Get the scroll positions
219
220 let positions = swiper.getTranslate() + delta * params.sensitivity;
221 if (positions >= swiper.minTranslate()) positions = swiper.minTranslate();
222 if (positions <= swiper.maxTranslate()) positions = swiper.maxTranslate(); // When loop is true:
223 // the disableParentSwiper will be true.
224 // When loop is false:
225 // if the scroll positions is not on edge,
226 // then the disableParentSwiper will be true.
227 // if the scroll on edge positions,
228 // then the disableParentSwiper will be false.
229
230 disableParentSwiper = swiper.params.loop ? true : !(positions === swiper.minTranslate() || positions === swiper.maxTranslate());
231 if (disableParentSwiper && swiper.params.nested) e.stopPropagation();
232
233 if (!swiper.params.freeMode || !swiper.params.freeMode.enabled) {
234 // Register the new event in a variable which stores the relevant data
235 const newEvent = {
236 time: now(),
237 delta: Math.abs(delta),
238 direction: Math.sign(delta),
239 raw: event
240 }; // Keep the most recent events
241
242 if (recentWheelEvents.length >= 2) {
243 recentWheelEvents.shift(); // only store the last N events
244 }
245
246 const prevEvent = recentWheelEvents.length ? recentWheelEvents[recentWheelEvents.length - 1] : undefined;
247 recentWheelEvents.push(newEvent); // If there is at least one previous recorded event:
248 // If direction has changed or
249 // if the scroll is quicker than the previous one:
250 // Animate the slider.
251 // Else (this is the first time the wheel is moved):
252 // Animate the slider.
253
254 if (prevEvent) {
255 if (newEvent.direction !== prevEvent.direction || newEvent.delta > prevEvent.delta || newEvent.time > prevEvent.time + 150) {
256 animateSlider(newEvent);
257 }
258 } else {
259 animateSlider(newEvent);
260 } // If it's time to release the scroll:
261 // Return now so you don't hit the preventDefault.
262
263
264 if (releaseScroll(newEvent)) {
265 return true;
266 }
267 } else {
268 // Freemode or scrollContainer:
269 // If we recently snapped after a momentum scroll, then ignore wheel events
270 // to give time for the deceleration to finish. Stop ignoring after 500 msecs
271 // or if it's a new scroll (larger delta or inverse sign as last event before
272 // an end-of-momentum snap).
273 const newEvent = {
274 time: now(),
275 delta: Math.abs(delta),
276 direction: Math.sign(delta)
277 };
278 const ignoreWheelEvents = lastEventBeforeSnap && newEvent.time < lastEventBeforeSnap.time + 500 && newEvent.delta <= lastEventBeforeSnap.delta && newEvent.direction === lastEventBeforeSnap.direction;
279
280 if (!ignoreWheelEvents) {
281 lastEventBeforeSnap = undefined;
282
283 if (swiper.params.loop) {
284 swiper.loopFix();
285 }
286
287 let position = swiper.getTranslate() + delta * params.sensitivity;
288 const wasBeginning = swiper.isBeginning;
289 const wasEnd = swiper.isEnd;
290 if (position >= swiper.minTranslate()) position = swiper.minTranslate();
291 if (position <= swiper.maxTranslate()) position = swiper.maxTranslate();
292 swiper.setTransition(0);
293 swiper.setTranslate(position);
294 swiper.updateProgress();
295 swiper.updateActiveIndex();
296 swiper.updateSlidesClasses();
297
298 if (!wasBeginning && swiper.isBeginning || !wasEnd && swiper.isEnd) {
299 swiper.updateSlidesClasses();
300 }
301
302 if (swiper.params.freeMode.sticky) {
303 // When wheel scrolling starts with sticky (aka snap) enabled, then detect
304 // the end of a momentum scroll by storing recent (N=15?) wheel events.
305 // 1. do all N events have decreasing or same (absolute value) delta?
306 // 2. did all N events arrive in the last M (M=500?) msecs?
307 // 3. does the earliest event have an (absolute value) delta that's
308 // at least P (P=1?) larger than the most recent event's delta?
309 // 4. does the latest event have a delta that's smaller than Q (Q=6?) pixels?
310 // If 1-4 are "yes" then we're near the end of a momentum scroll deceleration.
311 // Snap immediately and ignore remaining wheel events in this scroll.
312 // See comment above for "remaining wheel events in this scroll" determination.
313 // If 1-4 aren't satisfied, then wait to snap until 500ms after the last event.
314 clearTimeout(timeout);
315 timeout = undefined;
316
317 if (recentWheelEvents.length >= 15) {
318 recentWheelEvents.shift(); // only store the last N events
319 }
320
321 const prevEvent = recentWheelEvents.length ? recentWheelEvents[recentWheelEvents.length - 1] : undefined;
322 const firstEvent = recentWheelEvents[0];
323 recentWheelEvents.push(newEvent);
324
325 if (prevEvent && (newEvent.delta > prevEvent.delta || newEvent.direction !== prevEvent.direction)) {
326 // Increasing or reverse-sign delta means the user started scrolling again. Clear the wheel event log.
327 recentWheelEvents.splice(0);
328 } else if (recentWheelEvents.length >= 15 && newEvent.time - firstEvent.time < 500 && firstEvent.delta - newEvent.delta >= 1 && newEvent.delta <= 6) {
329 // We're at the end of the deceleration of a momentum scroll, so there's no need
330 // to wait for more events. Snap ASAP on the next tick.
331 // Also, because there's some remaining momentum we'll bias the snap in the
332 // direction of the ongoing scroll because it's better UX for the scroll to snap
333 // in the same direction as the scroll instead of reversing to snap. Therefore,
334 // if it's already scrolled more than 20% in the current direction, keep going.
335 const snapToThreshold = delta > 0 ? 0.8 : 0.2;
336 lastEventBeforeSnap = newEvent;
337 recentWheelEvents.splice(0);
338 timeout = nextTick(() => {
339 swiper.slideToClosest(swiper.params.speed, true, undefined, snapToThreshold);
340 }, 0); // no delay; move on next tick
341 }
342
343 if (!timeout) {
344 // if we get here, then we haven't detected the end of a momentum scroll, so
345 // we'll consider a scroll "complete" when there haven't been any wheel events
346 // for 500ms.
347 timeout = nextTick(() => {
348 const snapToThreshold = 0.5;
349 lastEventBeforeSnap = newEvent;
350 recentWheelEvents.splice(0);
351 swiper.slideToClosest(swiper.params.speed, true, undefined, snapToThreshold);
352 }, 500);
353 }
354 } // Emit event
355
356
357 if (!ignoreWheelEvents) emit('scroll', e); // Stop autoplay
358
359 if (swiper.params.autoplay && swiper.params.autoplayDisableOnInteraction) swiper.autoplay.stop(); // Return page scroll on edge positions
360
361 if (position === swiper.minTranslate() || position === swiper.maxTranslate()) return true;
362 }
363 }
364
365 if (e.preventDefault) e.preventDefault();else e.returnValue = false;
366 return false;
367 }
368
369 function events(method) {
370 let target = swiper.$el;
371
372 if (swiper.params.mousewheel.eventsTarget !== 'container') {
373 target = $(swiper.params.mousewheel.eventsTarget);
374 }
375
376 target[method]('mouseenter', handleMouseEnter);
377 target[method]('mouseleave', handleMouseLeave);
378 target[method]('wheel', handle);
379 }
380
381 function enable() {
382 if (swiper.params.cssMode) {
383 swiper.wrapperEl.removeEventListener('wheel', handle);
384 return true;
385 }
386
387 if (swiper.mousewheel.enabled) return false;
388 events('on');
389 swiper.mousewheel.enabled = true;
390 return true;
391 }
392
393 function disable() {
394 if (swiper.params.cssMode) {
395 swiper.wrapperEl.addEventListener(event, handle);
396 return true;
397 }
398
399 if (!swiper.mousewheel.enabled) return false;
400 events('off');
401 swiper.mousewheel.enabled = false;
402 return true;
403 }
404
405 on('init', () => {
406 if (!swiper.params.mousewheel.enabled && swiper.params.cssMode) {
407 disable();
408 }
409
410 if (swiper.params.mousewheel.enabled) enable();
411 });
412 on('destroy', () => {
413 if (swiper.params.cssMode) {
414 enable();
415 }
416
417 if (swiper.mousewheel.enabled) disable();
418 });
419 Object.assign(swiper.mousewheel, {
420 enable,
421 disable
422 });
423}
\No newline at end of file