diff --git a/__tests__/components/layout/DynamicHeaderWrapper.test.tsx b/__tests__/components/layout/DynamicHeaderWrapper.test.tsx
index 45f30ebc..bd40f679 100644
--- a/__tests__/components/layout/DynamicHeaderWrapper.test.tsx
+++ b/__tests__/components/layout/DynamicHeaderWrapper.test.tsx
@@ -52,6 +52,10 @@ describe("DynamicHeaderWrapper", () => {
expect(screen.getByTestId("header8")).toHaveAttribute("data-scroll", "false");
Object.defineProperty(window, "scrollY", { value: 120, writable: true, configurable: true });
+ window.requestAnimationFrame = (cb) => {
+ cb(performance.now());
+ return 1;
+ };
fireEvent.scroll(document);
expect(screen.getByTestId("header8")).toHaveAttribute("data-scroll", "true");
diff --git a/__tests__/components/layout/Layout.test.tsx b/__tests__/components/layout/Layout.test.tsx
index e326e1c3..d3130390 100644
--- a/__tests__/components/layout/Layout.test.tsx
+++ b/__tests__/components/layout/Layout.test.tsx
@@ -96,6 +96,10 @@ describe("Layout", () => {
expect(screen.getByTestId("header-1")).toHaveAttribute("data-scroll", "false");
Object.defineProperty(window, "scrollY", { value: 150, writable: true, configurable: true });
+ window.requestAnimationFrame = (cb) => {
+ cb(performance.now());
+ return 1;
+ };
fireEvent.scroll(document);
expect(screen.getByTestId("header-1")).toHaveAttribute("data-scroll", "true");
diff --git a/__tests__/snapshots/elements/BackToTop.test.tsx b/__tests__/snapshots/elements/BackToTop.test.tsx
index 25288fb9..9368ca2e 100644
--- a/__tests__/snapshots/elements/BackToTop.test.tsx
+++ b/__tests__/snapshots/elements/BackToTop.test.tsx
@@ -1,5 +1,5 @@
import { expect, describe, it } from "@jest/globals";
-import { render } from "@testing-library/react";
+import { render, fireEvent } from "@testing-library/react";
import BackToTop from "@/components/elements/BackToTop";
describe("BackToTop Component", () => {
@@ -7,4 +7,15 @@ describe("BackToTop Component", () => {
const { container } = render();
expect(container).toMatchSnapshot();
});
+
+ it("updates scroll state on scroll event", () => {
+ render();
+
+ Object.defineProperty(window, "scrollY", { value: 150, writable: true, configurable: true });
+ window.requestAnimationFrame = (cb) => {
+ cb(performance.now());
+ return 1;
+ };
+ fireEvent.scroll(window);
+ });
});
diff --git a/components/elements/BackToTop.tsx b/components/elements/BackToTop.tsx
index 3fdb6396..9aaacc42 100644
--- a/components/elements/BackToTop.tsx
+++ b/components/elements/BackToTop.tsx
@@ -5,11 +5,18 @@ export default function BackToTop({ target }: Readonly<{ target: string }>) {
const [hasScrolled, setHasScrolled] = useState(false);
useEffect(() => {
+ const state = { isTicking: false };
const onScroll = () => {
- setHasScrolled(window.scrollY > 100);
+ if (!state.isTicking) {
+ window.requestAnimationFrame(() => {
+ setHasScrolled(window.scrollY > 100);
+ state.isTicking = false;
+ });
+ state.isTicking = true;
+ }
};
- window.addEventListener("scroll", onScroll);
+ window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
diff --git a/components/layout/DynamicHeaderWrapper.tsx b/components/layout/DynamicHeaderWrapper.tsx
index 1206feb6..dbb5a706 100644
--- a/components/layout/DynamicHeaderWrapper.tsx
+++ b/components/layout/DynamicHeaderWrapper.tsx
@@ -16,17 +16,22 @@ export default function DynamicHeaderWrapper({ navigation }: Readonly(false);
React.useEffect(() => {
+ const state = { isTicking: false };
const handleScroll = (): void => {
- const scrollCheck: boolean = window.scrollY > 100;
- if (scrollCheck !== scroll) {
- setScroll(scrollCheck);
+ if (!state.isTicking) {
+ window.requestAnimationFrame(() => {
+ const scrollCheck: boolean = window.scrollY > 100;
+ setScroll((prev) => (prev !== scrollCheck ? scrollCheck : prev));
+ state.isTicking = false;
+ });
+ state.isTicking = true;
}
};
- document.addEventListener("scroll", handleScroll);
+ document.addEventListener("scroll", handleScroll, { passive: true });
return () => {
document.removeEventListener("scroll", handleScroll);
};
- }, [scroll]);
+ }, []);
return (
<>
diff --git a/components/layout/Layout.tsx b/components/layout/Layout.tsx
index 7f289889..958cb750 100644
--- a/components/layout/Layout.tsx
+++ b/components/layout/Layout.tsx
@@ -79,19 +79,25 @@ export default function Layout({ headerStyle, footerStyle, breadcrumbTitle: _bre
useEffect(() => {
AOS.init();
+
+ const state = { isTicking: false };
const handleScroll = (): void => {
- const scrollCheck: boolean = window.scrollY > 100;
- if (scrollCheck !== scroll) {
- setScroll(scrollCheck);
+ if (!state.isTicking) {
+ window.requestAnimationFrame(() => {
+ const scrollCheck: boolean = window.scrollY > 100;
+ setScroll((prev) => (prev !== scrollCheck ? scrollCheck : prev));
+ state.isTicking = false;
+ });
+ state.isTicking = true;
}
};
- document.addEventListener("scroll", handleScroll);
+ document.addEventListener("scroll", handleScroll, { passive: true });
return () => {
document.removeEventListener("scroll", handleScroll);
};
- }, [scroll]);
+ }, []);
const defaultNavigation: EditionNavigation = {
main: mainNavLinks,