์ฌ๋ฌ ์น์ฌ์ดํธ์์๋ scroll๊ณผ ๊ด๊ณ์์ด ์๋จ์ ๋ถ์ด์๋ sticky header๋ฅผ ์์ฃผ ์ฌ์ฉํ๊ณ ์๋๋ฐ์,
์๋ ์์์ ๊ฐ์ด ํน์ ์์ญ์์ css๊ฐ ๋ฐ๋ ์ ์๋ header๋ฅผ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค.
โ๏ธ ์ฌ์ฉ ์คํ : React, emotion/css
๐ ๊ธฐ๋ณธ ๊ตฌ์กฐ ์์ฑํ๊ธฐ
ํค๋๋ ๊ณตํต์ ์ผ๋ก ์ฌ์ฉํ ๊ฐ๋ฅ์ฑ์ด ๋๊ธฐ ๋๋ฌธ์ Header๋ก ๋ถ๋ฆฌํ์ต๋๋ค.
App.js
import { css } from "@emotion/react";
import Header from "./Header.jsx";
export default function App() {
return (
<div className="App" css={wrapStyle}>
<Header />
</div>
);
}
const wrapStyle = css`
`;
Header.jsx
import { css } from "@emotion/react";
export default function Header() {
return <header css={headerStyle}>Header</header>;
}
const headerStyle = (isPoint) => css`
`;
๐ ์๋จ์ Header ๊ณ ์ ์ํค๊ธฐ
CSS๋ฅผ ์๋์ ๊ฐ์ด ์ค์ ํ๊ณ customํ ์ค์ ๋ค์ ํ๋ฉด Header๋ ๊ฐ์ฅ ์๋จ์ ๋ถ์ด์๊ฒ ๋ฉ๋๋ค.
position : sticky
top : 0
import { css } from "@emotion/react";
export default function Header() {
return <header css={headerStyle}>Header</header>;
}
const headerStyle = (isPoint) => css`
height: 44px;
position: sticky;
top: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: #e0e0e0;
`;
๐ Header์ CSS ๊ฐ ๋ฐ๋ ์กฐ๊ฑด ์ค์ ํ๊ธฐ
scroll์ด ์๊ธธ๋งํ body๋ฅผ ๋ง๋ค๊ณ ์ 30 length์ ๋ฐฐ์ด์ ๋ง๋ค๊ฒ ์ต๋๋ค.
๋ชฉํ๋ " 15๋ฒ์งธ <h1>์ ๋ง๋๋ฉด Header์ ์์์ด ๋ณ๊ฒฝ๋๋ ๊ฒ "
App.js
import { css } from "@emotion/react";
import Header from "./Header.jsx";
import { useRef } from "react";
export default function App() {
const pointRef = useRef(null);
return (
<div className="App" css={wrapStyle}>
<Header pointRef={pointRef} />
{new Array(30).fill(0).map((ele, idx) => (
<h1
key={idx}
className={idx >= 15 ? "coloredText" : "text"}
ref={idx === 15 ? pointRef : null}
>
{idx}
</h1>
))}
</div>
);
}
const wrapStyle = css`
.text {
background-color: #f5f5f5;
}
.coloredText {
background-color: #ffb600;
}
`;
Header.jsx
- setPointHeader - point์ง์ (ex 15๋ฒ์งธ h1) window scroll์ด pointRef์ ๋๋ฌํ๋ ์๊ฐ isPoint true ๋ก ์ค์ ํฉ๋๋ค.
- useEffect - addEventListener์๋ return removeEventListener๋ ํด์ค์ผ ํ๋๋ฐ ๊ทธ ์ด์ ๋ React๊ฐ ์ฌ๋ ๋๋ง ๋ ๋๋ง๋ค ์ด๋ฒคํธ ๋์์ ๊ณ์ ๊ฐ์ ํจ์๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์ react component๊ฐ unmounted ๋๋ ์์ ์ ์ ํํ ์ ๊ฑฐ๋์ผ ํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
import { useEffect, useState, useMemo } from "react";
import { css } from "@emotion/react";
export default function Header({ pointRef }) {
const HEADER_HEIGHT = 44;
const [isPoint, setIsPoint] = useState(false);
const setPointHeader = useMemo(
() =>{
const isReachPoint =
window.scrollY > pointRef.current.offsetTop - HEADER_HEIGHT;
if (isReachPoint !== isPoint) setIsPoint(isReachPoint);
}
, [isPoint]
);
useEffect(() => {
window.addEventListener("scroll", setPointHeader);
return () => {
window.removeEventListener("scroll", setPointHeader);
};
}, [isPoint, setPointHeader, pointRef]);
return <header css={headerStyle(isPoint)}>Header</header>;
}
const headerStyle = (isPoint) => css`
height: 44px;
position: sticky;
top: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: ${isPoint ? "#ffb600" : "#e0e0e0"};
`;
๐ throttle๋ก ์ต์ ํ
๊ทธ๋ผ์๋ scoll event๋ scroll ํ ๋๋ง๋ค ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ ์ถ๊ฐ์ ์ธ ์ต์ ํ๊ฐ ํ์ํฉ๋๋ค.
- throttle: ๊ฐ์ ์ด๋ฒคํธ๊ฐ ๋ฐ๋ณต ์คํ๋ ๋ , ์์ ์ค์ ํ ์๊ฐ ๊ฐ๊ฒฉ (ms) ๋ด์์ ํ๋ฒ์ ์ฝ๋ฐฑํจ์๋ง ํธ์ถํ๊ฒ ํฉ๋๋ค.
Header.jsx
๋ฐ๋ผ์ ์๋์ ๊ฐ์ด 0.3์ด ์ ๋๋ก ์ค์ ํฉ๋๋ค.
// ์ค๋ต
const setPointHeader = useMemo(
() =>
throttle(() => {
const isReachPoint =
window.scrollY > pointRef.current.offsetTop - HEADER_HEIGHT;
if (isReachPoint !== isPoint) setIsPoint(isReachPoint);
}, 300),
[isPoint]
);
๐ฅณ ๊ทธ๋ผ ์๋ฃ
code๋ก ์ง์ ํ์ธํ๊ณ ์ถ๋ค๋ฉด ink_0์ codesandbox - [React] sticky header change CSS in point ๋ก ํ์ธํด์ฃผ์ธ์
์ฐธ๊ณ ๋งํฌ
๐ [React] ๋ฆฌ์กํธ ํค๋ ์คํฌ๋กค ์ CSS ๋ณ๊ฒฝ
๐ [React] scroll ์ด๋ฒคํธ throttle๋ก ์ต์ ํ ์ํค๊ธฐ
๐ How to Cleanup Event Listeners in React
๋๊ธ