일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 리액트 네이티브
- firebase 라이브러리
- aws bucket 정책
- img upload
- 리엑트 네이티브 아이콘
- react native CLI
- react native font
- AWS Access Key
- babel.config.js
- react native
- GIT
- 에러
- react native 세팅
- s3 upload
- 리액트
- js
- 백준
- 문자열 대소문자
- 문자열 대소문자 구별
- react native picker
- 리액트 네이티브 에러
- fire base
- error
- AWS
- react native 개발
- Next.js
- PongWorld
- React
- Access Key 생성
- Project
- Today
- Total
밝을희 클태
[NextJS] 스크롤 애니메이션 구현하기 IntersectionObserver API 본문
IntersectionObserver API
는 특정요소가 뷰포트와 교차하는지 비동기적으로 관찰하는 데 사용된다. 이걸로 스크롤 애니메이션을 구현할 수 있다.
코드 :
function Item({ children }: { children: React.ReactNode }) {
const ref = useRef<HTMLDivElement | null>(null);
const [visible, setVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(({ target, isIntersecting }) => {
if (target === ref.current) setVisible(isIntersecting);
});
},
{
threshold: 1,
}
);
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return (
<div ref={ref}>
{children}
<style jsx>{`
div {
opacity: ${visible ? 1 : 0};
transform: ${visible ? "translateY(0)" : "translateY(30px)"};
transition: opacity 0.5s ease, transform 1s ease;
}
`}</style>
</div>
);
}
코드 설명 :
외부 요소와 연결하기 위해 ref 를 초기화해준다. typescript를 사용해서 참조하려는 타입을 명시해 준다.
const ref = useRef<HTMLDivElement | null>(null);
요소가 뷰포트와 만났을 때 확인하기 위한 useState
const [visible, setVisible] = useState(false);
IntersectionObserver 초기화
entries는 감시되고 있는 요소의 정보를 담고 있는 배열이다. entries의 target(감시되고 있는 요소 자체), isIntersecting(요소가 뷰포트에 들어왔으면(true), 아니면(false) )
target과 ref의 DOM을 비교하면서 같으면 visible의 상태를 업데이트, 이때 target이 뷰포트와 교차한 상태면 (true) 아니면 (false)
그리고 threshold는 요소가 뷰포트에 얼마나 보이면 isIntersecting을 true로 바꿀지에 대한 값이다.
IntersectionObserver을 초기화해주고 ref.current가 있으면
if (ref.current) observer.observe(ref.current);
observer에 감시 대상을 추가해 준다.
그리고 컴포넌트가 언마운트 되면 아래 코드로 감시를 중단한다 메모리 누수를 방지하는데 필요하다.
return () => observer.disconnect();
new IntersectionObserver(callback, options)
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(({ target, isIntersecting }) => {
if (target === ref.current) setVisible(isIntersecting);
});
},
{
threshold: 1,
}
);
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
처음엔 이렇게 했는데 이렇게 하면 Item 컴포넌트 하나당 각자의 독립적인 IntersectionObserver를 가지게 된다 복잡한 로직이나 개별적인 애니메이션을 사용할 때는 이렇게 해야 되겠지만 위 방법을 사용하면 성능이 저하된다.
로직이 같은 컴포넌트를 공유할 때는 부모 컴포넌트에서 하나의 IntersectionObserver를 사용하는 게 성능에 좋다.
하나의 IntersectionObserver 사용
코드 :
function Item({
children,
visible,
}: {
children: React.ReactNode;
visible: boolean;
}) {
return (
<div>
<div
style={{
opacity: visible ? 1 : 0,
transform: visible ? "translateY(0)" : "translateY(30px)",
transition: "opacity 0.5s ease, transform 1s ease",
}}
>
{children}
</div>
</div>
);
}
export default function AboutScreen() {
const [visibleItems, setVisibleItems] = useState(new Set());
useEffect(() => {
// IntersectionObserver 인스턴스 생성
const observer = new IntersectionObserver(
(entries) => {
// 각 관찰 대상의 변화에 대한 콜백 함수
entries.forEach(({ target, isIntersecting }) => {
// 각 관찰 대상에 대한 처리
const id = target.getAttribute("data-id"); // 각 대상의 data-id 속성 값 추출
if (isIntersecting) {
// 요소가 뷰포트에 들어왔는지 확인
// visibleItems 상태를 업데이트하여 요소의 id 추가
setVisibleItems((prevItems) => new Set(prevItems).add(id));
} else {
// visibleItems 상태를 업데이트하여 요소의 id 제거
setVisibleItems((prevItems) => {
const newItems = new Set(prevItems);
newItems.delete(id);
return newItems;
});
}
});
},
{
threshold: 0.9, // 관찰 기준점 설정 (90% 요소가 보여야 감지)
}
);
// 모든 .item 요소를 관찰 대상으로 추가
document.querySelectorAll(".item").forEach((item) => {
observer.observe(item);
});
// 컴포넌트가 언마운트 될 때 관찰을 중단
return () => observer.disconnect();
}, []);
return (
<div className="container">
<div className="image-container">
<Image
src={starBack}
alt="starBackground"
quality={100}
layout="fill"
/>
</div>
{Array.from({ length: 4 }, (_, index) => index).map((index) => (
<Item key={index} visible={visibleItems.has(`item-${index}`)}>
<div
className="item"
data-id={`item-${index}`}
style={{
width: 200,
height: 200,
background: index % 2 === 0 ? "red" : "blue",
}}
></div>
</Item>
))}
</div>
'NextJS' 카테고리의 다른 글
[ Next.JS ] Input 태그에서 HEIC 확장자를 jpeg로 변환하기 (0) | 2024.06.15 |
---|---|
[ Next.JS ] AWS S3에 이미지(image)를 업로드, 미리보기 (0) | 2024.06.15 |
[ Next.js] Image태그의 sizes (1) | 2024.06.12 |