From e6b496d5889e4023d4d4a410e0cbca3ee27a334c Mon Sep 17 00:00:00 2001 From: Owen Mills Date: Sat, 9 Mar 2019 18:50:41 -0500 Subject: [PATCH] #1448: Position ghost absolutely on IOS --- Sortable.js | 103 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 87 insertions(+), 16 deletions(-) diff --git a/Sortable.js b/Sortable.js index 7ab12e480..ab76170f7 100644 --- a/Sortable.js +++ b/Sortable.js @@ -70,6 +70,10 @@ targetMoveDistance, + // For positioning ghost absolutely + ghostRelativeParent, + ghostRelativeParentInitialScroll = [], // (left, top) + forRepaintDummy, realDragElRect, // dragEl rect after current animation @@ -96,6 +100,9 @@ Edge = !!navigator.userAgent.match(/Edge/i), FireFox = !!navigator.userAgent.match(/firefox/i), Safari = !!(navigator.userAgent.match(/safari/i) && !navigator.userAgent.match(/chrome/i) && !navigator.userAgent.match(/android/i)), + IOS = !!(navigator.userAgent.match(/iP(ad|od|hone)/i)), + + PositionGhostAbsolutely = IOS, CSSFloatProperty = Edge || IE11OrLess ? 'cssFloat' : 'float', @@ -353,6 +360,7 @@ // emulate drag over during autoscroll (fallback), emulating native DnD behaviour if (isFallback && this.layer === 0) { Sortable.active._emulateDragOver(true); + Sortable.active._onTouchMove(touchEvt, true); } var scrollOffsetY = autoScrolls[this.layer].vy ? autoScrolls[this.layer].vy * speed : 0; var scrollOffsetX = autoScrolls[this.layer].vx ? autoScrolls[this.layer].vx * speed : 0; @@ -948,9 +956,9 @@ } }, - _emulateDragOver: function (bypassLastTouchCheck) { + _emulateDragOver: function (forAutoScroll) { if (touchEvt) { - if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY && !bypassLastTouchCheck) { + if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY && !forAutoScroll) { return; } this._lastX = touchEvt.clientX; @@ -995,7 +1003,7 @@ }, - _onTouchMove: function (/**TouchEvent*/evt) { + _onTouchMove: function (/**TouchEvent*/evt, forAutoScroll) { if (tapEvt) { var options = this.options, fallbackTolerance = options.fallbackTolerance, @@ -1004,11 +1012,15 @@ matrix = ghostEl && _matrix(ghostEl), scaleX = ghostEl && matrix && matrix.a, scaleY = ghostEl && matrix && matrix.d, - dx = ((touch.clientX - tapEvt.clientX) + fallbackOffset.x) / (scaleX ? scaleX : 1), - dy = ((touch.clientY - tapEvt.clientY) + fallbackOffset.y) / (scaleY ? scaleY : 1), + relativeScrollOffset = PositionGhostAbsolutely && ghostRelativeParent && _getRelativeScrollOffset(ghostRelativeParent), + dx = ((touch.clientX - tapEvt.clientX) + + fallbackOffset.x) / (scaleX || 1) + + (relativeScrollOffset ? (relativeScrollOffset[0] - ghostRelativeParentInitialScroll[0]) : 0) / (scaleX || 1), + dy = ((touch.clientY - tapEvt.clientY) + + fallbackOffset.y) / (scaleY || 1) + + (relativeScrollOffset ? (relativeScrollOffset[1] - ghostRelativeParentInitialScroll[1]) : 0) / (scaleY || 1), translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)'; - // only set the status to dragging, when we are actually dragging if (!Sortable.active && !awaitingDragStarted) { if (fallbackTolerance && @@ -1019,13 +1031,11 @@ this._onDragStart(evt, true); } - this._handleAutoScroll(touch, true); - + !forAutoScroll && this._handleAutoScroll(touch, true); moved = true; touchEvt = touch; - _css(ghostEl, 'webkitTransform', translate3d); _css(ghostEl, 'mozTransform', translate3d); _css(ghostEl, 'msTransform', translate3d); @@ -1036,11 +1046,46 @@ }, _appendGhost: function () { + // Bug if using scale(): https://stackoverflow.com/questions/2637058 + // Not being adjusted for if (!ghostEl) { - var rect = _getRect(dragEl, this.options.fallbackOnBody ? document.body : rootEl, true), + var container = this.options.fallbackOnBody ? document.body : rootEl, + rect = _getRect(dragEl, true, container, !PositionGhostAbsolutely), css = _css(dragEl), options = this.options; + // Position absolutely + if (PositionGhostAbsolutely) { + // Get relatively positioned parent + ghostRelativeParent = container; + + while ( + _css(ghostRelativeParent, 'position') === 'static' && + _css(ghostRelativeParent, 'transform') === 'none' && + ghostRelativeParent !== document + ) { + ghostRelativeParent = ghostRelativeParent.parentNode; + } + + if (ghostRelativeParent !== document) { + var ghostRelativeParentRect = _getRect(ghostRelativeParent, true); + + rect.top -= ghostRelativeParentRect.top; + rect.left -= ghostRelativeParentRect.left; + } + + if (ghostRelativeParent !== document.body && ghostRelativeParent !== document.documentElement) { + if (ghostRelativeParent === document) ghostRelativeParent = _getWindowScrollingElement(); + + rect.top += ghostRelativeParent.scrollTop; + rect.left += ghostRelativeParent.scrollLeft; + } else { + ghostRelativeParent = _getWindowScrollingElement(); + } + ghostRelativeParentInitialScroll = _getRelativeScrollOffset(ghostRelativeParent); + } + + ghostEl = dragEl.cloneNode(true); _toggleClass(ghostEl, options.ghostClass, false); @@ -1054,11 +1099,11 @@ _css(ghostEl, 'width', rect.width); _css(ghostEl, 'height', rect.height); _css(ghostEl, 'opacity', '0.8'); - _css(ghostEl, 'position', 'fixed'); + _css(ghostEl, 'position', (PositionGhostAbsolutely ? 'absolute' : 'fixed')); _css(ghostEl, 'zIndex', '100000'); _css(ghostEl, 'pointerEvents', 'none'); - options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl); + container.appendChild(ghostEl); } }, @@ -2245,7 +2290,7 @@ * @param {[Boolean]} adjustForTransform Whether the rect should compensate for parent's transform * @return {Object} The boundingClientRect of el */ - function _getRect(el, container, adjustForTransform) { + function _getRect(el, adjustForTransform, container, adjustForFixed) { if (!el.getBoundingClientRect && el !== win) return; var elRect, @@ -2273,7 +2318,7 @@ width = window.innerWidth; } - if (adjustForTransform && el !== win) { + if (adjustForFixed && el !== win) { // Adjust for translate() container = container || el.parentNode; @@ -2295,9 +2340,11 @@ /* jshint boss:true */ } while (container = container.parentNode); } + } + if (adjustForTransform && el !== win) { // Adjust for scale() - var matrix = _matrix(el), + var matrix = _matrix(container || el), scaleX = matrix && matrix.a, scaleY = matrix && matrix.d; @@ -2328,7 +2375,6 @@ * Checks if a side of an element is scrolled past a side of it's parents * @param {HTMLElement} el The element who's side being scrolled out of view is in question * @param {String} side Side of the element in question ('top', 'left', 'right', 'bottom') - * @return {int} Amount the element is overflowing over the specified side of it's parent scroll element * @return {HTMLElement} The parent scroll element that the el's side is scrolled past, or null if there is no such element */ function _isScrolledPast(el, side) { @@ -2356,6 +2402,31 @@ return false; } + /** + * Returns the scroll offset of the given element, added with all the scroll offsets of parent elements. + * The value is returned in real pixels. + * @param {HTMLElement} el + * @return {Array} Offsets in the format of [left, top] + */ + function _getRelativeScrollOffset(el) { + var offsetLeft = 0, + offsetTop = 0, + winScroller = _getWindowScrollingElement(); + + if (el) { + do { + var matrix = _matrix(el), + scaleX = matrix.a, + scaleY = matrix.d; + + offsetLeft += el.scrollLeft * scaleX; + offsetTop += el.scrollTop * scaleY; + } while (el !== winScroller && (el = el.parentNode)); + } + + return [offsetLeft, offsetTop]; + } + // Fixed #973: _on(document, 'touchmove', function(evt) { if ((Sortable.active || awaitingDragStarted) && evt.cancelable) {