๐Ÿ’ป์›น(Web)

ํ”„๋ก ํŠธ์—”๋“œ ์„ฑ๋Šฅ ์ตœ์ ํ™”

stonesy 2024. 4. 12. 00:24
728x90

ํ”„๋ก ํŠธ์—”๋“œ ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐฉ๋ฒ•์„ ๋กœ๋”ฉ ์ตœ์ ํ™”, ๋ Œ๋”๋ง ์ตœ์ ํ™” ๊ด€์ ์—์„œ ์ •๋ฆฌํ•ด๋ณด์•˜๋‹ค.

 

๐Ÿ“–๋กœ๋”ฉ ์ตœ์ ํ™” ๋ฐฉ๋ฒ•

๐Ÿ”Žํ™”๋ฉด์ด ๊ทธ๋ ค์ง€๊ธฐ๊นŒ์ง€ ๊ณผ์ •

domContentLoaded

domContentEventLoaded๋Š” ์›น ํŽ˜์ด์ง€์˜ DOM ํŠธ๋ฆฌ๊ฐ€ ์™„์ „ํžˆ ๋กœ๋“œ๋˜๊ณ  ํŒŒ์‹ฑ๋˜์—ˆ์„ ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ์ด๋ฒคํŠธ์ด๋‹ค. domContentEventLoaded ์ด๋ฒคํŠธ๋Š” ์ด๋ฏธ์ง€, ์Šคํƒ€์ผ์‹œํŠธ ๋ฐ ํ•˜์œ„ ํ”„๋ ˆ์ž„ ๋กœ๋”ฉ์„ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋ฐœ์ƒํ•˜๋ฉฐ, ์›น ํŽ˜์ด์ง€์˜ ์ดˆ๊ธฐํ™” ๋ฐ ์„ค์ • ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์ ์ ˆํ•œ ์‹œ์ ์ด๋‹ค.

load

load ์ด๋ฒคํŠธ๋Š” ์›น ํŽ˜์ด์ง€์™€ ๊ทธ ์•ˆ์— ์žˆ๋Š” ๋ชจ๋“  ๋ฆฌ์†Œ์Šค(์ด๋ฏธ์ง€, ์Šคํƒ€์ผ์‹œํŠธ, ์Šคํฌ๋ฆฝํŠธ ๋“ฑ)๊ฐ€ ์™„์ „ํžˆ ๋กœ๋“œ๋˜์—ˆ์„ ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค. ํŽ˜์ด์ง€๊ฐ€ ์™„์ „ํžˆ ๋กœ๋“œ๋œ ํ›„์—๋งŒ ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ์ž‘์—…์ด ์žˆ๋Š” ๊ฒฝ์šฐ์— ์œ ์šฉํ•˜๋‹ค.

 

๐Ÿ”Ž๋ธ”๋ก ๋ฆฌ์†Œ์Šค

๋ธ”๋ก ๋ฆฌ์†Œ์Šค๋ž€ ์›น ํŽ˜์ด์ง€์˜ ๋ Œ๋”๋ง์„ ์ฐจ๋‹จํ•˜๊ณ  ํŽ˜์ด์ง€์˜ ๋กœ๋“œ ์†๋„๋ฅผ ๋Š๋ฆฌ๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฆฌ์†Œ์Šค๋ฅผ ๋งํ•œ๋‹ค. ์ฃผ๋กœ CSS, JavaScript ํŒŒ์ผ, ์ด๋ฏธ์ง€ ํฐํŠธ ๋“ฑ์ด ํ•ด๋‹น๋œ๋‹ค.

โœ๏ธ๋ธ”๋ก ๋ฆฌ์†Œ์Šค ์ตœ์ ํ™” ๋ฐฉ๋ฒ•

  1. JavaScript ๋กœ๋“œ ์‹œ์  ์ดˆ์ ํ™”
    • <body> ํƒœ๊ทธ ํ•˜๋‹จ ๋ฐฐ์น˜: HTML ํŒŒ์‹ฑ์ด ๋จผ์ € ์ด๋ฃจ์–ด์ง€๊ณ , ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋กœ๋”ฉ์ด ๋งˆ์ง€๋ง‰์— ์ด๋ฃจ์–ด์ ธ ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์‹œ๊ฐ„์„ ๋‹จ์ถ•ํ•  ์ˆ˜ ์žˆ๋‹ค. 
    • async์™€ defer ์‚ฌ์šฉ: <head> ํƒœ๊ทธ ๋‚ด์— ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์œ„์น˜์‹œํ‚ค๋˜, async๋‚˜ defer ์†์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๋”ฉ ๋ฐฉ์‹์„ ์ตœ์ ํ™”ํ•œ๋‹ค. async๋Š” ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋กœ๋“œ๋˜๋ฉฐ, defer๋Š” HTML ํŒŒ์‹ฑ ํ›„, DOMContentLoaded ์ด๋ฒคํŠธ ๋ฐ”๋กœ ์ „์— ์‹คํ–‰๋œ๋‹ค.
  2. CSS ํŒŒ์ผ๊ณผ ์ธ๋ผ์ธ ์Šคํƒ€์ผ
    • ์ธ๋ผ์ธ ์Šคํƒ€์ผ ์‚ฌ์šฉ: ์™ธ๋ถ€ ์Šคํƒ€์ผ์‹œํŠธ ๋Œ€์‹  HTML ๋ฌธ์„œ ๋‚ด์— ์ง์ ‘ CSS๋ฅผ ํฌํ•จ์‹œํ‚ค๋ฉด HTTP ์š”์ฒญ ์ˆ˜๋ฅผ ์ค„์—ฌ ๋กœ๋”ฉ ์‹œ๊ฐ„์„ ๋‹จ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์™ธ๋ถ€ ์Šคํƒ€์ผ ์‹œํŠธ(@import)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๋ธŒ๋ผ์šฐ์ €๋Š” ์Šคํƒ€์ผ์‹œํŠธ๋ฅผ ๋ณ‘๋ ฌ๋กœ ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์—…์‹ ๋•Œ๋ฌธ์— ๋กœ๋“œ ์‹œ๊ฐ„์ด ๋Š˜์–ด๋‚  ์ˆ˜ ์žˆ๋‹ค.
    • ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ ์‚ฌ์šฉ: ํŠน์ • ์กฐ๊ฑด์—์„œ๋งŒ ํ•„์š”ํ•œ CSS๊ฐ€ ์žˆ์„ ๋•Œ ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ถˆํ•„์š”ํ•œ ๋ธ”๋กœํ‚น์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํŽ˜์ด์ง€๋ฅผ ์ธ์‡„ํ•˜๊ฑฐ๋‚˜(print.css) ํ™”๋ฉด์ด ์„ธ๋กœ ๋ชจ๋“œ์ผ ๋•Œ(portrait.css) ์‚ฌ์šฉํ•˜๋Š” CSS๊ฐ€ ์žˆ์„ ๋•Œ, ํ•ด๋‹น ์Šคํƒ€์ผ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋„๋ก <script> ํƒœ๊ทธ์— media ์†์„ฑ์„ ๋ช…์‹œํ•˜์—ฌ ์‚ฌ์šฉํ•œ๋‹ค.(์ถœ์ฒ˜: ์„ฑ๋Šฅ ์ตœ์ ํ™” | TOAST UI :: Make Your Web Delicious! )

๐Ÿ”Ž๋ฆฌ์†Œ์Šค ์š”์ฒญ ์ˆ˜ ์ค„์ด๊ธฐ: ์ด๋ฏธ์ง€ ์Šคํ”„๋ผ์ดํŠธ

์ด๋ฏธ์ง€ ์Šคํ”„๋ผ์ดํŠธ๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ด๋ฏธ์ง€๋ฅผ ํ•˜๋‚˜๋กœ ๋งŒ๋“ค๊ณ , CSS background-position ์†์„ฑ์„ ์‚ฌ์šฉํ•ด ๋ถ€๋ถ„ ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ์ด๋ฏธ์ง€ ์Šคํ”„๋ผ์ดํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๋ฆฌ์†Œ์Šค ์š”์ฒญ ์ˆ˜๊ฐ€ ์ค„์–ด๋“ค์–ด ๋กœ๋”ฉ ์‹œ๊ฐ„์„ ๋น ๋ฅด๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ทธ ๋ฐ–์—๋„ CSS, ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ฒˆ๋“คํ•˜๊ธฐ / ๋‚ด๋ถ€ ์Šคํƒ€์ผ์‹œํŠธ ์‚ฌ์šฉํ•˜๊ธฐ ๋“ฑ์œผ๋กœ ๋ฆฌ์†Œ์Šค ์š”์ฒญ ์ˆ˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค.

 

 

๐Ÿ“–๋ Œ๋”๋ง ์ตœ์ ํ™” ๋ฐฉ๋ฒ•

ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์ตœ์ ํ™”๋œ ๋ Œ๋”๋ง ๊ธฐ๋ฒ•์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ค๊ณ  ์›น ์‚ฌ์ดํŠธ์˜ ์ดํƒˆ๋ฅ ์„ ๋‚ฎ์ถ”๋Š” ํ•ต์‹ฌ์ ์ธ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜๋ฏ€๋กœ ์ค‘์š”ํ•˜๋‹ค. ํŠนํžˆ, ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ๊ฐ€์ƒ ์Šคํฌ๋กค๋ง, ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋ฐ ๋ฌดํ•œ ์Šคํฌ๋กค ๊ตฌํ˜„์ด ์žˆ๋‹ค.

๐Ÿ”Ž๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ๊ฐ€ ์›น ํŽ˜์ด์ง€์— ๊ทธ๋ ค์ง€๊ธฐ๊นŒ์ง€์˜ ๊ณผ์ •: ๋กœ๋“œ์™€ ๋ Œ๋”๋ง

๋กœ๋“œ

๋กœ๋“œ๋ž€ ์›น ํŽ˜์ด์ง€์˜ ๋ฆฌ์†Œ์Šค(HTML, CSS, JavaScript ํŒŒ์ผ ๋“ฑ)์ด ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์‚ฌ์šฉ์ž์˜ ๋ธŒ๋ผ์šฐ์ €๋กœ ์ „์†ก๋˜๋Š” ๊ณผ์ •์ด๋‹ค. ๋กœ๋“œ ๊ณผ์ •์ด ์™„๋ฃŒ๋˜์–ด์•ผ ์›น ํŽ˜์ด์ง€๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋ Œ๋”๋ง๋  ์ˆ˜ ์žˆ๋‹ค.

๋ Œ๋”๋ง

๋ Œ๋”๋ง์€ ๋กœ๋“œ๋œ ์›น ํŽ˜์ด์ง€์˜ ๋ฆฌ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹ค์ œ๋กœ ํ™”๋ฉด์„ ํŽ˜์ด์ง€์— ๊ทธ๋ฆฌ๋Š” ๊ณผ์ •์ด๋‹ค.

๊ฐ€์ƒ ์Šคํฌ๋กค๋ง, ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋ฐ ๋ฌดํ•œ ์Šคํฌ๋กค ๊ตฌํ˜„์€ ๋ Œ๋”๋ง ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐฉ๋ฒ•์ด๋‹ค.

 

๐Ÿ”ŽVirtual Sroll

Virtual Scroll์€ ๋งŽ์€ ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๋Š” ์˜์—ญ๋‚ด์—์„œ ๋ณผ ์ˆ˜ ์žˆ์„๋งŒํผ์˜ ๋ฐ์ดํ„ฐ๋งŒ ํ‘œ์‹œํ•˜์—ฌ DOM์„ ๊ทธ๋ฆฌ๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๋ฆฌ์†Œ์Šค๋ฅผ ์ค„์ด๋Š” ๊ธฐ๋ฒ•์ž…์ด๋‹ค. ์ „์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ํ˜„์žฌ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณด๊ณ  ์žˆ๋Š” ํ™”๋ฉด์˜ ์Šคํฌ๋กค ์œ„์น˜๋ฅผ ๊ณ„์‚ฐํ•˜์—ฌ ํ•ด๋‹น ์œ„์น˜์— ๋งž๋Š” ๋ฐ์ดํ„ฐ๋งŒ ๊ทธ๋ ค์ฃผ๋Š” ์›๋ฆฌ๋‹ค.

ํ˜„์žฌ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณด๊ณ  ์žˆ๋Š” ํ™”๋ฉด์˜ ๋ฐ์ดํ„ฐ๋งŒ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด์„œ scroll ์ด๋ฒคํŠธ๋ฅผ ์ ์šฉํ•ด์•ผ ํ•œ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์กฐ๋ฅผ ํ™œ์šฉํ•˜์ž.

useEffect(()=>{
	//...๋กœ์ง ์ˆ˜ํ–‰
	return () =>{
		//์ปดํฌ๋„ŒํŠธ ์–ธ๋งˆ์šดํŠธ ์‹œ ์‹คํ–‰๋  ๋กœ์ง
	}
},[]); //์˜์กด์„ฑ ๋ฐฐ์—ด์ด ๋น„์–ด ์žˆ์œผ๋ฏ€๋กœ, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ๋  ๋•Œ๋งŒ ์‹คํ–‰๋œ๋‹ค.

โ€ป์ฝ”๋“œ

import React from 'react';
import { useState, useEffect } from 'react';
import styles from "./Search.module.css";

function Search2() {
  const [searchItems, setSearchItems] = useState([]);
  const [scrollPos, setScorllPos] = useState(0);
  const windowHeight = window.innerHeight;
  const CONTENT_HEIGHT = 400;
  const NODE_PADDING = 2;

  function onScroll(){
    setScorllPos(window.scrollY);
  }

  useEffect(()=>{
    fetch('์š”์ฒญURL')
    .then((res)=>res.json())
    .then((data)=>setSearchItems(data?.collection?.items));
  
    window.addEventListener("scroll", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
    }
  },[]);

  return ( 
    <div>
      <h1>Hello, World!</h1>
      {searchItems.map((searchItem, i)=>(
        <div className={styles.card} key={i}>
          {scrollPos < CONTENT_HEIGHT * (i+1+NODE_PADDING) 
          && (scrollPos + windowHeight) > CONTENT_HEIGHT * (i-1-NODE_PADDING) ? 
          <div className={styles.imgBox}><img className={styles.cardImg} src={searchItem?.links?.[0]?.href} /></div> 
          : <div className={styles.imgBox}></div>}
          {scrollPos < CONTENT_HEIGHT * (i+1+NODE_PADDING) 
          && (scrollPos + windowHeight) > CONTENT_HEIGHT * (i-1-NODE_PADDING) ? 
          <div className={styles.contentBox}><p>{searchItem?.data[0]?.title}</p></div>
          : <div className={styles.contentBox}>null</div>}
        </div>
      ))}
    </div>
  );
}

export default Search2;


 

๐Ÿ”Ž๋ฌดํ•œ์Šคํฌ๋กค

ํŽ˜์ด์ง€๋„ค์ด์…˜๊ณผ ๋น„๊ตํ•œ ๋ฌดํ•œ์Šคํฌ๋กค์˜ ์žฅ์ ์€ ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๊ณ  ์‹ถ์„ ๋•Œ ์ถ”๊ฐ€ ํ–‰๋™์„ ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๋Š” ์ ์ด๋‹ค. ์ด๋Š” ์ƒํ™ฉ์— ๋”ฐ๋ผ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

 

โœ๏ธ๊ธฐ์กด scorll ์ด๋ฒคํŠธ์˜ ๋ฌธ์ œ

๋ฌดํ•œ์Šคํฌ๋กค์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” virtual scroll๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ scroll ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์ง€ํ•ด์•ผ ํ•œ๋‹ค. scroll ์ด๋ฒคํŠธ๋Š” ์‚ฌ์šฉ์ž์˜ ์Šคํฌ๋กค์— ๋”ฐ๋ผ ์งง์€ ์‹œ๊ฐ„์— ๋ฌด์ˆ˜ํžˆ ๋งŽ์€ ์ˆ˜์˜ ์ด๋ฒคํŠธ๊ฐ€ ๋™๊ธฐ์ ์œผ๋กœ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”์ธ ์Šค๋ ˆ๋“œ(Main Thread)์— ์˜ํ–ฅ์„ ์ค€๋‹ค. ํ•œ ํŽ˜์ด์ง€ ๋‚ด์— ์—ฌ๋Ÿฌ scroll ์ด๋ฒคํŠธ(๋ฌดํ•œ ์Šคํฌ๋กค, ๊ด‘๊ณ  ๋ฐฐ๋„ˆ, ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋“ฑ)๊ฐ€ ๋“ฑ๋ก๋˜์–ด ์žˆ์„ ๊ฒฝ์šฐ, ๊ฐ ์—˜๋ฆฌ๋จผํŠธ๋งˆ๋‹ค ์ด๋ฒคํŠธ๊ฐ€ ๋“ฑ๋ก๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ์ž๊ฐ€ ์Šคํฌ๋กคํ•  ๋•Œ๋งˆ๋‹ค ์ด๋ฅผ ๊ฐ์ง€ํ•˜๋Š” ์ด๋ฒคํŠธ๊ฐ€ ๊ณ„์† ํ˜ธ์ถœ๋œ๋‹ค. ์ด๋ฅผ debounce์™€ throttle๋กœ ์ผ๋ถ€ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์—ฌ์ „ํžˆ ์„ฑ๋Šฅ ๋ถ€ํ•˜๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค.

๋˜ํ•œ ํŠน์ • ์ง€์ ์„ ๊ด€์ฐฐํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” getBoundingClientRect()ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ด ํ•จ์ˆ˜๋Š” reflow๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. reflow๋ž€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์›น ํŽ˜์ด์ง€์˜ ์ผ๋ถ€ ๋˜๋Š” ์ „์ฒด๋ฅผ ๋‹ค์‹œ ๊ทธ๋ ค์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ๋ฐœ์ƒํ•˜๋ฉฐ, ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ฌธ์„œ ๋‚ด ์š”์†Œ๋“ค์˜ ์œ„์น˜์™€ ๊ธฐํ•˜ํ•™์  ํ˜•ํƒœ๋ฅผ ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜์—ฌ ๋ ˆ์ด์•„์›ƒ์ด๋‚˜ ์ฝ˜ํ…์ธ ์˜ ๋ณ€๊ฒฝ์— ๋งž๊ฒŒ ์กฐ์ •ํ•˜๋Š” ๊ณผ์ •์„ ์˜๋ฏธํ•œ๋‹ค.

 

โœ๏ธIntersection Observer API

Intersection Observer API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์œ„์™€ ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. Intersecion Observer API๋Š” ๋น„๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์œผ๋ฉด์„œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๊ด€์ฐฐํ•  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ IntersectionObserverEntry ์†์„ฑ์„ ํ™œ์šฉํ•ด getBoundingClientRect()๋ฅผ ํ˜ธ์ถœํ•œ ๊ฒƒ๊ณผ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋”ฐ๋กœ getBoundingClientRect() ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ํ•„์š”๊ฐ€ ์—†์–ด ๋ฆฌํ”Œ๋กœ์šฐ ํ˜„์ƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

const intersectionObserver = new IntersectionObserver(callback[, options])

โ€ป์ฝ”๋“œ

import React from 'react';
import { useState, useEffect } from 'react';
import styles from "./Search.module.css";

function Search3() {
  const [searchItems, setSearchItems] = useState([]);
  const [page, setPage] = useState(0);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(()=>{
    console.log("page: ",page);
    fetch(`https://images-api.nasa.gov/search?q=apollo&page=${page}`)
    .then((res)=>res.json())
    .then((data)=>{
      setSearchItems((prevData)=>[...prevData, ...data?.collection?.items]);
      setIsLoading(false);
    });
  },[page]);

  const handleObserver = (entries) => {
    const target = entries[0];
    if (target.isIntersecting && !isLoading) {
      setPage((prevPage) => prevPage + 1);
      setIsLoading(true);
    }
  };

  useEffect(()=>{
    const observer = new IntersectionObserver(handleObserver, {
      threshold: 0, //  Intersection Observer์˜ ์˜ต์…˜, 0์ผ ๋•Œ๋Š” ๊ต์ฐจ์ ์ด ํ•œ ๋ฒˆ๋งŒ ๋ฐœ์ƒํ•ด๋„ ์‹คํ–‰, 1์€ ๋ชจ๋“  ์˜์—ญ์ด ๊ต์ฐจํ•ด์•ผ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰.
    });
    // ์ตœํ•˜๋‹จ ์š”์†Œ๋ฅผ ๊ด€์ฐฐ ๋Œ€์ƒ์œผ๋กœ ์ง€์ •ํ•จ
    const observerTarget = document.getElementById("observer");
    // ๊ด€์ฐฐ ์‹œ์ž‘
    if (observerTarget) {
      observer.observe(observerTarget);
    }
  },[]);

  return ( 
    <div>
      <h1>Hello, World!</h1>
      {searchItems.map((searchItem, i)=>(
        <div className={styles.card} key={i}>
          <div className={styles.imgBox}><img className={styles.cardImg} src={searchItem?.links?.[0]?.href} /></div> 
          <div className={styles.contentBox}><p>{searchItem?.data[0]?.title}</p></div>
        </div>
      ))}
      <div id="observer" style={{ backgroundColor:"pink", height: "10px" }}></div>
    </div>
  );
}

export default Search3;
728x90