- ๐ React ์คํฌ๋กค ์ ๋๋ฉ์ด์ ๊ตฌํํ๊ธฐ
๐ React ์คํฌ๋กค ์ ๋๋ฉ์ด์ ๊ตฌํํ๊ธฐ
์ฌ์ฉ์์ ๋๊ธธ์ ๋๊ธฐ ์ํด ๋ค์ํ ๋ฐฉ๋ฒ๋ค์ด ์ฌ์ฉ ๋๋๋ฐ,
๊ทธ ์ค์์๋ ๊ฐ์ฅ ๋์ ๋๋ ๊ฒ์ด ์คํฌ๋กค ์ ๋๋ฉ์ด์
์ด์์ต๋๋ค.
์ค์ ์ฌ์ด๋ ํ๋ก์ ํธ์์ ๊ตฌํํ ๋ชจ์ต์ ๋ค์ ๋งํฌ์์ ํ์ธํ ์ ์์ต๋๋ค.
๋ง๋ชจ(LINGMO) - ๋น์ ์ ๋งํฌ ๋ชจ์
๊ฐ์ฑ ์๋ ๋๋ง์ ๋งํฌ ๋ชจ์์ ๋ง๋ค์ด๋ณด์ธ์!
lingmo.me
์ง์ ๊ตฌํํด๋ณด๋ ์๊ฐํ๋ ๋งํผ ์ด๋ ต์ง๋ ์์๋ ์คํฌ๋กค ์ ๋๋ฉ์ด์
์ React๋ก ๊ฐ๋จํ๊ฒ ๊ตฌํํด๋ณธ ํ
hook์ผ๋ก ๋ถ๋ฆฌํด ์ฌ์ฌ์ฉ์ด ์ฉ์ดํ๋๋ก ๊ฐ์ ํ ์ฝ๋๋ฅผ ์๊ฐํฉ๋๋ค.
๐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ์ฝ๊ฒ ๊ตฌํํ๊ธฐ
[React] ๋ฆฌ์กํธ ์คํฌ๋กค ์ ๋๋ฉ์ด์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฐ๋ฐ
์คํฌ๋กค ์ ๋๋ฉ์ด์ ๊ธฐ๋ฅ์ ์ฝ๊ณ ๋น ๋ฅด๊ฒ ๊ตฌํํ๊ณ ์ถ์ผ์ ๋ถ๋ค์ ์ ๊ธ์ ์ฐธ๊ณ ํด์ฃผ์ธ์!
๐ IntersectionObserver
IntersectionObserver์ JavaScript์ API ์ค ํ๋๋ก,
์์๊ฐ ๋ทฐํฌํธ(ํ๋ฉด)์ ๋ํ๋๊ฑฐ๋ ์ฌ๋ผ์ง ๋๋ฅผ ๊ฐ์งํ๋ ์ญํ ์ ํฉ๋๋ค.
์ด๋ฅผ ํตํด ์คํฌ๋กค, ๋ ์ด์ง ๋ก๋ฉ(lazy loading), ๋ฌดํ ์คํฌ๋กค(infinite scrolling) ๋ฑ
๋ค์ํ ์ํฉ์์ ์์๋ค์ ๊ฐ์งํ๊ณ ํธ๋ฆฌ๊ฑฐํ๋๋ฐ ์ ์ฉํ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์๋๋ ๊ฐ๋จํ๊ฒ ๊ตฌํํด๋ณธ ์์ ์ ๋๋ค.
๐ styled-components๋ก ์ ๋๋ฉ์ด์ ๊ตฌํ
์ฐ์ IntersectionObserver๋ฅผ ์ค๋ช
ํ๊ธฐ ์์ ์ค์ง์ ์ผ๋ก ์์์ ์ ๋๋ฉ์ด์
ํจ๊ณผ๋ฅผ ์ ์ฉํ๋ ๋ถ๋ถ์
styled-component๋ก ๊ตฌํํ ์ฝ๋ ๋จผ์ ๋ณด๊ฒ ์ต๋๋ค.
import styled, { keyframes } from "styled-components";
const frameInAnimation = keyframes`
0% {
opacity: 0;
transform: translateX(-100%);
}
100%{
opacity: 1;
transform: translateX(0%);
}
`;
export const Container = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100vh;
&.frame-in {
animation: ${frameInAnimation} 2s forwards;
}
`;
styled-components์ keyframes
๋ก ํฌ๋ช
๋์ ์์น๊ฐ ์ด๋ํ๋ ์ ๋๋ฉ์ด์
์ ์์ฑํฉ๋๋ค.
๊ทธ ํ ์ ๋๋ฉ์ด์
์ด ์ ์ฉ๋ div ํ๊ทธ์ ์ํ๋ layout์ ์ก์์ฃผ๊ณ &.
์ ์ด์ฉํด frame-in
์ด๋ className์ด ์ ์ฉ๋๋ฉด ์ ๋๋ฉ์ด์
ํจ๊ณผ๊ฐ ์๋ํ๊ฒ ํด์ค๋๋ค.
animation ์์ฑ์๋ ์์์ ์์ฑํ
์ ๋๋ฉ์ด์
์ด๋ฆ(animation-name
)๊ณผ ๋๋ถ์ด
์ ๋๋ฉ์ด์
์๊ฐ(animation-duration
),
์ ๋๋ฉ์ด์
๊ฐ ์ ์ฉ(animation-fill-mode
)๋ฅผ ๋ฃ์ด์ฃผ์์ต๋๋ค.
์ด ์ค์์๋ animation-fill-mode
๋ ์์ฑ์ ์ ๋๋ฉ์ด์
์ด ๋๋ ํ์ ์ํ๋ฅผ ์ค์ ํฉ๋๋ค.
์ด ์์ฑ์ ๋ค์๊ณผ ๊ฐ์ ๊ฐ์ ๊ฐ์ง ์ ์๋๋ฐ forwards
๋ฅผ ๋ถ์ฌํด ์ ๋๋ฉ์ด์
์ ์ข
๋ฃ๋ ๋์ ์ํ๊ฐ ๊ณ์ ์ง์๋๋๋ก ํฉ๋๋ค.
- none
์ ๋๋ฉ์ด์ ์ด ๋๋ ํ ์ํ๋ฅผ ์ค์ ํ์ง ์์ต๋๋ค. - forwards
์ ๋๋ฉ์ด์ ์ด ๋๋ ํ ๊ทธ ์ง์ ์ ๊ทธ๋๋ก ์์ต๋๋ค. - backwards
์ ๋๋ฉ์ด์ ์ด ๋๋ ํ ์์์ ์ผ๋ก ๋์์ต๋๋ค. - both
์ ๋๋ฉ์ด์ ์ด์ ์ ๋ค ๊ฒฐ๊ณผ๋ฅผ ์กฐํฉํ์ฌ ์ค์ ํฉ๋๋ค. - inherit
์ ๋๋ฉ์ด์ ์ ์ํ๋ฅผ ์์ ์์ํํ ์์๋ฐ์ต๋๋ค.
๐ React์ IntersectionObserver ์ ์ฉ
import { useState, useRef, useEffect } from "react";
import { Container } from "./styled";
export default function App() {
const [isInViewport, setIsInViewport] = useState(false);
const ref = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (!ref.current) return; // ์์๊ฐ ์์ง ์ค๋น๋์ง ์์ ๊ฒฝ์ฐ ์ค๋จ
const callback = (entries: IntersectionObserverEntry[]) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// ์์๊ฐ ๋ทฐํฌํธ์ ๋ํ๋ฌ์ ๊ฒฝ์ฐ
setIsInViewport(true);
} else {
// ์์๊ฐ ๋ทฐํฌํธ๋ฅผ ๋ฒ์ด๋ ๊ฒฝ์ฐ
setIsInViewport(false);
}
});
};
const options = { root: null, rootMargin: "0px", threshold: 0 };
const observer = new IntersectionObserver(callback, options);
observer.observe(ref.current); // ์์ ๊ด์ฐฐ ์์
return () => {
observer.disconnect(); // ์ปดํฌ๋ํธ ์ธ๋ง์ดํธ ์ ๊ด์ฐฐ ์ค๋จ
};
}, []);
return (
<>
<Container>
<h1>์๋๋ก ์คํฌ๋กค ํ์ธ์</h1>
</Container>
<Container className={isInViewport ? "frame-in" : ""} ref={ref}>
<h1>์๋
ํ์ธ์</h1>
</Container>
</>
);
}
useEffect๋ฅผ ์ด์ฉํด IntersectionObserver์ ๊ฐ์ฒด(observer)๊ฐ ref๋ฅผ ๊ฐ์งํด์ callback์ ํธ๋ฆฌ๊ฑฐํ๋ ์ฝ๋์ ๋๋ค.
์๋ํ๋ ์์๋ฅผ ๋ฐ์ง์๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- observer๊ฐ ์คํฌ๋กค์ ๊ฐ์งํ๊ณ callback ํธ์ถ
- callback์ entry์์ ์ฌ์ฉ์์ viewport์ ref๋ฅผ ์ ์ฉํ Container๊ฐ ์ง์ ํ๋์ง ์ฌ๋ถ ํ๋จ
- ๋ง์ฝ ์ง์
ํ์ง ์์๋ค๋ฉด isInViewport ์ํ๋ฅผ false๋ก set,
์ง์ ํ๋ค๋ฉด true๋ก set - isInViewport๊ฐ true๊ฐ ๋๋ฉด Container์ className์ frame-in์ ์ฝ์
- ์ ๋๋ฉ์ด์ ์๋
์ ์ฝ๋์์ useEffect ๋ด ์คํฌ๋กค ๊ฐ์ง๋ฅผ ์ํ ์์๋ค์ ๊ธฐ๋ฅ๊ณผ ์๋ฏธ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๐ IntersectionObserverEntry
IntersectionObserverEntry
๋ IntersectionObserver
์ ์ฝ๋ฐฑ ํจ์ ๋ด์์
๊ฐ ๊ด์ฐฐ ๋์ ์์์ ๊ฐ์์ฑ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ ๊ฐ์ฒด์ด๋ฉฐ ์ค์ํ ์์ฑ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
isIntersecting
๊ด์ฐฐ ๋์ ์์๊ฐ ํ์ฌ ๋ทฐํฌํธ ๋ด์ ๋ณด์ด๋์ง ์ฌ๋ถ๋ฅผ ๋ํ๋ ๋๋ค. Boolean ๊ฐ์ผ๋ก ๋ฐํ๋ฉ๋๋ค.intersectionRatio
๊ด์ฐฐ ๋์ ์์์ ๋ทฐํฌํธ์ ๊ต์ฐจ ์์ญ์ ๋น์จ์ ๋ํ๋ ๋๋ค.
์ด ๊ฐ์ 0๋ถํฐ 1๊น์ง์ ๋ฒ์๋ฅผ ๊ฐ์ง๋ฉฐ, 0์ ๊ต์ฐจ ์์ญ์ด ์์์, 1์ ๊ด์ฐฐ ๋์ ์์๊ฐ ์์ ํ ๋ทฐํฌํธ ๋ด์ ์์์ ์๋ฏธํฉ๋๋ค.
๐ IntersectionObserver์ options
const options = { root: null, rootMargin: "0px", threshold: 0 };
const observer = new IntersectionObserver(callback, options);
IntersectionObserver
์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ์์ฑ์์ ๋ค์ด๊ฐ๋ options ์ธ์์ ๊ฐ๋ค์ ๋ค์๊ณผ ๊ฐ์ ์๋ฏธ๋ฅผ ์ง๋๊ณ ์์ต๋๋ค.
root
- ์ด ์ต์ ์ ๋ทฐํฌํธ๋ฅผ ๊ธฐ์ค์ผ๋ก ๊ด์ฐฐํ ๋์ ์์๋ค์ ์ ํํ๋ ์ญํ ์ ํฉ๋๋ค.
- ๊ธฐ๋ณธ๊ฐ์
null
๋ก, ๋ทฐํฌํธ ์ ์ฒด๊ฐ ๊ด์ฐฐ ๋์์ด ๋ฉ๋๋ค. - ๋ค๋ฅธ DOM ์์๋ฅผ ์ง์ ํ์ฌ ํด๋น ์์ ๋ด์์์ ๊ฐ์์ฑ์ ๊ฐ์งํ๋๋ก ์ค์ ํ ์ ์์ต๋๋ค.
rootMargin
- ๋ฃจํธ ์์์ ํ๊ฒ ์์ ์ฌ์ด์ ์ฌ๋ฐฑ(๋ง์ง)์ ์ค์ ํฉ๋๋ค.
- ๊ฐ์ CSS์ ๋ง์ง ๊ฐ๊ณผ ๋์ผํ ํ์์ผ๋ก ์ง์ ํ๋ฉฐ, "top right bottom left" ์์์ ๋๋ค.
- ๋ง์ง ๊ฐ์ ํตํด ์์๊ฐ ๋ทฐํฌํธ ๋ด์ ๋ค์ด์ฌ ๋ ์ผ๋ง๋ ์ด์ ์, ๋๋ ๋๊ฐ ๋ ์ผ๋ง๋ ๋๊ฐ๊ธฐ ์ ์ ๊ฐ์์ฑ์ ๊ฐ์งํ ์ง๋ฅผ ์กฐ์ ํ ์ ์์ต๋๋ค.
threshold
- ํ๊ฒ ์์์ ๊ฐ์์ฑ์ ํ๋จํ๋ ๊ธฐ์ค์ผ๋ก, ๊ฐ์ 0๋ถํฐ 1 ์ฌ์ด์ ์์์ ์ซ์์ ๋๋ค.
- ๊ธฐ๋ณธ๊ฐ์ 0์ผ๋ก, ํ๊ฒ ์์๊ฐ ๋ทฐํฌํธ์ ์กฐ๊ธ์ด๋ผ๋ ๋ค์ด์ค๋ฉด
isIntersecting
์ดtrue
๊ฐ ๋ฉ๋๋ค. - ๊ฐ์ด ์ปค์ง์๋ก ํ๊ฒ ์์๊ฐ ๋ทฐํฌํธ ๋ด์์ ๋ ๋ง์ ๋น์จ์ ์ฐจ์งํด์ผ ๊ฐ์์ฑ์ด ํ์ธ๋ฉ๋๋ค.
๐ IntersectionObserver๋ฅผ ์ฌ์ฉํ ๋ ๊ณ ๋ คํด์ผ ํ ์ฌํญ
์ ๋ง ํธ๋ฆฌํ๊ฒ ์์๋ค์ ๊ฐ์งํ ์ ์๋๋ก ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ IntersectionObserver
์ด์ง๋ง
๋ค์๊ณผ ๊ฐ์ ์ฌํญ๋ค์ ๊ณ ๋ คํ๋ฉฐ ์ ์ฉํด๋ด์ผ ํฉ๋๋ค.
- ๋ธ๋ผ์ฐ์ ์ง์
IntersectionObserver
๋ ๋๋ถ๋ถ์ ํ๋ ๋ธ๋ผ์ฐ์ ์์ ์ง์๋์ง๋ง, ์ค๋๋ ๋ธ๋ผ์ฐ์ ์์๋ ์ง์๋์ง ์์ ์ ์์ต๋๋ค. ์ด ๋๋ฌธ์ ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ง์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค. ํ์ํ๋ค๋ฉด ํด๋ฆฌํ(polyfill)์ ์ฌ์ฉํ์ฌ ๋ฏธ์ง์ ๋ธ๋ผ์ฐ์ ์์๋ ์ฌ์ฉํ ์ ์๋๋ก ํ ์ ์์ต๋๋ค. - ๋ ๋๋ง ์ฑ๋ฅ
IntersectionObserver
๋ฅผ ์ฌ์ฉํ์ฌ ์์์ ๊ฐ์์ฑ์ ๊ฐ์งํ๋ฉด์ ์์ฃผ ์คํ๋๋ ์ฝ๋ฐฑ ํจ์๋ ๋ ๋๋ง ์ฑ๋ฅ์ ์ํฅ์ ์ค ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ์ ์ ํthreshold
๊ฐ์ ์ค์ ํ์ฌ ์ฝ๋ฐฑ ํจ์๊ฐ ๋ถํ์ํ๊ฒ ์์ฃผ ์คํ๋์ง ์๋๋ก ์กฐ์ ํด์ผ ํฉ๋๋ค. - ์ฝ๋ฐฑ ํจ์ ๋น์ฉ
IntersectionObserver
์ ์ฝ๋ฐฑ ํจ์ ๋ด์์ ์คํ๋๋ ์ฝ๋๊ฐ ๋ฌด๊ฑฐ์ด ๊ฒฝ์ฐ,
๋ทฐํฌํธ ๋ด ์์์ ๊ฐ์์ฑ ๋ณ๊ฒฝ ์ ์ฑ๋ฅ ์ ํ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. - ๋ฉ๋ชจ๋ฆฌ ๋์
IntersectionObserver
์ ์ฝ๋ฐฑ ํจ์์์ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ์ง ์๋๋ก ์ฃผ์ํด์ผ ํฉ๋๋ค.
์ฝ๋ฐฑ ํจ์ ๋ด์์ ์ธ๋ถ ๋ณ์๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ ํด๋ก์ (closure) ๋ฌธ์ ์ ์ ์ํด์ผ ํฉ๋๋ค. - ๋์ ๋ณ๊ฒฝ: DOM ์์๊ฐ ๋์ ์ผ๋ก ์์ฑ๋๊ฑฐ๋ ๋ณ๊ฒฝ๋ ๊ฒฝ์ฐ, ํด๋น ์์์ ๊ฐ์์ฑ์ ์ฌ๋ฐ๋ฅด๊ฒ ๊ฐ์งํ ์ ์๋๋ก
IntersectionObserver
๋ฅผ ์ ๋ฐ์ดํธํด์ผ ํ ์ ์์ต๋๋ค. - ์์ ํฌ๊ธฐ
๊ฐ์์ฑ์ ํ๋จํ๋ ๋ฐ ๊ธฐ์ค์ด ๋๋ ์์์ ํฌ๊ธฐ๊ฐ ์๊ฑฐ๋ ์์ ๊ฒฝ์ฐ, ์ ํํ ๊ฐ์์ฑ์ ๊ฐ์งํ๊ธฐ ์ด๋ ค์ธ ์ ์์ต๋๋ค. - ์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง(SSR)
์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ,IntersectionObserver
๋ ๋ธ๋ผ์ฐ์ ํ๊ฒฝ์์๋ง ์๋ํ๋ฏ๋ก ํด๋ผ์ด์ธํธ ์ฌ์ด๋์์๋ง ์ฌ์ฉํ๋๋ก ์ฃผ์ํด์ผ ํฉ๋๋ค.
๐ hook๊ณผ ์ปดํฌ๋ํธ๋ก ๋ถ๋ฆฌํ๊ธฐ
์๋ฌด๋๋ ์ ์์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ๊ธฐ์๋ ๋ช ๊ฐ์ง ๋ถํธํ ์ ์ด ์์์ต๋๋ค.
- useEffect ์ฌ์ฉ์ผ๋ก ์ธํ ์ปดํฌ๋ํธ ์ฝ๋ ๋ผ์ธ ์ฆ๊ฐ ๋ฐ ๊ฐ๋ ์ฑ ์ ํ
- ์ ๋๋ฉ์ด์ ์ ์ฉ ์์๋ง๋ค ๋์ด๋๋ ref์ useEffect
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ ๋ถํธ ์ฌํญ๋ค์ ๊ฐ์ ํ๊ณ ScrollAnimation์ ๋ด๋นํด์ ์ฒ๋ฆฌํ hook์ ๋ง๋ค์์ต๋๋ค.
hook์์ ref๋ฅผ ๋ฐฐ์ด๋ก ๋ง๋ค์ด ์ฌ๋ฌ ์์๋ค์ ํ๊บผ๋ฒ์ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ๋ ๊ตฌํํด ๋ดค๋๋ฐ
๋ถ๋ณ์ฑ ๋๋ฌธ์ ๋ก์ง์ด ๋ถํ์ํ๊ฒ ๋ณต์กํด์ง๊ณ ๊ฐ๋
์ฑ๋ ์ข์ง ์์์ ธ ์ปดํฌ๋ํธ ๋ฐฉ์์ผ๋ก ๋ง๋ค์ด๋ดค์ต๋๋ค.
๐ useScrollAnimation.ts
import { useRef, useState, useEffect } from "react";
export const useScrollAnimation = () => {
const [isInViewport, setIsInViewport] = useState(false);
const ref = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (!ref.current) return; // ์์๊ฐ ์์ง ์ค๋น๋์ง ์์ ๊ฒฝ์ฐ ์ค๋จ
const callback = (entries: IntersectionObserverEntry[]) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// ์์๊ฐ ๋ทฐํฌํธ์ ๋ํ๋ฌ์ ๊ฒฝ์ฐ
setIsInViewport(true);
} else {
// ์์๊ฐ ๋ทฐํฌํธ๋ฅผ ๋ฒ์ด๋ ๊ฒฝ์ฐ
setIsInViewport(false);
}
});
};
const options = { root: null, rootMargin: "0px", threshold: 0 };
const observer = new IntersectionObserver(callback, options);
observer.observe(ref.current); // ์์ ๊ด์ฐฐ ์์
return () => {
observer.disconnect(); // ์ปดํฌ๋ํธ ์ธ๋ง์ดํธ ์ ๊ด์ฐฐ ์ค๋จ
};
}, []);
return { isInViewport, ref };
};
์์์ ๋ดค๋ ์คํฌ๋กค ๊ฐ์ง ๋ก์ง์ ๊ทธ๋๋ก hook์ผ๋ก ๋ถ๋ฆฌํ์ต๋๋ค.
hook์์ ref์ animation ์ ์ฉ ์ฌ๋ถ๋ฅผ ํ๋จํ๋ ๋ก์ง๊ณผ ์ํ๋ฅผ ์ ์ธํ๊ณ return ํด์ค๋๋ค.
๐ ScrollAnimationContainer.tsx
import { Container } from "./styled";
import { useScrollAnimation } from "./useScrollAnimation";
type PropsType = {
children: React.ReactNode;
};
export const ScrollAnimationContainer = ({ children }: PropsType) => {
const { ref, isInViewport } = useScrollAnimation();
return (
<Container ref={ref} className={isInViewport ? "frame-in" : ""}>
{children}
</Container>
);
};
๊ทธ ๋ค์ useScrollAnimation ํ ์ ์ด์ฉํด children์ ๊ฐ์ธ์ฃผ๊ธฐ๋ง ํ๋ฉด ์ ๋๋ฉ์ด์ ์ด ์ ์ฉ๋๋ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด์ค๋๋ค.
๐ ์ปดํฌ๋ํธ ์ฌ์ฉ
import { ScrollAnimationContainer } from "./ScrollAnimationContainer";
import { Container } from "./styled";
export default function App() {
return (
<>
<Container>
<h1>์๋๋ก ์คํฌ๋กค ํ์ธ์</h1>
</Container>
<ScrollAnimationContainer>
<h1>์๋
</h1>
</ScrollAnimationContainer>
<ScrollAnimationContainer>
<h1>ํ์ธ์</h1>
</ScrollAnimationContainer>
<ScrollAnimationContainer>
<h1>๋ฐ๊ฐ์ต๋๋ค</h1>
</ScrollAnimationContainer>
</>
);
}
์์์ ๋ชจ๋ ๋ก์ง์ ์ฒ๋ฆฌํด์ฃผ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด๋ ๋๋ถ์
์์๋ค์ ๊ฐํธํ๊ฒ wrapping ํ๊ธฐ๋ง ํด๋ ์คํฌ๋กค ์ ๋๋ฉ์ด์
์ด ์ ์ฉ๋๋ ๋ชจ์ต์ ํ์ธํ ์ ์์ต๋๋ค.
๊ทธ๋ผ ์์ธํ ์ฝ๋์ ๊ตฌ์กฐ๋ ์์ ์ฌ๋ ค๋ CodeSandBox๋ฅผ ์ฐธ๊ณ ํด์ฃผ์๊ณ
์คํฌ๋กค ์ ๋๋ฉ์ด์
์ ์ ์ฉํด ๋ ๋์ ์ธ ์น์ ๊ฐ๋ฐํด๋ด์ผ๊ฒ ์ต๋๋ค.
๋๊ธ