[Observer API ํํค์น๊ธฐ] 2. IntersectionObserver
- 1. IntersectionObserver
- 1.1. callback
- 1.1.1. IntersectionObserverEntry
- 1.1.1.1. ๊ต์ฐจ ์ฌ๋ถ (isIntersecting)
- 1.1.1.2. ๋์ DOM (target)
- 1.1.1.3. ๊ต์ฐจ ๋น์จ (intersectionRatio)
- 1.1.1.4. ๋ฃจํธ DOM (rootBounds)
- 1.1.1.5. ์ด๋ฒคํธ ๋ฐ์ ์๊ฐ (time)
- 1.1.1.6. ๋์ DOM์ ๋ฐ์ด๋๋ฆฌ (boundingClientRect)
- 1.1.1.7. ๋์ DOM์ ๊ต์ฐจ ๋ฐ์ด๋๋ฆฌ (intersectionRect)
- 1.1.2. observer
- 1.2. options
- 1.2.1. ๋ฃจํธ DOM (root)
- 1.2.2. ๋ฃจํธ DOM์ margin (rootMargin)
- 1.2.3. ๋์ DOM์ ๊ต์ฐจ ๋น์จ (threshold)
- 2. React์์ ์ปค์คํ ํ ์ผ๋ก ๊ฐํธํ๊ฒ ์ฌ์ฉํ๊ธฐ
- 2.1. ์ ์ฒด ์ฝ๋
- 2.2. CodeSandbox๋ฅผ ํตํ ์์
- 3. ๋ง์น๋ฉฐ
IntersectionObserver๋ DOM๊ณผ ๋ทฐํฌํธ๋ฅผ ๋ค๋ฃจ๋ ๋ฐ ํนํ๋ ์ต์ ๋ฒ๋ค. DOM์ด ๋ทฐํฌํธ์ ๊ต์ฐจํ๋์ง ์ฌ๋ถ๋ฅผ ๊ฐ์งํ์ฌ DOM์ด ์ค์ ์ฌ์ฉ์ ํ๋ฉด์ ๋ณด์ฌ์ง๋์ง, ์๋๋ฉด ์จ๊ฒจ์ก๋์ง ๊ตฌ๋ถํ ์ ์๋ค.
TYPESCRIPT1const io = new IntersectionObserver(callback, options);
์ ์ฝ๋๋ก ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, callback, options ๋ ๊ฐ์ง ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ๋๋ค.
IntersectionObserver์ ์ฝ๋ฐฑ ํ๋ผ๋ฏธํฐ๋ ์์๊ฐ ๋ทฐํฌํธ์ ๋ณด์ด๊ฑฐ๋, ๋ณด์ด์ง ์์ ๋ ๋์ํ๋ ์ด๋ฒคํธ ๋ฉ์๋๋ค.
IntersectionObserverCallback ํ์
์ผ๋ก ์ ์ธ๋์ด ์์ผ๋ฉฐ, ์ฝ๋๋ก ํํํ๋ฉด ์๋์ ๊ฐ๋ค.
TYPESCRIPT1const io = new IntersectionObserver((entries, observer) => {});
entries๋ IntersectionObserverEntry[] ํ์
์ ๊ฐ๋๋ค. IntersectionObserver์ ๋ฑ๋ก๋ ์์์ ์ด๋ฒคํธ ์ ๋ณด๋ฅผ ๋ฐฐ์ด ํํ๋ก ๋ฐํํ๋ค.
๋ฐฐ์ด ํ์
์ธ ์ด์ ๋ IntersectionObserver์ ์ฌ๋ฌ DOM์ ๋ฑ๋กํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
isIntersecting
isIntersecting ์์ฑ์ ๊ต์ฐจ ์ฌ๋ถ๋ฅผ boolean ๊ฐ์ผ๋ก ๋ฐํํด์ค๋ค. ์ง์ ๋ ์ต์
์ ๋ฐ๋ผ DOM์ ๋ณด์ฌ์ง ๊ฒฝ์ฐ true, ์๋ ๊ฒฝ์ฐ false๋ฅผ ๋ฐํํ๋ค.
target
์ด๋ฒคํธ ์์ฑ์์ ํํ ๋ณด๋ target ์์ฑ๊ณผ ๋์ผํ๊ฒ, ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ์์ ์ ๋ฐํํ๋ค.
intersectionRatio
DOM์ด ๊ต์ฐจ๋ ๋น์จ์ number๋ก ๋ฐํํด์ค๋ค. ์ถํ ์ค๋ช
ํ options์์ ์ง์ ํ๋ threshold์ ๋ฐ์ ํ ์ฐ๊ด์ด ์๋ค.
threshold์ ๋ฐฐ์ด์ ์ง์ ํ์ฌ ๋ค์์ ๋น์จ์ ์ง์ ํ์ ๊ฒฝ์ฐ, ์ด ๊ฐ์ ํตํด ๋น์จ์ ํน์ ํ ์ ์๋ค.
rootBounds
๋ทฐํฌํธ์ ๊ธฐ์ค์ธ DOM์ Element ํํ๋ก ๋ฐํํด์ค๋ค. options์์ root ์์ฑ์ ์ง์ ํ์ง ์์๋ค๋ฉด null์ ๋ฐํํ๋ค.
์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ์๊ฐ์ number ๊ฐ์ผ๋ก ๋ฐํํด์ค๋ค.
time
๋์ DOM์ ๋ฐ์ด๋๋ฆฌ ์ ๋ณด๋ฅผ ๋ฐํํด์ค๋ค. x, y, width, height ์ ๋ณด๋ฅผ ์ป์ ์ ์๋ค.
intersectionRect
๋์ DOM๊ณผ ๋ทฐํฌํธ์ ๊ต์ฐจ ์์ญ์ ๋ํ ๋ฐ์ด๋๋ฆฌ ์ ๋ณด๋ฅผ ๋ฐํํด์ค๋ค. boundingClientRect์ ๋ง์ฐฌ๊ฐ์ง๋ก x, y, width, height ์ ๋ณด๋ฅผ ์ป์ ์ ์์ง๋ง, ๋ทฐํฌํธ์ ๊ต์ฐจ๋ ์์ญ์ ํ์ ๋๋ค๋ ์ฐจ์ด์ ์ด ์๋ค.
IntersectionObserver ๊ฐ์ฒด๋ฅผ ๋ฐํํด์ค๋ค. ์ด๋ฅผ ํตํด ์ฝ๋ฐฑ ๋ฉ์๋ ๋ด์์๋ IntersectionObserver๋ฅผ ์ฐ์์ ์ผ๋ก ๋ค๋ฃฐ ์ ์๋ค.
options ์์ฑ์ ์ ๋ฌํ์ฌ IntersectionObserver์ ์ธ๋ถ ๋์์ ์ ์ดํ ์ ์๋ค.
root
๋ทฐํฌํธ์ ๊ธฐ์ค์ด ๋๋ ๋ฃจํธ DOM์ ์ง์ ํ ์ ์๋ค. ๋ง์ฝ ์ง์ ํ์ง ์์ ๊ฒฝ์ฐ ํ์ฌ ๋ณด๊ณ ์๋ ๋ธ๋ผ์ฐ์ ์ ๋ทฐํฌํธ๊ฐ ์ง์ ๋๋ค.
rootMargin
๋ฃจํธ DOM์ ๊ธฐ์ค์ผ๋ก ๊ต์ฐจ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋๋ฐ, rootMargin์ ํตํด ๊ฐ์ง ์์ญ์ ์กฐ์ ํ ์ ์๋ค.
์๋ฅผ ๋ค๋ฉด, root๊ฐ ๋ทฐํฌํธ์ผ ๋, rootMargin์ 100px๋ก ์ง์ ํ๋ฉด ๋ทฐํฌํธ๋ณด๋ค 100px ๋จผ ๊ณณ์์๋ถํฐ ๊ต์ฐจ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ค.
์ด๋ฅผ ํตํด, ๋ฏธ๋ฆฌ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํค๋ ์์ ํ์ฉ์ด ๊ฐ๋ฅํ๋ค.
CSS์ margin ์์ฑ๊ณผ ๋์ผํ๊ฒ y x ํน์ top right bottom left ๋ฐฉ์์ผ๋ก ์ง์ ํ๋ ๊ฒ๋ ๊ฐ๋ฅํ๋ค.
threshold
๊ต์ฐจ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๊ธฐ ์ํ ๊ต์ฐจ ๋น์จ์ number ํน์ number[]๋ก ์ง์ ํ๋ฉฐ, ์์๋ 0 ~ 1 ์ฌ์ด์ ์ค์๋ฅผ ๊ฐ์ง๋ค. ์๋ฅผ๋ค์ด, 0.2๋ ๊ต์ฐจ๋น์จ์ด 20%์ผ ๊ฒฝ์ฐ ๊ต์ฐจ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํจ๋ค.
๋ฐฐ์ด๋ก ์ง์ ํ ๊ฒฝ์ฐ, ์ง์ ํ ๋น์จ๋ง๋ค ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ค. ์ฆ [ 0.2, 0.5, 0.8 ]์ผ ๊ฒฝ์ฐ, 20%, 50%, 80% ๊ต์ฐจ ์ ๊ฐ๊ฐ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ ์์ด๋ค.
IntersectionObserver๋ฅผ ์ฌ์ฉํ๋ค๋ณด๋ฉด, ์ ์ ํ ์๋ช
์ฃผ๊ธฐ์ ๋ง์ถฐ DOM์ ๋ฑ๋กํ๋ ๊ฒ์ด ์๊ฐ๋ณด๋ค ๋ฒ๊ฑฐ๋ก์์ ์ ์ ์๋ค.
์ปค์คํ
ํ
์ ํตํด IntersectionObserver๋ฅผ ๋์ฑ ์ฝ๊ฒ ์ฌ์ฉํด๋ณด์.
TYPESCRIPT1 2 3 4export function useIntersectionObserver(): void { // }
์์ ๊ฐ์ด useIntersectionObserver๋ผ๋ ๋ฉ์๋๋ฅผ ์ ์ํด๋ณด์.
useIntersectionObserver๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์ ์๋์ ๊ฐ์ ์ธ ๊ฐ์ง ์์๊ฐ ํ์ํ๋ค.
- ๋์ DOM
- ์ฝ๋ฐฑ ๋ฉ์๋
- ์ต์
์ ์ธ ์์๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ์ ์ํ์. 1๋ฒ์ ๊ฒฝ์ฐ, ์ผ๋ฐ์ ์ผ๋ก HTMLElement๊ฐ ํ์ํ์ง๋ง, ํด๋น ํ
์์๋ HTMLElement ํ์
๋ฟ๋ง ์๋๋ผ, string์ ๋ฐ์ #id, .class์ ๊ฐ์ ์ ํ์๋ก ํ๊ทธ๋ฅผ ํ์ฉํ ์ ์๋๋ก ๋ง๋ค ๊ฒ์ด๋ค.
TYPESCRIPT1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21import { useEffect } from "react"; export type UseIntersectionObserverCallback = ( entry: IntersectionObserverEntry ) => void; /** * IntersectionObserver ์ ์ฉ ํ ๋ฉ์๋ * * @param {Element | string | null} ref: Element * @param {UseIntersectionObserverCallback} callback: ์ฝ๋ฐฑ ๋ฉ์๋ * @param {IntersectionObserverInit} options: ์ต์ */ export function useIntersectionObserver( ref: Element | string | null, callback: UseIntersectionObserverCallback, options?: IntersectionObserverInit ): void { // }
ํ๋ผ๋ฏธํฐ์ ์ ์๋ ์์ ๊ฐ๋ค. ์ดํ IntersectionObserver์ ์ ์ธํ๊ณ ์ฝ๋ฐฑ ๋ฉ์๋์ ์ต์
์ ํ ๋นํ๋ค.
TYPESCRIPT1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25import { useEffect } from "react"; export type UseIntersectionObserverCallback = ( entry: IntersectionObserverEntry ) => void; /** * IntersectionObserver ์ ์ฉ ํ ๋ฉ์๋ * * @param {Element | string | null} ref: Element * @param {UseIntersectionObserverCallback} callback: ์ฝ๋ฐฑ ๋ฉ์๋ * @param {IntersectionObserverInit} options: ์ต์ */ export function useIntersectionObserver( ref: Element | string | null, callback: UseIntersectionObserverCallback, options?: IntersectionObserverInit ): void { useEffect(() => { const io = new IntersectionObserver((entries) => { entries.forEach(callback); }, options); }, [ref, callback, options]); }
์์ ๊ฐ์ด IntersectionObserver์ ์ด๊ธฐํํ์ฌ io ๋ณ์๋ก ํ ๋นํ๋ค.
์ดํ ref๋ก ์ง์ ํ DOM์ ํ ๋นํ๋ค.
ref๋ ์ธ ๊ฐ์ง ์ํ์ ๋ฐ๋ผ ์๋์ ๊ฐ์ ๋ถ๊ธฐ๋ฅผ ๊ฑฐ์น๋ค.
null์ผ ๊ฒฝ์ฐ -> ํจ์คํ๋ค.string์ผ ๊ฒฝ์ฐ ->document.querySelector๋ฉ์๋๋ก ํ๊ทธ๋ฅผ ์ ํํ ๋ค, ํด๋น ํ๊ทธ๋ฅผ ๋ฑ๋กํ๋ค.HTMLElement์ผ ๊ฒฝ์ฐ -> ์ด๋ฏธElement์ด๋ฏ๋ก, ์ฆ์ ๋ฑ๋กํ๋ค.
typeof ref === "string" ๊ตฌ๋ฌธ์ ํตํด ref๊ฐ ๋ฌธ์์ด์ธ์ง๋ฅผ ํ๋ณํ์ฌ ๊ตฌํํ๋ค.
io.observe() ๋ฉ์๋๋ฅผ ํตํด ํ๊ทธ๋ฅผ ๋ฑ๋กํ ์ ์๋ค.
TYPESCRIPT1 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 32 33 34 35 36 37 38 39 40 41 42 43import { useEffect } from "react"; export type UseIntersectionObserverCallback = ( entry: IntersectionObserverEntry ) => void; /** * IntersectionObserver ์ ์ฉ ํ ๋ฉ์๋ * * @param {Element | string | null} ref: Element * @param {UseIntersectionObserverCallback} callback: ์ฝ๋ฐฑ ๋ฉ์๋ * @param {IntersectionObserverInit} options: ์ต์ */ export function useIntersectionObserver( ref: Element | string | null, callback: UseIntersectionObserverCallback, options?: IntersectionObserverInit ): void { useEffect(() => { const io = new IntersectionObserver((entries) => { entries.forEach(callback); }, options); // DOM์ด ์ ํจํ ๊ฒฝ์ฐ if (ref) { // ref๊ฐ ๋ฌธ์์ด์ผ ๊ฒฝ์ฐ if (typeof ref === "string") { const tag = document.querySelector(ref); // ํ๊ทธ๊ฐ ์ ํจํ ๊ฒฝ์ฐ if (tag) { io.observe(tag); } } // DOM์ผ ๊ฒฝ์ฐ else { io.observe(ref); } } }, [ref, callback, options]); }
๋ง์ง๋ง์ผ๋ก, ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง ๋ ๋๋ง๋ค IntersectionObserver์ ๋ฑ๋ก์ด ์ค์ฒฉ๋์ง ์๋๋ก, ์ด๊ธฐํ ์ฝ๋๋ฅผ ๋ฃ์ด์ค๋ค.
io.disconnect() ๋ฉ์๋๋ฅผ ํตํด IntersectionObserver๋ฅผ ์ ๊ฑฐํ ์ ์๋ค.
TYPESCRIPT1 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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47import { useEffect } from "react"; export type UseIntersectionObserverCallback = ( entry: IntersectionObserverEntry ) => void; /** * IntersectionObserver ์ ์ฉ ํ ๋ฉ์๋ * * @param {Element | string | null} ref: Element * @param {UseIntersectionObserverCallback} callback: ์ฝ๋ฐฑ ๋ฉ์๋ * @param {IntersectionObserverInit} options: ์ต์ */ export function useIntersectionObserver( ref: Element | string | null, callback: UseIntersectionObserverCallback, options?: IntersectionObserverInit ): void { useEffect(() => { const io = new IntersectionObserver((entries) => { entries.forEach(callback); }, options); // DOM์ด ์ ํจํ ๊ฒฝ์ฐ if (ref) { // ref๊ฐ ๋ฌธ์์ด์ผ ๊ฒฝ์ฐ if (typeof ref === "string") { const tag = document.querySelector(ref); // ํ๊ทธ๊ฐ ์ ํจํ ๊ฒฝ์ฐ if (tag) { io.observe(tag); } } // DOM์ผ ๊ฒฝ์ฐ else { io.observe(ref); } } return () => { io.disconnect(); }; }, [ref, callback, options]); }
์ ์ฒด ์ฝ๋๋ ์์ ๊ฐ๋ค.
CodeSandbox๋ก ๊ฐ๋จํ ์์๋ฅผ ๊ตฌํํ๋ค. CodeSandbox์ ๋ ๋๋ง ๋ฐฉ์ ๋๋ฌธ์ธ์ง, rootMargin์ ์ ๋๋ก ๋์ํ์ง ์๋ ๋ฏํ๋ค.
์ด์ ๊ฐ์ด IntersectionObserver๋ฅผ ํ์ฉํ๋ฉด DOM์ ๋ทฐํฌํธ ๊ด๋ จ ์์๋ฅผ ์ฝ๊ฒ ๋ค๋ฃฐ ์ ์๋ค.
์ด API๋ ์ธํผ๋ํฐ ์คํฌ๋กค, ์ ๋๋งค์ด์ ๋์ ํธ๋ฆฌ๊ฑฐ ๋ฑ, ๊ธฐ์กด์ Event Driven์ผ๋ก ๊ตฌํํ๊ธฐ ๋ณต์กํ ๊ธฐ๋ฅ์ ์ฝ๊ฒ ๊ฐ๋ฐํ ์ ์๋ค.
๋ค์ ์ฅ์์ ResizeObserver์ ๋ํด ๋ค๋ค๋ณด์.