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
This commit is contained in:
Aarni Koskela 2021-12-09 12:51:02 +02:00 committed by GitHub
parent ace52f7e49
commit 3028d3d7ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 49 additions and 47 deletions

View File

@ -7,6 +7,7 @@ import { shortenHTTPVerb } from '../../utils/openapi';
import { MenuItems } from './MenuItems'; import { MenuItems } from './MenuItems';
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements'; import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
import { l } from '../../services/Labels'; import { l } from '../../services/Labels';
import { scrollIntoViewIfNeeded } from '../../utils';
export interface MenuItemProps { export interface MenuItemProps {
item: IMenuItem; item: IMenuItem;
@ -33,7 +34,7 @@ export class MenuItem extends React.Component<MenuItemProps> {
scrollIntoViewIfActive() { scrollIntoViewIfActive() {
if (this.props.item.active && this.ref.current) { 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<OperationMenuItemC
componentDidUpdate() { componentDidUpdate() {
if (this.props.item.active && this.ref.current) { if (this.props.item.active && this.ref.current) {
this.ref.current.scrollIntoViewIfNeeded(); scrollIntoViewIfNeeded(this.ref.current);
} }
} }

View File

@ -37,7 +37,6 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
if (item && item.active && this.context.menuToggle) { if (item && item.active && this.context.menuToggle) {
return item.expanded ? item.collapse() : item.expand(); return item.expanded ? item.collapse() : item.expand();
} }
this.props.menu.activateAndScroll(item, true); this.props.menu.activateAndScroll(item, true);
setTimeout(() => { setTimeout(() => {
if (this._updateScroll) { if (this._updateScroll) {

View File

@ -24,52 +24,54 @@ export function html2Str(html: string): string {
.join(' '); .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) { export function scrollIntoViewIfNeeded(el: HTMLElement, centerIfNeeded = true) {
(Element as any).prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) { const parent = el.parentNode as HTMLElement | null;
centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded; 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; if ((overTop || overBottom) && centerIfNeeded) {
const parentComputedStyle = window.getComputedStyle(parent, undefined); parent.scrollTop =
const parentBorderTopWidth = parseInt( el.offsetTop -
parentComputedStyle.getPropertyValue('border-top-width'), parent.offsetTop -
10, parent.clientHeight / 2 -
); parentBorderTopWidth +
const parentBorderLeftWidth = parseInt( el.clientHeight / 2;
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) { if ((overLeft || overRight) && centerIfNeeded) {
parent.scrollTop = parent.scrollLeft =
this.offsetTop - el.offsetLeft -
parent.offsetTop - parent.offsetLeft -
parent.clientHeight / 2 - parent.clientWidth / 2 -
parentBorderTopWidth + parentBorderLeftWidth +
this.clientHeight / 2; el.clientWidth / 2;
} }
if ((overLeft || overRight) && centerIfNeeded) { if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
parent.scrollLeft = el.scrollIntoView(alignWithTop);
this.offsetLeft - }
parent.offsetLeft -
parent.clientWidth / 2 -
parentBorderLeftWidth +
this.clientWidth / 2;
}
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
this.scrollIntoView(alignWithTop);
}
};
} }