free-mode.js 7.6 KB

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