[Observer API ํํค์น๊ธฐ] 2. IntersectionObserver
โฐ 2024-06-14 (๊ธ) 01:37:29
IntersectionObserver
IntersectionObserver
๋ DOM๊ณผ ๋ทฐํฌํธ๋ฅผ ๋ค๋ฃจ๋ ๋ฐ ํนํ๋ ์ต์ ๋ฒ๋ค. DOM์ด ๋ทฐํฌํธ์ ๊ต์ฐจํ๋์ง ์ฌ๋ถ๋ฅผ ๊ฐ์งํ์ฌ DOM์ด ์ค์ ์ฌ์ฉ์ ํ๋ฉด์ ๋ณด์ฌ์ง๋์ง, ์๋๋ฉด ์จ๊ฒจ์ก๋์ง ๊ตฌ๋ถํ ์ ์๋ค.
TYPESCRIPT
1
const io = new IntersectionObserver(callback, options);
์ ์ฝ๋๋ก ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, callback
, options
๋ ๊ฐ์ง ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ๋๋ค.
callback
IntersectionObserver
์ ์ฝ๋ฐฑ ํ๋ผ๋ฏธํฐ๋ ์์๊ฐ ๋ทฐํฌํธ์ ๋ณด์ด๊ฑฐ๋, ๋ณด์ด์ง ์์ ๋ ๋์ํ๋ ์ด๋ฒคํธ ๋ฉ์๋๋ค.
IntersectionObserverCallback
ํ์
์ผ๋ก ์ ์ธ๋์ด ์์ผ๋ฉฐ, ์ฝ๋๋ก ํํํ๋ฉด ์๋์ ๊ฐ๋ค.
TYPESCRIPT
1
const io = new IntersectionObserver((entries, observer) => {});
IntersectionObserverEntry
entries
๋ IntersectionObserverEntry[]
ํ์
์ ๊ฐ๋๋ค. IntersectionObserver
์ ๋ฑ๋ก๋ ์์์ ์ด๋ฒคํธ ์ ๋ณด๋ฅผ ๋ฐฐ์ด ํํ๋ก ๋ฐํํ๋ค.
๋ฐฐ์ด ํ์
์ธ ์ด์ ๋ IntersectionObserver
์ ์ฌ๋ฌ DOM์ ๋ฑ๋กํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
๊ต์ฐจ ์ฌ๋ถ (isIntersecting)
isIntersecting
์์ฑ์ ๊ต์ฐจ ์ฌ๋ถ๋ฅผ boolean
๊ฐ์ผ๋ก ๋ฐํํด์ค๋ค. ์ง์ ๋ ์ต์
์ ๋ฐ๋ผ DOM์ ๋ณด์ฌ์ง ๊ฒฝ์ฐ true
, ์๋ ๊ฒฝ์ฐ false
๋ฅผ ๋ฐํํ๋ค.
๋์ DOM (target)
์ด๋ฒคํธ ์์ฑ์์ ํํ ๋ณด๋ target
์์ฑ๊ณผ ๋์ผํ๊ฒ, ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ์์ ์ ๋ฐํํ๋ค.
๊ต์ฐจ ๋น์จ (intersectionRatio)
DOM์ด ๊ต์ฐจ๋ ๋น์จ์ number
๋ก ๋ฐํํด์ค๋ค. ์ถํ ์ค๋ช
ํ options
์์ ์ง์ ํ๋ threshold
์ ๋ฐ์ ํ ์ฐ๊ด์ด ์๋ค.
threshold
์ ๋ฐฐ์ด์ ์ง์ ํ์ฌ ๋ค์์ ๋น์จ์ ์ง์ ํ์ ๊ฒฝ์ฐ, ์ด ๊ฐ์ ํตํด ๋น์จ์ ํน์ ํ ์ ์๋ค.
๋ฃจํธ DOM (rootBounds)
๋ทฐํฌํธ์ ๊ธฐ์ค์ธ DOM์ Element
ํํ๋ก ๋ฐํํด์ค๋ค. options
์์ root
์์ฑ์ ์ง์ ํ์ง ์์๋ค๋ฉด null
์ ๋ฐํํ๋ค.
์ด๋ฒคํธ ๋ฐ์ ์๊ฐ (time)
์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ์๊ฐ์ number
๊ฐ์ผ๋ก ๋ฐํํด์ค๋ค.
๋์ DOM์ ๋ฐ์ด๋๋ฆฌ (boundingClientRect)
๋์ DOM์ ๋ฐ์ด๋๋ฆฌ ์ ๋ณด๋ฅผ ๋ฐํํด์ค๋ค. x
, y
, width
, height
์ ๋ณด๋ฅผ ์ป์ ์ ์๋ค.
๋์ DOM์ ๊ต์ฐจ ๋ฐ์ด๋๋ฆฌ (intersectionRect)
๋์ DOM๊ณผ ๋ทฐํฌํธ์ ๊ต์ฐจ ์์ญ์ ๋ํ ๋ฐ์ด๋๋ฆฌ ์ ๋ณด๋ฅผ ๋ฐํํด์ค๋ค. boundingClientRect
์ ๋ง์ฐฌ๊ฐ์ง๋ก x
, y
, width
, height
์ ๋ณด๋ฅผ ์ป์ ์ ์์ง๋ง, ๋ทฐํฌํธ์ ๊ต์ฐจ๋ ์์ญ์ ํ์ ๋๋ค๋ ์ฐจ์ด์ ์ด ์๋ค.
observer
IntersectionObserver
๊ฐ์ฒด๋ฅผ ๋ฐํํด์ค๋ค. ์ด๋ฅผ ํตํด ์ฝ๋ฐฑ ๋ฉ์๋ ๋ด์์๋ IntersectionObserver
๋ฅผ ์ฐ์์ ์ผ๋ก ๋ค๋ฃฐ ์ ์๋ค.
options
options
์์ฑ์ ์ ๋ฌํ์ฌ IntersectionObserver
์ ์ธ๋ถ ๋์์ ์ ์ดํ ์ ์๋ค.
๋ฃจํธ DOM (root)
๋ทฐํฌํธ์ ๊ธฐ์ค์ด ๋๋ ๋ฃจํธ DOM์ ์ง์ ํ ์ ์๋ค. ๋ง์ฝ ์ง์ ํ์ง ์์ ๊ฒฝ์ฐ ํ์ฌ ๋ณด๊ณ ์๋ ๋ธ๋ผ์ฐ์ ์ ๋ทฐํฌํธ๊ฐ ์ง์ ๋๋ค.
๋ฃจํธ DOM์ margin (rootMargin)
๋ฃจํธ DOM์ ๊ธฐ์ค์ผ๋ก ๊ต์ฐจ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋๋ฐ, rootMargin
์ ํตํด ๊ฐ์ง ์์ญ์ ์กฐ์ ํ ์ ์๋ค.
์๋ฅผ ๋ค๋ฉด, root
๊ฐ ๋ทฐํฌํธ์ผ ๋, rootMargin
์ 100px
๋ก ์ง์ ํ๋ฉด ๋ทฐํฌํธ๋ณด๋ค 100px
๋จผ ๊ณณ์์๋ถํฐ ๊ต์ฐจ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ค.
์ด๋ฅผ ํตํด, ๋ฏธ๋ฆฌ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํค๋ ์์ ํ์ฉ์ด ๊ฐ๋ฅํ๋ค.
CSS์ margin
์์ฑ๊ณผ ๋์ผํ๊ฒ y x
ํน์ top right bottom left
๋ฐฉ์์ผ๋ก ์ง์ ํ๋ ๊ฒ๋ ๊ฐ๋ฅํ๋ค.
๋์ DOM์ ๊ต์ฐจ ๋น์จ (threshold)
๊ต์ฐจ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๊ธฐ ์ํ ๊ต์ฐจ ๋น์จ์ number
ํน์ number[]
๋ก ์ง์ ํ๋ฉฐ, ์์๋ 0
~ 1
์ฌ์ด์ ์ค์๋ฅผ ๊ฐ์ง๋ค. ์๋ฅผ๋ค์ด, 0.2
๋ ๊ต์ฐจ๋น์จ์ด 20%
์ผ ๊ฒฝ์ฐ ๊ต์ฐจ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํจ๋ค.
๋ฐฐ์ด๋ก ์ง์ ํ ๊ฒฝ์ฐ, ์ง์ ํ ๋น์จ๋ง๋ค ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ค. ์ฆ [ 0.2, 0.5, 0.8 ]
์ผ ๊ฒฝ์ฐ, 20%
, 50%
, 80%
๊ต์ฐจ ์ ๊ฐ๊ฐ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ ์์ด๋ค.
React์์ ์ปค์คํ
ํ
์ผ๋ก ๊ฐํธํ๊ฒ ์ฌ์ฉํ๊ธฐ
IntersectionObserver
๋ฅผ ์ฌ์ฉํ๋ค๋ณด๋ฉด, ์ ์ ํ ์๋ช
์ฃผ๊ธฐ์ ๋ง์ถฐ DOM์ ๋ฑ๋กํ๋ ๊ฒ์ด ์๊ฐ๋ณด๋ค ๋ฒ๊ฑฐ๋ก์์ ์ ์ ์๋ค.
์ปค์คํ
ํ
์ ํตํด IntersectionObserver
๋ฅผ ๋์ฑ ์ฝ๊ฒ ์ฌ์ฉํด๋ณด์.
TYPESCRIPT
1 2 3 4
export function useIntersectionObserver(): void { // }
์์ ๊ฐ์ด useIntersectionObserver
๋ผ๋ ๋ฉ์๋๋ฅผ ์ ์ํด๋ณด์.
useIntersectionObserver
๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์ ์๋์ ๊ฐ์ ์ธ ๊ฐ์ง ์์๊ฐ ํ์ํ๋ค.
- ๋์ DOM
- ์ฝ๋ฐฑ ๋ฉ์๋
- ์ต์
์ ์ธ ์์๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ์ ์ํ์. 1๋ฒ์ ๊ฒฝ์ฐ, ์ผ๋ฐ์ ์ผ๋ก HTMLElement
๊ฐ ํ์ํ์ง๋ง, ํด๋น ํ
์์๋ HTMLElement
ํ์
๋ฟ๋ง ์๋๋ผ, string
์ ๋ฐ์ #id
, .class
์ ๊ฐ์ ์ ํ์๋ก ํ๊ทธ๋ฅผ ํ์ฉํ ์ ์๋๋ก ๋ง๋ค ๊ฒ์ด๋ค.
TYPESCRIPT
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
import { 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
์ ์ ์ธํ๊ณ ์ฝ๋ฐฑ ๋ฉ์๋์ ์ต์
์ ํ ๋นํ๋ค.
TYPESCRIPT
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
import { 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()
๋ฉ์๋๋ฅผ ํตํด ํ๊ทธ๋ฅผ ๋ฑ๋กํ ์ ์๋ค.
TYPESCRIPT
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 32 33 34 35 36 37 38 39 40 41 42 43
import { 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
๋ฅผ ์ ๊ฑฐํ ์ ์๋ค.
์ ์ฒด ์ฝ๋
TYPESCRIPT
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
import { 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๋ก ๊ฐ๋จํ ์์๋ฅผ ๊ตฌํํ๋ค. CodeSandbox์ ๋ ๋๋ง ๋ฐฉ์ ๋๋ฌธ์ธ์ง, rootMargin
์ ์ ๋๋ก ๋์ํ์ง ์๋ ๋ฏํ๋ค.
๋ง์น๋ฉฐ
์ด์ ๊ฐ์ด IntersectionObserver
๋ฅผ ํ์ฉํ๋ฉด DOM์ ๋ทฐํฌํธ ๊ด๋ จ ์์๋ฅผ ์ฝ๊ฒ ๋ค๋ฃฐ ์ ์๋ค.
์ด API๋ ์ธํผ๋ํฐ ์คํฌ๋กค, ์ ๋๋งค์ด์ ๋์ ํธ๋ฆฌ๊ฑฐ ๋ฑ, ๊ธฐ์กด์ Event Driven์ผ๋ก ๊ตฌํํ๊ธฐ ๋ณต์กํ ๊ธฐ๋ฅ์ ์ฝ๊ฒ ๊ฐ๋ฐํ ์ ์๋ค.
๋ค์ ์ฅ์์ ResizeObserver
์ ๋ํด ๋ค๋ค๋ณด์.
๐ท๏ธ ํ๊ทธ
์ฝ์ด์ฃผ์ ์ ๊ณ ๋ง์์!
๋์์ด ๋์ จ๋ค๋ฉด, ๊ณต๊ฐ์ด๋ ๋๊ธ์ ๋ฌ์์ฃผ์๋ ๊ฑด ์ด๋ค๊ฐ์?
๋ธ๋ก๊ทธ ์ด์์ ํฐ ํ์ด ๋ฉ๋๋ค.