From 3028d3d7ac8898401d3e2359320dd4f4a48a466a Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 9 Dec 2021 12:51:02 +0200 Subject: [PATCH] Fix Chrome scrolling by using alternate scrollIntoViewIfNeeded (#1823) * Break out scrollIntoViewIfNeeded into freely usable function * Always use alternate implementation of scrollIntoViewIfNeeded Refs #1714 Refs #1742 --- src/components/SideMenu/MenuItem.tsx | 5 +- src/components/SideMenu/SideMenu.tsx | 1 - src/utils/dom.ts | 90 ++++++++++++++-------------- 3 files changed, 49 insertions(+), 47 deletions(-) diff --git a/src/components/SideMenu/MenuItem.tsx b/src/components/SideMenu/MenuItem.tsx index 955ba636..138e88c9 100644 --- a/src/components/SideMenu/MenuItem.tsx +++ b/src/components/SideMenu/MenuItem.tsx @@ -7,6 +7,7 @@ import { shortenHTTPVerb } from '../../utils/openapi'; import { MenuItems } from './MenuItems'; import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements'; import { l } from '../../services/Labels'; +import { scrollIntoViewIfNeeded } from '../../utils'; export interface MenuItemProps { item: IMenuItem; @@ -33,7 +34,7 @@ export class MenuItem extends React.Component { scrollIntoViewIfActive() { if (this.props.item.active && this.ref.current) { - this.ref.current.scrollIntoViewIfNeeded(); + scrollIntoViewIfNeeded(this.ref.current); } } @@ -77,7 +78,7 @@ export class OperationMenuItemContent extends React.Component { if (this._updateScroll) { diff --git a/src/utils/dom.ts b/src/utils/dom.ts index 515d9a2b..bb036934 100644 --- a/src/utils/dom.ts +++ b/src/utils/dom.ts @@ -24,52 +24,54 @@ export function html2Str(html: string): string { .join(' '); } -// scrollIntoViewIfNeeded polyfill +// Alternate scrollIntoViewIfNeeded implementation. +// Used in all cases, since it seems Chrome's implementation is buggy +// when "Experimental Web Platform Features" is enabled (at least of version 96). +// See #1714, #1742 -if (typeof Element !== 'undefined' && !(Element as any).prototype.scrollIntoViewIfNeeded) { - (Element as any).prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) { - centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded; +export function scrollIntoViewIfNeeded(el: HTMLElement, centerIfNeeded = true) { + const parent = el.parentNode as HTMLElement | null; + if (!parent) { + return; + } + const parentComputedStyle = window.getComputedStyle(parent, undefined); + const parentBorderTopWidth = parseInt( + parentComputedStyle.getPropertyValue('border-top-width'), + 10, + ); + const parentBorderLeftWidth = parseInt( + parentComputedStyle.getPropertyValue('border-left-width'), + 10, + ); + const overTop = el.offsetTop - parent.offsetTop < parent.scrollTop; + const overBottom = + el.offsetTop - parent.offsetTop + el.clientHeight - parentBorderTopWidth > + parent.scrollTop + parent.clientHeight; + const overLeft = el.offsetLeft - parent.offsetLeft < parent.scrollLeft; + const overRight = + el.offsetLeft - parent.offsetLeft + el.clientWidth - parentBorderLeftWidth > + parent.scrollLeft + parent.clientWidth; + const alignWithTop = overTop && !overBottom; - const parent = this.parentNode; - const parentComputedStyle = window.getComputedStyle(parent, undefined); - const parentBorderTopWidth = parseInt( - parentComputedStyle.getPropertyValue('border-top-width'), - 10, - ); - const parentBorderLeftWidth = parseInt( - parentComputedStyle.getPropertyValue('border-left-width'), - 10, - ); - const overTop = this.offsetTop - parent.offsetTop < parent.scrollTop; - const overBottom = - this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth > - parent.scrollTop + parent.clientHeight; - const overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft; - const overRight = - this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth > - parent.scrollLeft + parent.clientWidth; - const alignWithTop = overTop && !overBottom; + if ((overTop || overBottom) && centerIfNeeded) { + parent.scrollTop = + el.offsetTop - + parent.offsetTop - + parent.clientHeight / 2 - + parentBorderTopWidth + + el.clientHeight / 2; + } - if ((overTop || overBottom) && centerIfNeeded) { - parent.scrollTop = - this.offsetTop - - parent.offsetTop - - parent.clientHeight / 2 - - parentBorderTopWidth + - this.clientHeight / 2; - } + if ((overLeft || overRight) && centerIfNeeded) { + parent.scrollLeft = + el.offsetLeft - + parent.offsetLeft - + parent.clientWidth / 2 - + parentBorderLeftWidth + + el.clientWidth / 2; + } - if ((overLeft || overRight) && centerIfNeeded) { - parent.scrollLeft = - this.offsetLeft - - parent.offsetLeft - - parent.clientWidth / 2 - - parentBorderLeftWidth + - this.clientWidth / 2; - } - - if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) { - this.scrollIntoView(alignWithTop); - } - }; + if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) { + el.scrollIntoView(alignWithTop); + } }