index.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. /**
  2. * The component for supporting mouse drag and swipe.
  3. *
  4. * @author Naotoshi Fujita
  5. * @copyright Naotoshi Fujita. All rights reserved.
  6. */
  7. import { LOOP } from '../../constants/types';
  8. import { TTB } from '../../constants/directions';
  9. import { subscribe } from '../../utils/dom';
  10. import { between } from '../../utils/utils';
  11. import { IDLE } from '../../constants/states';
  12. /**
  13. * Adjust how much the track can be pulled on the first or last page.
  14. * The larger number this is, the farther the track moves.
  15. * This should be around 5.
  16. *
  17. * @type {number}
  18. */
  19. const FRICTION_REDUCER = 5;
  20. /**
  21. * To start dragging the track, the drag angle must be less than this threshold.
  22. *
  23. * @type {number}
  24. */
  25. const ANGLE_THRESHOLD = 30;
  26. /**
  27. * When a drag distance is over this value, the action will be treated as "swipe", not "flick".
  28. *
  29. * @type {number}
  30. */
  31. const SWIPE_THRESHOLD = 150;
  32. /**
  33. * The component supporting mouse drag and swipe.
  34. *
  35. * @param {Splide} Splide - A Splide instance.
  36. * @param {Object} Components - An object containing components.
  37. *
  38. * @return {Object} - The component object.
  39. */
  40. export default ( Splide, Components ) => {
  41. /**
  42. * Store the Move component.
  43. *
  44. * @type {Object}
  45. */
  46. const Track = Components.Track;
  47. /**
  48. * Store the Controller component.
  49. *
  50. * @type {Object}
  51. */
  52. const Controller = Components.Controller;
  53. /**
  54. * Coordinate of the track on starting drag.
  55. *
  56. * @type {Object}
  57. */
  58. let startCoord;
  59. /**
  60. * Analyzed info on starting drag.
  61. *
  62. * @type {Object|null}
  63. */
  64. let startInfo;
  65. /**
  66. * Analyzed info being updated while dragging/swiping.
  67. *
  68. * @type {Object}
  69. */
  70. let currentInfo;
  71. /**
  72. * Determine whether slides are being dragged or not.
  73. *
  74. * @type {boolean}
  75. */
  76. let isDragging = false;
  77. /**
  78. * Whether the slider direction is vertical or not.
  79. *
  80. * @type {boolean}
  81. */
  82. const isVertical = Splide.options.direction === TTB;
  83. /**
  84. * Axis for the direction.
  85. *
  86. * @type {string}
  87. */
  88. const axis = isVertical ? 'y' : 'x';
  89. /**
  90. * Drag component object.
  91. *
  92. * @type {Object}
  93. */
  94. const Drag = {
  95. /**
  96. * Mount only when the drag option is true.
  97. *
  98. * @type {boolean}
  99. */
  100. required: Splide.options.drag,
  101. /**
  102. * Whether dragging is disabled or not.
  103. *
  104. * @type {boolean}
  105. */
  106. disabled: false,
  107. /**
  108. * Called when the component is mounted.
  109. */
  110. mount() {
  111. const list = Components.Elements.list;
  112. subscribe( list, 'touchstart mousedown', start );
  113. subscribe( list, 'touchmove mousemove', move, false );
  114. subscribe( list, 'touchend touchcancel mouseleave mouseup dragend', end );
  115. },
  116. };
  117. /**
  118. * Called when the track starts to be dragged.
  119. *
  120. * @param {TouchEvent|MouseEvent} e - TouchEvent or MouseEvent object.
  121. */
  122. function start( e ) {
  123. if ( ! Drag.disabled && ! isDragging && Splide.State.is( IDLE ) ) {
  124. startCoord = Track.toCoord( Track.position );
  125. startInfo = analyze( e, {} );
  126. currentInfo = startInfo;
  127. }
  128. }
  129. /**
  130. * Called while the track being dragged.
  131. *
  132. * @param {TouchEvent|MouseEvent} e - TouchEvent or MouseEvent object.
  133. */
  134. function move( e ) {
  135. if ( startInfo ) {
  136. currentInfo = analyze( e, startInfo );
  137. if ( isDragging ) {
  138. if ( e.cancelable ) {
  139. e.preventDefault();
  140. }
  141. const position = startCoord[ axis ] + currentInfo.offset[ axis ];
  142. Track.translate( resist( position ) );
  143. } else {
  144. if ( shouldMove( currentInfo ) ) {
  145. Splide.emit( 'drag', startInfo );
  146. isDragging = true;
  147. }
  148. }
  149. }
  150. }
  151. /**
  152. * Determine whether to start moving the track or not by drag angle.
  153. *
  154. * @param {Object} info - An information object.
  155. *
  156. * @return {boolean} - True if the track should be moved or false if not.
  157. */
  158. function shouldMove( { offset } ) {
  159. if ( Splide.State.is( IDLE ) ) {
  160. let angle = Math.atan( Math.abs( offset.y ) / Math.abs( offset.x ) ) * 180 / Math.PI;
  161. if ( isVertical ) {
  162. angle = 90 - angle;
  163. }
  164. return angle < ANGLE_THRESHOLD;
  165. }
  166. return false;
  167. }
  168. /**
  169. * Resist dragging the track on the first/last page because there is no more.
  170. *
  171. * @param {number} position - A position being applied to the track.
  172. *
  173. * @return {Object} - Adjusted position.
  174. */
  175. function resist( position ) {
  176. if ( ! Splide.is( LOOP ) ) {
  177. const { trim, toPosition } = Track;
  178. const sign = Controller.isRtl() ? -1 : 1;
  179. const start = sign * trim( toPosition( 0 ) );
  180. const end = sign * trim( toPosition( Controller.edgeIndex ) );
  181. position *= sign;
  182. if ( position > start ) {
  183. position = FRICTION_REDUCER * Math.log( position - start ) + start;
  184. } else if ( position < end ) {
  185. position = -FRICTION_REDUCER * Math.log( end - position ) + end;
  186. }
  187. position *= sign;
  188. }
  189. return position;
  190. }
  191. /**
  192. * Called when dragging ends.
  193. */
  194. function end() {
  195. startInfo = null;
  196. if ( isDragging ) {
  197. Splide.emit( 'dragged', currentInfo );
  198. go( currentInfo );
  199. isDragging = false;
  200. }
  201. }
  202. /**
  203. * Go to the slide determined by the analyzed data.
  204. *
  205. * @param {Object} info - An info object.
  206. */
  207. function go( info ) {
  208. const velocity = info.velocity[ axis ];
  209. const absV = Math.abs( velocity );
  210. if ( absV > 0 ) {
  211. const Layout = Components.Layout;
  212. const options = Splide.options;
  213. const sign = velocity < 0 ? -1 : 1;
  214. let destination = Track.position;
  215. if ( absV > options.flickThreshold && Math.abs( info.offset[ axis ] ) < SWIPE_THRESHOLD ) {
  216. destination += sign * Math.min( absV * options.flickPower, Layout.width * ( options.flickMaxPages || 1 ) );
  217. } else {
  218. // Do not allow the track to go to a previous position.
  219. destination += sign * Layout.slideWidth / 2;
  220. }
  221. let index = Track.toIndex( destination );
  222. if ( ! Splide.is( LOOP ) ) {
  223. index = between( index, 0, Controller.edgeIndex );
  224. }
  225. Controller.go( index, options.isNavigation );
  226. }
  227. }
  228. /**
  229. * Analyze the given event object and return important information for handling swipe behavior.
  230. *
  231. * @param {Event} e - Touch or Mouse event object.
  232. * @param {Object} startInfo - Information analyzed on start for calculating difference from the current one.
  233. *
  234. * @return {Object} - An object containing analyzed information, such as offset, velocity, etc.
  235. */
  236. function analyze( e, startInfo ) {
  237. const { timeStamp, touches } = e;
  238. const { clientX, clientY } = touches ? touches[ 0 ] : e;
  239. const { x: fromX = clientX, y: fromY = clientY } = startInfo.to || {};
  240. const startTime = startInfo.timeStamp || 0;
  241. const offset = { x: clientX - fromX, y: clientY - fromY };
  242. const duration = timeStamp - startTime;
  243. const velocity = { x: offset.x / duration, y: offset.y / duration };
  244. return {
  245. from: { x: fromX, y: fromY },
  246. to : { x: clientX, y: clientY },
  247. offset,
  248. timeStamp,
  249. velocity,
  250. };
  251. }
  252. return Drag;
  253. }