AutoScroll.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. import {
  2. on,
  3. off,
  4. css,
  5. throttle,
  6. cancelThrottle,
  7. scrollBy,
  8. getParentAutoScrollElement,
  9. expando,
  10. getRect,
  11. getWindowScrollingElement
  12. } from '../../src/utils.js';
  13. import Sortable from '../../src/Sortable.js';
  14. import { Edge, IE11OrLess, Safari } from '../../src/BrowserInfo.js';
  15. let autoScrolls = [],
  16. scrollEl,
  17. scrollRootEl,
  18. scrolling = false,
  19. lastAutoScrollX,
  20. lastAutoScrollY,
  21. touchEvt,
  22. pointerElemChangedInterval;
  23. function AutoScrollPlugin() {
  24. function AutoScroll() {
  25. this.defaults = {
  26. scroll: true,
  27. scrollSensitivity: 30,
  28. scrollSpeed: 10,
  29. bubbleScroll: true
  30. };
  31. // Bind all private methods
  32. for (let fn in this) {
  33. if (fn.charAt(0) === '_' && typeof this[fn] === 'function') {
  34. this[fn] = this[fn].bind(this);
  35. }
  36. }
  37. }
  38. AutoScroll.prototype = {
  39. dragStarted({ originalEvent }) {
  40. if (this.sortable.nativeDraggable) {
  41. on(document, 'dragover', this._handleAutoScroll);
  42. } else {
  43. if (this.options.supportPointer) {
  44. on(document, 'pointermove', this._handleFallbackAutoScroll);
  45. } else if (originalEvent.touches) {
  46. on(document, 'touchmove', this._handleFallbackAutoScroll);
  47. } else {
  48. on(document, 'mousemove', this._handleFallbackAutoScroll);
  49. }
  50. }
  51. },
  52. dragOverCompleted({ originalEvent }) {
  53. // For when bubbling is canceled and using fallback (fallback 'touchmove' always reached)
  54. if (!this.options.dragOverBubble && !originalEvent.rootEl) {
  55. this._handleAutoScroll(originalEvent);
  56. }
  57. },
  58. drop() {
  59. if (this.sortable.nativeDraggable) {
  60. off(document, 'dragover', this._handleAutoScroll);
  61. } else {
  62. off(document, 'pointermove', this._handleFallbackAutoScroll);
  63. off(document, 'touchmove', this._handleFallbackAutoScroll);
  64. off(document, 'mousemove', this._handleFallbackAutoScroll);
  65. }
  66. clearPointerElemChangedInterval();
  67. clearAutoScrolls();
  68. cancelThrottle();
  69. },
  70. nulling() {
  71. touchEvt =
  72. scrollRootEl =
  73. scrollEl =
  74. scrolling =
  75. pointerElemChangedInterval =
  76. lastAutoScrollX =
  77. lastAutoScrollY = null;
  78. autoScrolls.length = 0;
  79. },
  80. _handleFallbackAutoScroll(evt) {
  81. this._handleAutoScroll(evt, true);
  82. },
  83. _handleAutoScroll(evt, fallback) {
  84. const x = (evt.touches ? evt.touches[0] : evt).clientX,
  85. y = (evt.touches ? evt.touches[0] : evt).clientY,
  86. elem = document.elementFromPoint(x, y);
  87. touchEvt = evt;
  88. // IE does not seem to have native autoscroll,
  89. // Edge's autoscroll seems too conditional,
  90. // MACOS Safari does not have autoscroll,
  91. // Firefox and Chrome are good
  92. if (fallback || Edge || IE11OrLess || Safari) {
  93. autoScroll(evt, this.options, elem, fallback);
  94. // Listener for pointer element change
  95. let ogElemScroller = getParentAutoScrollElement(elem, true);
  96. if (
  97. scrolling &&
  98. (
  99. !pointerElemChangedInterval ||
  100. x !== lastAutoScrollX ||
  101. y !== lastAutoScrollY
  102. )
  103. ) {
  104. pointerElemChangedInterval && clearPointerElemChangedInterval();
  105. // Detect for pointer elem change, emulating native DnD behaviour
  106. pointerElemChangedInterval = setInterval(() => {
  107. let newElem = getParentAutoScrollElement(document.elementFromPoint(x, y), true);
  108. if (newElem !== ogElemScroller) {
  109. ogElemScroller = newElem;
  110. clearAutoScrolls();
  111. }
  112. autoScroll(evt, this.options, newElem, fallback);
  113. }, 10);
  114. lastAutoScrollX = x;
  115. lastAutoScrollY = y;
  116. }
  117. } else {
  118. // if DnD is enabled (and browser has good autoscrolling), first autoscroll will already scroll, so get parent autoscroll of first autoscroll
  119. if (!this.options.bubbleScroll || getParentAutoScrollElement(elem, true) === getWindowScrollingElement()) {
  120. clearAutoScrolls();
  121. return;
  122. }
  123. autoScroll(evt, this.options, getParentAutoScrollElement(elem, false), false);
  124. }
  125. }
  126. };
  127. return Object.assign(AutoScroll, {
  128. pluginName: 'scroll',
  129. initializeByDefault: true
  130. });
  131. }
  132. function clearAutoScrolls() {
  133. autoScrolls.forEach(function(autoScroll) {
  134. clearInterval(autoScroll.pid);
  135. });
  136. autoScrolls = [];
  137. }
  138. function clearPointerElemChangedInterval() {
  139. clearInterval(pointerElemChangedInterval);
  140. }
  141. const autoScroll = throttle(function(evt, options, rootEl, isFallback) {
  142. // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521
  143. if (!options.scroll) return;
  144. const x = (evt.touches ? evt.touches[0] : evt).clientX,
  145. y = (evt.touches ? evt.touches[0] : evt).clientY,
  146. sens = options.scrollSensitivity,
  147. speed = options.scrollSpeed,
  148. winScroller = getWindowScrollingElement();
  149. let scrollThisInstance = false,
  150. scrollCustomFn;
  151. // New scroll root, set scrollEl
  152. if (scrollRootEl !== rootEl) {
  153. scrollRootEl = rootEl;
  154. clearAutoScrolls();
  155. scrollEl = options.scroll;
  156. scrollCustomFn = options.scrollFn;
  157. if (scrollEl === true) {
  158. scrollEl = getParentAutoScrollElement(rootEl, true);
  159. }
  160. }
  161. let layersOut = 0;
  162. let currentParent = scrollEl;
  163. do {
  164. let el = currentParent,
  165. rect = getRect(el),
  166. top = rect.top,
  167. bottom = rect.bottom,
  168. left = rect.left,
  169. right = rect.right,
  170. width = rect.width,
  171. height = rect.height,
  172. canScrollX,
  173. canScrollY,
  174. scrollWidth = el.scrollWidth,
  175. scrollHeight = el.scrollHeight,
  176. elCSS = css(el),
  177. scrollPosX = el.scrollLeft,
  178. scrollPosY = el.scrollTop;
  179. if (el === winScroller) {
  180. canScrollX = width < scrollWidth && (elCSS.overflowX === 'auto' || elCSS.overflowX === 'scroll' || elCSS.overflowX === 'visible');
  181. canScrollY = height < scrollHeight && (elCSS.overflowY === 'auto' || elCSS.overflowY === 'scroll' || elCSS.overflowY === 'visible');
  182. } else {
  183. canScrollX = width < scrollWidth && (elCSS.overflowX === 'auto' || elCSS.overflowX === 'scroll');
  184. canScrollY = height < scrollHeight && (elCSS.overflowY === 'auto' || elCSS.overflowY === 'scroll');
  185. }
  186. let vx = canScrollX && (Math.abs(right - x) <= sens && (scrollPosX + width) < scrollWidth) - (Math.abs(left - x) <= sens && !!scrollPosX);
  187. let vy = canScrollY && (Math.abs(bottom - y) <= sens && (scrollPosY + height) < scrollHeight) - (Math.abs(top - y) <= sens && !!scrollPosY);
  188. if (!autoScrolls[layersOut]) {
  189. for (let i = 0; i <= layersOut; i++) {
  190. if (!autoScrolls[i]) {
  191. autoScrolls[i] = {};
  192. }
  193. }
  194. }
  195. if (autoScrolls[layersOut].vx != vx || autoScrolls[layersOut].vy != vy || autoScrolls[layersOut].el !== el) {
  196. autoScrolls[layersOut].el = el;
  197. autoScrolls[layersOut].vx = vx;
  198. autoScrolls[layersOut].vy = vy;
  199. clearInterval(autoScrolls[layersOut].pid);
  200. if (vx != 0 || vy != 0) {
  201. scrollThisInstance = true;
  202. /* jshint loopfunc:true */
  203. autoScrolls[layersOut].pid = setInterval((function () {
  204. // emulate drag over during autoscroll (fallback), emulating native DnD behaviour
  205. if (isFallback && this.layer === 0) {
  206. Sortable.active._onTouchMove(touchEvt); // To move ghost if it is positioned absolutely
  207. }
  208. let scrollOffsetY = autoScrolls[this.layer].vy ? autoScrolls[this.layer].vy * speed : 0;
  209. let scrollOffsetX = autoScrolls[this.layer].vx ? autoScrolls[this.layer].vx * speed : 0;
  210. if (typeof(scrollCustomFn) === 'function') {
  211. if (scrollCustomFn.call(Sortable.dragged.parentNode[expando], scrollOffsetX, scrollOffsetY, evt, touchEvt, autoScrolls[this.layer].el) !== 'continue') {
  212. return;
  213. }
  214. }
  215. scrollBy(autoScrolls[this.layer].el, scrollOffsetX, scrollOffsetY);
  216. }).bind({layer: layersOut}), 24);
  217. }
  218. }
  219. layersOut++;
  220. } while (options.bubbleScroll && currentParent !== winScroller && (currentParent = getParentAutoScrollElement(currentParent, false)));
  221. scrolling = scrollThisInstance; // in case another function catches scrolling as false in between when it is not
  222. }, 30);
  223. export default AutoScrollPlugin;