mousewheel.mjs 15 KB

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