diff --git a/packages/mui-material/src/useScrollTrigger/useScrollTrigger.js b/packages/mui-material/src/useScrollTrigger/useScrollTrigger.js index 4edc7b29160951..15e049eaf6dda4 100644 --- a/packages/mui-material/src/useScrollTrigger/useScrollTrigger.js +++ b/packages/mui-material/src/useScrollTrigger/useScrollTrigger.js @@ -24,19 +24,31 @@ const defaultTarget = typeof window !== 'undefined' ? window : null; export default function useScrollTrigger(options = {}) { const { getTrigger = defaultTrigger, target = defaultTarget, ...other } = options; const store = React.useRef(); + const ticking = React.useRef(false); const [trigger, setTrigger] = React.useState(() => getTrigger(store, other)); React.useEffect(() => { if (target === null) { return setTrigger(false); } + + let rafID = null; const handleScroll = () => { - setTrigger(getTrigger(store, { target, ...other })); + if (!ticking.current) { + ticking.current = true; + rafID = window.requestAnimationFrame(() => { + setTrigger(getTrigger(store, { target, ...other })); + ticking.current = false; + }); + } }; - handleScroll(); // Re-evaluate trigger when dependencies change + setTrigger(getTrigger(store, { target, ...other })); // Re-evaluate trigger when dependencies change target.addEventListener('scroll', handleScroll, { passive: true }); return () => { target.removeEventListener('scroll', handleScroll, { passive: true }); + if (rafID) { + window.cancelAnimationFrame(rafID); + } }; // See Option 3. https://github.com/facebook/react/issues/14476#issuecomment-471199055 // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler diff --git a/packages/mui-material/src/useScrollTrigger/useScrollTrigger.test.js b/packages/mui-material/src/useScrollTrigger/useScrollTrigger.test.js index bca4b55e9c25f2..92b7235cdeb652 100644 --- a/packages/mui-material/src/useScrollTrigger/useScrollTrigger.test.js +++ b/packages/mui-material/src/useScrollTrigger/useScrollTrigger.test.js @@ -1,6 +1,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { expect } from 'chai'; +import { stub } from 'sinon'; import { act, createRenderer, RenderCounter, screen } from '@mui/internal-test-utils'; import describeSkipIf from '@mui/internal-test-utils/describeSkipIf'; import useScrollTrigger from '@mui/material/useScrollTrigger'; @@ -9,6 +10,21 @@ import Box from '@mui/material/Box'; describe('useScrollTrigger', () => { const { render } = createRenderer(); + let requestAnimationFrameStub; + let cancelAnimationFrameStub; + + beforeEach(() => { + requestAnimationFrameStub = stub(window, 'requestAnimationFrame').callsFake((callback) => { + callback(0); + return 0; + }); + cancelAnimationFrameStub = stub(window, 'cancelAnimationFrame'); + }); + + afterEach(() => { + requestAnimationFrameStub.restore(); + cancelAnimationFrameStub.restore(); + }); describe('defaultTrigger', () => { it('should be false by default', () => {