useVisibleElement
스크롤 시 해당 DOM 요소가 노출됬는지 여부 감지
Hooks(Legacy)
tsx1import { useCallback, useEffect, useRef, useState } from 'react';
2
3export default function useVisibleElement<T extends HTMLElement>(): {
4 ref: React.RefObject<T>;
5 isVisible: boolean;
6} {
7 const [isVisible, setIsVisible] = useState<boolean>(false);
8 const ref = useRef<T>(null);
9
10 const checkVisibility = useCallback(() => {
11 if (ref.current) {
12 const rect = ref.current.getBoundingClientRect();
13
14 if (rect.top < window.innerHeight - 300 && rect.bottom > 0) {
15 setIsVisible(true);
16 } else {
17 setIsVisible(false);
18 }
19 }
20 }, []);
21
22 useEffect(() => {
23 window.addEventListener('scroll', checkVisibility);
24 window.addEventListener('touchmove', checkVisibility);
25
26 checkVisibility();
27
28 return () => {
29 window.removeEventListener('scroll', checkVisibility);
30 window.removeEventListener('touchmove', checkVisibility);
31 };
32 }, [checkVisibility]);
33
34 return { ref, isVisible };
35}
Hooks(intersectionObserver API 사용)
intersectionObserver API를 사용하며 Hook의 기능을 추가/변경했다.
Props
- activeClass: 해당 요소가 노출될 때 추가할 클래스명
- key: 추적 요소의 고유 키값 부여
Return
- ref: 추적할 요소를 가진 container(div tag)를 ref로 지정하기 위함
- activeKey: 현재 노출된 요소의 키값
- activeElement: 현재 노출된 Element 요소
tsx1import { useEffect, useRef, useState } from 'react';
2
3interface Props {
4 key?: string;
5 activeClass?: string;
6}
7
8export default function useVisibleElement(props?: Props) {
9 const { key = 'visible-element', activeClass } = props || {};
10
11 const [activeKey, setActiveKey] = useState<string>('');
12 const [activeElement, setActiveElement] = useState<Element | null>(null);
13 const ref = useRef<HTMLDivElement>(null);
14
15 const callback = (entries: IntersectionObserverEntry[]) => {
16 entries.forEach((entry) => {
17 const key = entry.target.getAttribute('data-key');
18
19 if (entry.isIntersecting) {
20 if (activeClass) {
21 entry.target.classList.add(activeClass);
22 }
23
24 setActiveKey(key || '');
25 setActiveElement(entry.target);
26 } else if (activeClass) {
27 entry.target.classList.remove(activeClass);
28 }
29 });
30 };
31
32 useEffect(() => {
33 const intersectionObserver = new IntersectionObserver(callback);
34
35 const scrollItems = Array.from(ref.current?.children as HTMLCollection);
36
37 if (!scrollItems?.length) return () => {};
38
39 scrollItems.forEach((item, index) => {
40 item.setAttribute('data-key', `${key}-${index}`);
41 intersectionObserver.observe(item);
42 });
43
44 return () => {
45 intersectionObserver.disconnect();
46 };
47 }, []);
48
49 return {
50 ref,
51 activeKey,
52 activeElement,
53 };
54}
Usage
tsx1function Component() {
2 const { ref, activeKey, activeElement } = useVisibleElement({
3 activeClass: 'active',
4 });
5 console.log('activeKey', activeKey); // scroll-item-0
6 console.log('activeElement', activeElement); // <section><p>section 1</p></section>
7
8 return (
9 <div ref={ref}>
10 <section>
11 <p>section 1</p>
12 </section>
13 <section>
14 <p>section 2</p>
15 </section>
16 <section>
17 <p>section 3</p>
18 </section>
19 <section>
20 <p>section 4</p>
21 </section>
22 </div>
23 );
24}