From 65373c8ae584c9feaa4d44f2aab13ce015997ef5 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Tue, 30 Mar 2021 20:58:53 +0300 Subject: [PATCH 1/2] Check an overflow value and stop on fixed parent --- src/components/scrollManager.js | 41 ++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/components/scrollManager.js b/src/components/scrollManager.js index 13ff164e7e..b99fe4a13f 100644 --- a/src/components/scrollManager.js +++ b/src/components/scrollManager.js @@ -201,6 +201,19 @@ import layoutManager from './layoutManager'; */ const documentScroller = new DocumentScroller(); + const scrollerHints = { + x: { + nameScroll: 'scrollWidth', + nameClient: 'clientWidth', + nameStyle: 'overflowX' + }, + y: { + nameScroll: 'scrollHeight', + nameClient: 'clientHeight', + nameStyle: 'overflowY' + } + }; + /** * Returns parent element that can be scrolled. If no such, returns document scroller. * @@ -210,24 +223,26 @@ import layoutManager from './layoutManager'; */ function getScrollableParent(element, vertical) { if (element) { - let nameScroll = 'scrollWidth'; - let nameClient = 'clientWidth'; - let nameClass = 'scrollX'; - - if (vertical) { - nameScroll = 'scrollHeight'; - nameClient = 'clientHeight'; - nameClass = 'scrollY'; - } + const scrollerHint = vertical ? scrollerHints.y : scrollerHints.x; let parent = element.parentElement; - while (parent) { + while (parent && parent !== document.body) { // Skip 'emby-scroller' and 'emby-tabs' because they scroll by themselves if (!parent.classList.contains('emby-scroller') && - !parent.classList.contains('emby-tabs') && - parent[nameScroll] > parent[nameClient] && parent.classList.contains(nameClass)) { - return parent; + !parent.classList.contains('emby-tabs')) { + const styles = window.getComputedStyle(parent); + + // Stop on fixed parent + if (styles.position === 'fixed') { + return parent; + } + + const overflow = styles[scrollerHint.nameStyle]; + + if (overflow === 'scroll' || overflow === 'auto' && parent[scrollerHint.nameScroll] > parent[scrollerHint.nameClient]) { + return parent; + } } parent = parent.parentElement; From 4fb3a46fea21738da3e4c5d6dc7c08b5d72f0f07 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sat, 24 Apr 2021 21:30:40 +0300 Subject: [PATCH 2/2] Fix conflict with other scrollers --- src/components/scrollManager.js | 119 ++++++++++++++++++++++---------- src/libraries/scroller.js | 4 ++ src/scripts/scrollHelper.js | 4 ++ 3 files changed, 90 insertions(+), 37 deletions(-) diff --git a/src/components/scrollManager.js b/src/components/scrollManager.js index b99fe4a13f..da71b157cb 100644 --- a/src/components/scrollManager.js +++ b/src/components/scrollManager.js @@ -173,6 +173,15 @@ import layoutManager from './layoutManager'; return Math.min(document.documentElement.clientHeight, document.body.clientHeight); } + /** + * Returns attribute value. + * @param {string} attributeName - Attibute name. + * @return {string} Attibute value. + */ + getAttribute(attributeName) { + return document.body.getAttribute(attributeName); + } + /** * Returns bounding client rect. * @return {Rect} Bounding client rect. @@ -205,12 +214,14 @@ import layoutManager from './layoutManager'; x: { nameScroll: 'scrollWidth', nameClient: 'clientWidth', - nameStyle: 'overflowX' + nameStyle: 'overflowX', + nameScrollMode: 'data-scroll-mode-x' }, y: { nameScroll: 'scrollHeight', nameClient: 'clientHeight', - nameStyle: 'overflowY' + nameStyle: 'overflowY', + nameScrollMode: 'data-scroll-mode-y' } }; @@ -228,21 +239,24 @@ import layoutManager from './layoutManager'; let parent = element.parentElement; while (parent && parent !== document.body) { - // Skip 'emby-scroller' and 'emby-tabs' because they scroll by themselves - if (!parent.classList.contains('emby-scroller') && - !parent.classList.contains('emby-tabs')) { - const styles = window.getComputedStyle(parent); + const scrollMode = parent.getAttribute(scrollerHint.nameScrollMode); - // Stop on fixed parent - if (styles.position === 'fixed') { - return parent; - } + // Stop on self-scrolled containers + if (scrollMode === 'custom') { + return parent; + } - const overflow = styles[scrollerHint.nameStyle]; + const styles = window.getComputedStyle(parent); - if (overflow === 'scroll' || overflow === 'auto' && parent[scrollerHint.nameScroll] > parent[scrollerHint.nameClient]) { - return parent; - } + // Stop on fixed parent + if (styles.position === 'fixed') { + return parent; + } + + const overflow = styles[scrollerHint.nameStyle]; + + if (overflow === 'scroll' || overflow === 'auto' && parent[scrollerHint.nameScroll] > parent[scrollerHint.nameClient]) { + return parent; } parent = parent.parentElement; @@ -257,6 +271,8 @@ import layoutManager from './layoutManager'; * @property {number} scrollPos - Current scroll position. * @property {number} scrollSize - Scroll size. * @property {number} clientSize - Client size. + * @property {string} mode - Scrolling mode. + * @property {boolean} custom - Custom scrolling mode. */ /** @@ -273,12 +289,16 @@ import layoutManager from './layoutManager'; data.scrollPos = scroller.scrollLeft; data.scrollSize = scroller.scrollWidth; data.clientSize = scroller.clientWidth; + data.mode = scroller.getAttribute(scrollerHints.x.nameScrollMode); } else { data.scrollPos = scroller.scrollTop; data.scrollSize = scroller.scrollHeight; data.clientSize = scroller.clientHeight; + data.mode = scroller.getAttribute(scrollerHints.y.nameScrollMode); } + data.custom = data.mode === 'custom'; + return data; } @@ -363,9 +383,13 @@ import layoutManager from './layoutManager'; const scrollBehavior = smooth ? 'smooth' : 'instant'; if (xScroller !== yScroller) { - scrollToHelper(xScroller, {left: scrollX, behavior: scrollBehavior}); - scrollToHelper(yScroller, {top: scrollY, behavior: scrollBehavior}); - } else { + if (xScroller) { + scrollToHelper(xScroller, {left: scrollX, behavior: scrollBehavior}); + } + if (yScroller) { + scrollToHelper(yScroller, {top: scrollY, behavior: scrollBehavior}); + } + } else if (xScroller) { scrollToHelper(xScroller, {left: scrollX, top: scrollY, behavior: scrollBehavior}); } } @@ -392,8 +416,8 @@ import layoutManager from './layoutManager'; * @param {number} scrollY - Vertical coordinate. */ function animateScroll(xScroller, scrollX, yScroller, scrollY) { - const ox = xScroller.scrollLeft; - const oy = yScroller.scrollTop; + const ox = xScroller ? xScroller.scrollLeft : scrollX; + const oy = yScroller ? yScroller.scrollTop : scrollY; const dx = scrollX - ox; const dy = scrollY - oy; @@ -517,30 +541,51 @@ import layoutManager from './layoutManager'; scrollCenterX = scrollCenterY = false; } - const xScroller = getScrollableParent(element, false); - const yScroller = getScrollableParent(element, true); - - const elementRect = element.getBoundingClientRect(); + let xScroller = getScrollableParent(element, false); + let yScroller = getScrollableParent(element, true); const xScrollerData = getScrollerData(xScroller, false); const yScrollerData = getScrollerData(yScroller, true); - const xPos = getScrollerChildPos(xScroller, element, false); - const yPos = getScrollerChildPos(yScroller, element, true); - - const scrollX = calcScroll(xScrollerData, xPos, elementRect.width, scrollCenterX); - let scrollY = calcScroll(yScrollerData, yPos, elementRect.height, scrollCenterY); - - // HACK: Scroll to top for top menu because it is hidden - // FIXME: Need a marker to scroll top/bottom - if (isFixed && elementRect.bottom < 0) { - scrollY = 0; + // Exit, since we have no control over scrolling in this container + if (xScroller === yScroller && (xScrollerData.custom || yScrollerData.custom)) { + return; } - // HACK: Ensure we are at the top - // FIXME: Need a marker to scroll top/bottom - if (scrollY < minimumScrollY() && yScroller === documentScroller) { - scrollY = 0; + // Exit, since we have no control over scrolling in these containers + if (xScrollerData.custom && yScrollerData.custom) { + return; + } + + const elementRect = element.getBoundingClientRect(); + + let scrollX = 0; + let scrollY = 0; + + if (!xScrollerData.custom) { + const xPos = getScrollerChildPos(xScroller, element, false); + scrollX = calcScroll(xScrollerData, xPos, elementRect.width, scrollCenterX); + } else { + xScroller = null; + } + + if (!yScrollerData.custom) { + const yPos = getScrollerChildPos(yScroller, element, true); + scrollY = calcScroll(yScrollerData, yPos, elementRect.height, scrollCenterY); + + // HACK: Scroll to top for top menu because it is hidden + // FIXME: Need a marker to scroll top/bottom + if (isFixed && elementRect.bottom < 0) { + scrollY = 0; + } + + // HACK: Ensure we are at the top + // FIXME: Need a marker to scroll top/bottom + if (scrollY < minimumScrollY() && yScroller === documentScroller) { + scrollY = 0; + } + } else { + yScroller = null; } doScroll(xScroller, scrollX, yScroller, scrollY, smooth); diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index b438f7daae..355387807c 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -630,6 +630,8 @@ const scrollerFactory = function (frame, options) { //passive: true }); + scrollSource.removeAttribute(`data-scroll-mode-${o.horizontal ? 'x' : 'y'}`); + // Reset initialized status and return the instance self.initialized = 0; return self; @@ -751,6 +753,8 @@ const scrollerFactory = function (frame, options) { } } + scrollSource.setAttribute(`data-scroll-mode-${o.horizontal ? 'x' : 'y'}`, 'custom'); + if (transform || layoutManager.tv) { // This can prevent others from being able to listen to mouse events dom.addEventListener(dragSourceElement, 'mousedown', dragInitSlidee, { diff --git a/src/scripts/scrollHelper.js b/src/scripts/scrollHelper.js index 6c281539a6..57c8f28fb7 100644 --- a/src/scripts/scrollHelper.js +++ b/src/scripts/scrollHelper.js @@ -103,6 +103,8 @@ function centerOnFocusVertical(e) { export const centerFocus = { on: function (element, horizontal) { + element.setAttribute(`data-scroll-mode-${horizontal ? 'x' : 'y'}`, 'custom'); + if (horizontal) { dom.addEventListener(element, 'focus', centerOnFocusHorizontal, { capture: true, @@ -116,6 +118,8 @@ export const centerFocus = { } }, off: function (element, horizontal) { + element.removeAttribute(`data-scroll-mode-${horizontal ? 'x' : 'y'}`); + if (horizontal) { dom.removeEventListener(element, 'focus', centerOnFocusHorizontal, { capture: true,