未验证 提交 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 is a <s>minimalist</s> JavaScript library for reorderable drag-and-drop lists.
......@@ -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
scrollSensitivity: 30, // px, how near the mouse must be to an edge to start scrolling.
scrollSpeed: 10, // px
bubbleScroll: true, // apply autoscroll to all parent elements, allowing for easier movement
setData: function (/** DataTransfer */dataTransfer, /** HTMLElement*/dragEl) {
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
---
#### `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))
- to:`HTMLElement` — list, in which moved element.
......
......@@ -48,7 +48,11 @@
activeGroup,
putSortable,
autoScroll = {},
autoScrolls = [],
pointerElemChangedInterval,
lastPointerElemX,
lastPointerElemY,
tapEvt,
touchEvt,
......@@ -95,11 +99,30 @@
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) {
// Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521
if (rootEl && options.scroll) {
var _this = rootEl[expando],
el,
if (options.scroll) {
var _this = rootEl ? rootEl[expando] : window,
rect,
sens = options.scrollSensitivity,
speed = options.scrollSpeed,
......@@ -117,73 +140,88 @@
scrollOffsetY
;
// Delect scrollEl
// Detect scrollEl
if (scrollParentEl !== rootEl) {
_clearAutoScrolls();
scrollEl = options.scroll;
scrollParentEl = rootEl;
scrollCustomFn = options.scrollFn;
if (scrollEl === true) {
scrollEl = rootEl;
do {
if ((scrollEl.offsetWidth < scrollEl.scrollWidth) ||
(scrollEl.offsetHeight < scrollEl.scrollHeight)
) {
break;
}
/* jshint boss:true */
} while (scrollEl = scrollEl.parentNode);
scrollEl = _getParentAutoScrollElement(rootEl, true);
scrollParentEl = scrollEl;
}
}
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)) {
vx = (winWidth - x <= sens) - (x <= sens);
vy = (winHeight - y <= sens) - (y <= sens);
if (currentParent) {
el = currentParent;
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) {
autoScroll.el = el;
autoScroll.vx = vx;
autoScroll.vy = vy;
if (!autoScrolls[layersOut]) {
for (var i = 0; i <= layersOut; i++) {
if (!autoScrolls[i]) {
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) {
autoScroll.pid = setInterval(function () {
scrollOffsetY = vy ? vy * speed : 0;
scrollOffsetX = vx ? vx * speed : 0;
clearInterval(autoScrolls[layersOut].pid);
if ('function' === typeof(scrollCustomFn)) {
if (scrollCustomFn.call(_this, scrollOffsetX, scrollOffsetY, evt, touchEvt, el) !== 'continue') {
return;
if (el) {
autoScrolls[layersOut].pid = setInterval((function () {
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) {
win.scrollTo(win.pageXOffset + scrollOffsetX, win.pageYOffset + scrollOffsetY);
} else {
el.scrollTop += scrollOffsetY;
el.scrollLeft += scrollOffsetX;
}
}, 24);
if (autoScrolls[this.layersOut].el === win) {
win.scrollTo(win.pageXOffset + scrollOffsetX, win.pageYOffset + scrollOffsetY);
} else {
autoScrolls[this.layersOut].el.scrollTop += scrollOffsetY;
autoScrolls[this.layersOut].el.scrollLeft += scrollOffsetX;
}
}).bind({layersOut: layersOut}), 24);
}
}
}
layersOut++;
} while (options.bubbleScroll && (currentParent = _getParentAutoScrollElement(currentParent, false)));
}
}, 30),
_clearAutoScrolls = function () {
autoScrolls.forEach(function(autoScroll) {
clearInterval(autoScroll.pid);
});
autoScrolls = [];
},
_prepareGroup = function (options) {
function toFn(value, pull) {
if (value == null || value === false) {
......@@ -260,9 +298,10 @@
disabled: false,
store: null,
handle: null,
scroll: true,
scroll: true,
scrollSensitivity: 30,
scrollSpeed: 10,
bubbleScroll: true,
draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*',
ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen',
......@@ -400,6 +439,51 @@
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) {
var _this = this,
el = _this.el,
......@@ -526,6 +610,7 @@
_dragStarted: function () {
if (rootEl && dragEl) {
_on(document, 'drag', this._handleAutoScroll);
var options = this.options;
// Apply effect
......@@ -687,15 +772,19 @@
if (useFallback === 'touch') {
// Bind touch events
_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, 'touchcancel', _this._onDrop);
if (options.supportPointer) {
_on(document, 'pointermove', _this._handleAutoScroll);
_on(document, 'pointermove', _this._onTouchMove);
_on(document, 'pointerup', _this._onDrop);
}
} else {
// Old brwoser
_on(document, 'mousemove', _this._handleAutoScroll);
_on(document, 'mousemove', _this._onTouchMove);
_on(document, 'mouseup', _this._onDrop);
}
......@@ -760,9 +849,6 @@
) &&
(evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback
) {
// Smart auto-scrolling
_autoScroll(evt, options, this.el);
if (_silent) {
return;
}
......@@ -915,6 +1001,9 @@
_offUpEvents: function () {
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, 'pointermove', this._onTouchMove);
_off(ownerDocument, 'mouseup', this._onDrop);
......@@ -930,7 +1019,11 @@
options = this.options;
clearInterval(this._loopId);
clearInterval(autoScroll.pid);
clearInterval(pointerElemChangedInterval);
_clearAutoScrolls();
_cancelThrottle();
clearTimeout(this._dragStartTimer);
_cancelNextTick(this._cloneId);
......@@ -940,9 +1033,11 @@
_off(document, 'mouseover', this);
_off(document, 'mousemove', this._onTouchMove);
if (this.nativeDraggable) {
_off(document, 'drop', this);
_off(el, 'dragstart', this._onDragStart);
_off(document, 'drag', this._handleAutoScroll);
}
this._offUpEvents();
......@@ -1453,27 +1548,32 @@
return false;
}
var _throttleTimeout;
function _throttle(callback, ms) {
var args, _this;
return function () {
if (args === void 0) {
args = arguments;
_this = this;
if (!_throttleTimeout) {
var args = arguments,
_this = this
;
setTimeout(function () {
_throttleTimeout = setTimeout(function () {
if (args.length === 1) {
callback.call(_this, args[0]);
} else {
callback.apply(_this, args);
}
args = void 0;
_throttleTimeout = void 0;
}, ms);
}
};
}
function _cancelThrottle() {
clearTimeout(_throttleTimeout);
_throttleTimeout = void 0;
}
function _extend(dst, src) {
if (dst && src) {
for (var key in src) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册