From ef4d6ab98875891716fa2b9ce22ed34afc58a53f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 19 Sep 2024 12:52:46 +0200 Subject: [PATCH] Fix browser glitch caused by two overlapping scroll animations in web UI (#31960) --- .../features/ui/components/columns_area.jsx | 32 +------------------ app/javascript/mastodon/scroll.ts | 25 +++++++++------ app/javascript/mastodon/test_helpers.tsx | 8 +++++ 3 files changed, 25 insertions(+), 40 deletions(-) diff --git a/app/javascript/mastodon/features/ui/components/columns_area.jsx b/app/javascript/mastodon/features/ui/components/columns_area.jsx index 5a2ea8c2c8..ff76d5bcb2 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.jsx +++ b/app/javascript/mastodon/features/ui/components/columns_area.jsx @@ -4,8 +4,6 @@ import { Children, cloneElement, useCallback } from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { supportsPassiveEvents } from 'detect-passive-events'; - import { scrollRight } from '../../../scroll'; import BundleContainer from '../containers/bundle_container'; import { @@ -71,10 +69,6 @@ export default class ColumnsArea extends ImmutablePureComponent { }; componentDidMount() { - if (!this.props.singleColumn) { - this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); - } - if (this.mediaQuery) { if (this.mediaQuery.addEventListener) { this.mediaQuery.addEventListener('change', this.handleLayoutChange); @@ -87,23 +81,7 @@ export default class ColumnsArea extends ImmutablePureComponent { this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl'); } - UNSAFE_componentWillUpdate(nextProps) { - if (this.props.singleColumn !== nextProps.singleColumn && nextProps.singleColumn) { - this.node.removeEventListener('wheel', this.handleWheel); - } - } - - componentDidUpdate(prevProps) { - if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) { - this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); - } - } - componentWillUnmount () { - if (!this.props.singleColumn) { - this.node.removeEventListener('wheel', this.handleWheel); - } - if (this.mediaQuery) { if (this.mediaQuery.removeEventListener) { this.mediaQuery.removeEventListener('change', this.handleLayoutChange); @@ -116,7 +94,7 @@ export default class ColumnsArea extends ImmutablePureComponent { handleChildrenContentChange() { if (!this.props.singleColumn) { const modifier = this.isRtlLayout ? -1 : 1; - this._interruptScrollAnimation = scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier); + scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier); } } @@ -124,14 +102,6 @@ export default class ColumnsArea extends ImmutablePureComponent { this.setState({ renderComposePanel: !e.matches }); }; - handleWheel = () => { - if (typeof this._interruptScrollAnimation !== 'function') { - return; - } - - this._interruptScrollAnimation(); - }; - setRef = (node) => { this.node = node; }; diff --git a/app/javascript/mastodon/scroll.ts b/app/javascript/mastodon/scroll.ts index 35e13a4527..0756edb4ce 100644 --- a/app/javascript/mastodon/scroll.ts +++ b/app/javascript/mastodon/scroll.ts @@ -38,13 +38,20 @@ const scroll = ( const isScrollBehaviorSupported = 'scrollBehavior' in document.documentElement.style; -export const scrollRight = (node: Element, position: number) => { - if (isScrollBehaviorSupported) - node.scrollTo({ left: position, behavior: 'smooth' }); - else scroll(node, 'scrollLeft', position); -}; +export const scrollRight = (node: Element, position: number) => + requestIdleCallback(() => { + if (isScrollBehaviorSupported) { + node.scrollTo({ left: position, behavior: 'smooth' }); + } else { + scroll(node, 'scrollLeft', position); + } + }); -export const scrollTop = (node: Element) => { - if (isScrollBehaviorSupported) node.scrollTo({ top: 0, behavior: 'smooth' }); - else scroll(node, 'scrollTop', 0); -}; +export const scrollTop = (node: Element) => + requestIdleCallback(() => { + if (isScrollBehaviorSupported) { + node.scrollTo({ top: 0, behavior: 'smooth' }); + } else { + scroll(node, 'scrollTop', 0); + } + }); diff --git a/app/javascript/mastodon/test_helpers.tsx b/app/javascript/mastodon/test_helpers.tsx index f405090730..8a6f5a3377 100644 --- a/app/javascript/mastodon/test_helpers.tsx +++ b/app/javascript/mastodon/test_helpers.tsx @@ -8,6 +8,14 @@ import { render as rtlRender } from '@testing-library/react'; import { IdentityContext } from './identity_context'; +beforeEach(() => { + global.requestIdleCallback = jest + .fn() + .mockImplementation((fn: () => void) => { + fn(); + }); +}); + function render( ui: React.ReactElement, {