未验证 提交 ead51479 编写于 作者: O owen-m1 提交者: GitHub

Merge pull request #1386 from RubaXa/autoscroll-revamp

Autoscroll revamp
## Hi, this project very much requires the maintainer, if you have a chewing and appropriate skills, please [contact me](mailto:ibn@rubaxa.org?subject=Sortable%20vs.%20Maintainer)!
---
# Sortable # Sortable
Sortable is a <s>minimalist</s> JavaScript library for reorderable drag-and-drop lists. Sortable is a <s>minimalist</s> JavaScript library for reorderable drag-and-drop lists.
...@@ -109,6 +105,7 @@ var sortable = new Sortable(el, { ...@@ -109,6 +105,7 @@ var sortable = new Sortable(el, {
scrollFn: function(offsetX, offsetY, originalEvent, touchEvt, hoverTargetEl) { ... }, // if you have custom scrollbar scrollFn may be used for autoscrolling scrollFn: function(offsetX, offsetY, originalEvent, touchEvt, hoverTargetEl) { ... }, // if you have custom scrollbar scrollFn may be used for autoscrolling
scrollSensitivity: 30, // px, how near the mouse must be to an edge to start scrolling. scrollSensitivity: 30, // px, how near the mouse must be to an edge to start scrolling.
scrollSpeed: 10, // px scrollSpeed: 10, // px
bubbleScroll: true, // apply autoscroll to all parent elements, allowing for easier movement
setData: function (/** DataTransfer */dataTransfer, /** HTMLElement*/dragEl) { setData: function (/** DataTransfer */dataTransfer, /** HTMLElement*/dragEl) {
dataTransfer.setData('Text', dragEl.textContent); // `dataTransfer` object of HTML5 DragEvent dataTransfer.setData('Text', dragEl.textContent); // `dataTransfer` object of HTML5 DragEvent
...@@ -408,6 +405,14 @@ The speed at which the window should scroll once the mouse pointer gets within t ...@@ -408,6 +405,14 @@ The speed at which the window should scroll once the mouse pointer gets within t
--- ---
#### `bubbleScroll` option
If set to `true`, the normal `autoscroll` function will also be applied to all parent elements of the element the user is dragging over.
Demo: https://jsbin.com/kesewor/edit?html,js,output
---
### Event object ([demo](http://jsbin.com/xedusu/edit?js,output)) ### Event object ([demo](http://jsbin.com/xedusu/edit?js,output))
- to:`HTMLElement` — list, in which moved element. - to:`HTMLElement` — list, in which moved element.
......
...@@ -48,7 +48,11 @@ ...@@ -48,7 +48,11 @@
activeGroup, activeGroup,
putSortable, putSortable,
autoScroll = {}, autoScrolls = [],
pointerElemChangedInterval,
lastPointerElemX,
lastPointerElemY,
tapEvt, tapEvt,
touchEvt, touchEvt,
...@@ -95,11 +99,30 @@ ...@@ -95,11 +99,30 @@
alwaysFalse = function () { return false; }, alwaysFalse = function () { return false; },
_getParentAutoScrollElement = function(rootEl, includeSelf) {
// will skip to window in _autoScroll
if (!rootEl || !rootEl.getBoundingClientRect) return;
var elem = rootEl;
var gotSelf = false;
do {
if (
(elem.clientWidth < elem.scrollWidth) ||
(elem.clientHeight < elem.scrollHeight)
) {
if (!elem || !elem.getBoundingClientRect || elem === document.body) return;
if (gotSelf || includeSelf) return elem;
gotSelf = true;
}
} while (elem = elem.parentNode);
},
_autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) { _autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) {
// Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521 // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521
if (rootEl && options.scroll) { if (options.scroll) {
var _this = rootEl[expando], var _this = rootEl ? rootEl[expando] : window,
el,
rect, rect,
sens = options.scrollSensitivity, sens = options.scrollSensitivity,
speed = options.scrollSpeed, speed = options.scrollSpeed,
...@@ -117,73 +140,88 @@ ...@@ -117,73 +140,88 @@
scrollOffsetY scrollOffsetY
; ;
// Delect scrollEl // Detect scrollEl
if (scrollParentEl !== rootEl) { if (scrollParentEl !== rootEl) {
_clearAutoScrolls();
scrollEl = options.scroll; scrollEl = options.scroll;
scrollParentEl = rootEl;
scrollCustomFn = options.scrollFn; scrollCustomFn = options.scrollFn;
if (scrollEl === true) { if (scrollEl === true) {
scrollEl = rootEl; scrollEl = _getParentAutoScrollElement(rootEl, true);
scrollParentEl = scrollEl;
do {
if ((scrollEl.offsetWidth < scrollEl.scrollWidth) ||
(scrollEl.offsetHeight < scrollEl.scrollHeight)
) {
break;
}
/* jshint boss:true */
} while (scrollEl = scrollEl.parentNode);
} }
} }
if (scrollEl) {
el = scrollEl;
rect = scrollEl.getBoundingClientRect();
vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens);
vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens);
}
var layersOut = 0;
var currentParent = scrollEl;
do {
var el;
if (!(vx || vy)) { if (currentParent) {
vx = (winWidth - x <= sens) - (x <= sens); el = currentParent;
vy = (winHeight - y <= sens) - (y <= sens); rect = currentParent.getBoundingClientRect();
vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens);
vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens);
}
/* jshint expr:true */
(vx || vy) && (el = win);
}
if (!(vx || vy)) {
vx = (winWidth - x <= sens) - (x <= sens);
vy = (winHeight - y <= sens) - (y <= sens);
/* jshint expr:true */
(vx || vy) && (el = win);
}
if (autoScroll.vx !== vx || autoScroll.vy !== vy || autoScroll.el !== el) { if (!autoScrolls[layersOut]) {
autoScroll.el = el; for (var i = 0; i <= layersOut; i++) {
autoScroll.vx = vx; if (!autoScrolls[i]) {
autoScroll.vy = vy; autoScrolls[i] = {};
}
}
}
clearInterval(autoScroll.pid); if (autoScrolls[layersOut].vx !== vx || autoScrolls[layersOut].vy !== vy || autoScrolls[layersOut].el !== el) {
autoScrolls[layersOut].el = el;
autoScrolls[layersOut].vx = vx;
autoScrolls[layersOut].vy = vy;
if (el) { clearInterval(autoScrolls[layersOut].pid);
autoScroll.pid = setInterval(function () {
scrollOffsetY = vy ? vy * speed : 0;
scrollOffsetX = vx ? vx * speed : 0;
if ('function' === typeof(scrollCustomFn)) { if (el) {
if (scrollCustomFn.call(_this, scrollOffsetX, scrollOffsetY, evt, touchEvt, el) !== 'continue') { autoScrolls[layersOut].pid = setInterval((function () {
return; scrollOffsetY = autoScrolls[this.layersOut].vy ? autoScrolls[this.layersOut].vy * speed : 0;
scrollOffsetX = autoScrolls[this.layersOut].vx ? autoScrolls[this.layersOut].vx * speed : 0;
if ('function' === typeof(scrollCustomFn)) {
if (scrollCustomFn.call(_this, scrollOffsetX, scrollOffsetY, evt, touchEvt, autoScrolls[this.layersOut].el) !== 'continue') {
return;
}
} }
}
if (el === win) { if (autoScrolls[this.layersOut].el === win) {
win.scrollTo(win.pageXOffset + scrollOffsetX, win.pageYOffset + scrollOffsetY); win.scrollTo(win.pageXOffset + scrollOffsetX, win.pageYOffset + scrollOffsetY);
} else { } else {
el.scrollTop += scrollOffsetY; autoScrolls[this.layersOut].el.scrollTop += scrollOffsetY;
el.scrollLeft += scrollOffsetX; autoScrolls[this.layersOut].el.scrollLeft += scrollOffsetX;
} }
}, 24); }).bind({layersOut: layersOut}), 24);
}
} }
} layersOut++;
} while (options.bubbleScroll && (currentParent = _getParentAutoScrollElement(currentParent, false)));
} }
}, 30), }, 30),
_clearAutoScrolls = function () {
autoScrolls.forEach(function(autoScroll) {
clearInterval(autoScroll.pid);
});
autoScrolls = [];
},
_prepareGroup = function (options) { _prepareGroup = function (options) {
function toFn(value, pull) { function toFn(value, pull) {
if (value == null || value === false) { if (value == null || value === false) {
...@@ -260,9 +298,10 @@ ...@@ -260,9 +298,10 @@
disabled: false, disabled: false,
store: null, store: null,
handle: null, handle: null,
scroll: true, scroll: true,
scrollSensitivity: 30, scrollSensitivity: 30,
scrollSpeed: 10, scrollSpeed: 10,
bubbleScroll: true,
draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*', draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*',
ghostClass: 'sortable-ghost', ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen', chosenClass: 'sortable-chosen',
...@@ -400,6 +439,51 @@ ...@@ -400,6 +439,51 @@
this._prepareDragStart(evt, touch, target, startIndex); this._prepareDragStart(evt, touch, target, startIndex);
}, },
_handleAutoScroll: function(evt) {
if (!dragEl || !this.options.scroll || (this.options.supportPointer && evt.type == 'touchmove')) return;
var
x = (evt.touches ? evt.touches[0] : evt).clientX,
y = (evt.touches ? evt.touches[0] : evt).clientY,
elem = document.elementFromPoint(x, y),
_this = this
;
// touch does not have native autoscroll, even with DnD enabled
if (!_this.nativeDraggable || evt.touches || (evt.pointerType && evt.pointerType == 'touch')) {
_autoScroll(evt.touches ? evt.touches[0] : evt, _this.options, elem);
// Listener for pointer element change
var ogElemScroller = _getParentAutoScrollElement(elem, true);
if (!pointerElemChangedInterval ||
x != lastPointerElemX ||
y != lastPointerElemY) {
pointerElemChangedInterval && clearInterval(pointerElemChangedInterval);
// Detect for pointer elem change, emulating native DnD behaviour
pointerElemChangedInterval = setInterval(function() {
if (!dragEl) return;
var newElem = _getParentAutoScrollElement(document.elementFromPoint(x, y), true);
if (newElem != ogElemScroller) {
ogElemScroller = newElem;
_clearAutoScrolls();
_autoScroll(evt.touches ? evt.touches[0] : evt, _this.options, ogElemScroller);
}
}, 10);
lastPointerElemX = x;
lastPointerElemY = y;
}
} else {
// if DnD is enabled, first autoscroll will already scroll, so get parent autoscroll of first autoscroll
if (!_this.options.bubbleScroll) return;
_autoScroll(evt, _this.options, _getParentAutoScrollElement(elem, false));
}
},
_prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target, /** Number */startIndex) { _prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target, /** Number */startIndex) {
var _this = this, var _this = this,
el = _this.el, el = _this.el,
...@@ -526,6 +610,7 @@ ...@@ -526,6 +610,7 @@
_dragStarted: function () { _dragStarted: function () {
if (rootEl && dragEl) { if (rootEl && dragEl) {
_on(document, 'drag', this._handleAutoScroll);
var options = this.options; var options = this.options;
// Apply effect // Apply effect
...@@ -687,15 +772,19 @@ ...@@ -687,15 +772,19 @@
if (useFallback === 'touch') { if (useFallback === 'touch') {
// Bind touch events // Bind touch events
_on(document, 'touchmove', _this._onTouchMove); _on(document, 'touchmove', _this._onTouchMove);
// onTouchMove before handleAutoScroll in this case, because onTouchMove sets touchEvt
_on(document, 'touchmove', _this._handleAutoScroll);
_on(document, 'touchend', _this._onDrop); _on(document, 'touchend', _this._onDrop);
_on(document, 'touchcancel', _this._onDrop); _on(document, 'touchcancel', _this._onDrop);
if (options.supportPointer) { if (options.supportPointer) {
_on(document, 'pointermove', _this._handleAutoScroll);
_on(document, 'pointermove', _this._onTouchMove); _on(document, 'pointermove', _this._onTouchMove);
_on(document, 'pointerup', _this._onDrop); _on(document, 'pointerup', _this._onDrop);
} }
} else { } else {
// Old brwoser // Old brwoser
_on(document, 'mousemove', _this._handleAutoScroll);
_on(document, 'mousemove', _this._onTouchMove); _on(document, 'mousemove', _this._onTouchMove);
_on(document, 'mouseup', _this._onDrop); _on(document, 'mouseup', _this._onDrop);
} }
...@@ -760,9 +849,6 @@ ...@@ -760,9 +849,6 @@
) && ) &&
(evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback (evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback
) { ) {
// Smart auto-scrolling
_autoScroll(evt, options, this.el);
if (_silent) { if (_silent) {
return; return;
} }
...@@ -915,6 +1001,9 @@ ...@@ -915,6 +1001,9 @@
_offUpEvents: function () { _offUpEvents: function () {
var ownerDocument = this.el.ownerDocument; var ownerDocument = this.el.ownerDocument;
_off(document, 'touchmove', this._handleAutoScroll);
_off(document, 'pointermove', this._handleAutoScroll);
_off(document, 'mousemove', this._handleAutoScroll);
_off(document, 'touchmove', this._onTouchMove); _off(document, 'touchmove', this._onTouchMove);
_off(document, 'pointermove', this._onTouchMove); _off(document, 'pointermove', this._onTouchMove);
_off(ownerDocument, 'mouseup', this._onDrop); _off(ownerDocument, 'mouseup', this._onDrop);
...@@ -930,7 +1019,11 @@ ...@@ -930,7 +1019,11 @@
options = this.options; options = this.options;
clearInterval(this._loopId); clearInterval(this._loopId);
clearInterval(autoScroll.pid);
clearInterval(pointerElemChangedInterval);
_clearAutoScrolls();
_cancelThrottle();
clearTimeout(this._dragStartTimer); clearTimeout(this._dragStartTimer);
_cancelNextTick(this._cloneId); _cancelNextTick(this._cloneId);
...@@ -940,9 +1033,11 @@ ...@@ -940,9 +1033,11 @@
_off(document, 'mouseover', this); _off(document, 'mouseover', this);
_off(document, 'mousemove', this._onTouchMove); _off(document, 'mousemove', this._onTouchMove);
if (this.nativeDraggable) { if (this.nativeDraggable) {
_off(document, 'drop', this); _off(document, 'drop', this);
_off(el, 'dragstart', this._onDragStart); _off(el, 'dragstart', this._onDragStart);
_off(document, 'drag', this._handleAutoScroll);
} }
this._offUpEvents(); this._offUpEvents();
...@@ -1453,27 +1548,32 @@ ...@@ -1453,27 +1548,32 @@
return false; return false;
} }
var _throttleTimeout;
function _throttle(callback, ms) { function _throttle(callback, ms) {
var args, _this;
return function () { return function () {
if (args === void 0) { if (!_throttleTimeout) {
args = arguments; var args = arguments,
_this = this; _this = this
;
setTimeout(function () { _throttleTimeout = setTimeout(function () {
if (args.length === 1) { if (args.length === 1) {
callback.call(_this, args[0]); callback.call(_this, args[0]);
} else { } else {
callback.apply(_this, args); callback.apply(_this, args);
} }
args = void 0; _throttleTimeout = void 0;
}, ms); }, ms);
} }
}; };
} }
function _cancelThrottle() {
clearTimeout(_throttleTimeout);
_throttleTimeout = void 0;
}
function _extend(dst, src) { function _extend(dst, src) {
if (dst && src) { if (dst && src) {
for (var key in src) { for (var key in src) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册