TIL

๋ฌดํ•œ์Šคํฌ๋กค intersection-observer ์—†์ด ๊ตฌํ˜„ํ•ด๋ณด๊ธฐ

inz1234 2024. 7. 1. 22:39

๐Ÿšจ  TroubleShotting

๋ฌธ์ œ ๋ฐœ์ƒ์˜ ๋ฐฐ๊ฒฝ - As Is

๋ฌดํ•œ์Šคํฌ๋กค ์‚ฌ์šฉ ์ด์œ 
1. ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€์ ์œผ๋กœ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ ๋งค๋„๋Ÿฌ์šด UI
2.
์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ ์ด์ „ ๋ฐ์ดํ„ฐ๋ฅผ refetch ํ•˜์ง€ ์•Š๊ณ  ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋งŒ fetchํ•ด์„œ ๋ถˆํ•„์š”ํ•œ ๋„คํŠธ์›Œํฌ ์š”์ฒญ๋Ÿ‰์„ ์ค„์ผ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—

์ด๋Ÿฌํ•œ ์ด์œ ๋กœ useInfiniteQuery๋ฅผ ์ด์šฉํ•ด์„œ ๋ฌดํ•œ์Šคํฌ๋กค์„ ๊ตฌํ˜„ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ, intersection-observer ์—†์ด ๋ฌดํ•œ์Šคํฌ๋กค์„ ๊ตฌํ˜„ํ•ด๋ณด๊ณ  ์‹ถ์—ˆ๋‹ค.
๊ตณ์ด? ์‹ถ๊ธฐ๋„ ํ•˜์ง€๋งŒ ์ฝ”๋”ฉ์˜ ์ŠคํŒฉ์€ ์‹œํ–‰์ฐฉ์˜ค์˜ ๊ฒฐ๊ณผ๋ฌผ์ด๋ผ๊ณ  ํ–ˆ์œผ๋‹ˆ..! ํ•จ ํ•ด๋ณด์ž

๋ฐฉ๋ฒ•์€ ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

1. ์Šคํฌ๋กค ๋‚ด๋ฆฐ ๋†’์ด๊ฐ€ ์ „์ฒด ๋†’์ด๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ „์ฒด ๋†’์ด์˜ ์–ด๋Š์ •๋„๋ฅผ ๋„˜๊ธฐ๋ฉด fetchNextPage() 

2. ์Šคํฌ๋กค ๋‚ด๋ฆฐ ๋†’์ด๊ฐ€ intersection-observer์˜ useInview์ฒ˜๋Ÿผ ํŠน์ • ref ์š”์†Œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ช‡ ํผ์„ผํŠธ๋ฅผ ๋„˜๊ธฐ๋ฉด fetchNextPage()


How(๊ณผ์ •) ?

์‹œํ–‰์ฐฉ์˜ค 1

์ฒ˜์Œ์—๋Š” ๋‚ด๊ฐ€ ์Šคํฌ๋กคํ•œ ๋†’์ด vs ์ „์ฒด document์˜ ๋†’์ด์™€ ๋น„๊ตํ•ด์„œ 
์˜ˆ๋ฅผ ๋“ค๋ฉด, ์Šคํฌ๋กคํ•œ ๋†’์ด๊ฐ€ ์ „์ฒด document์˜ x 0.8 ๋ณด๋‹ค ํฌ๋ฉด fetchNextPage()๋ฅผ ํ•˜๋„๋ก ํ•˜๋ คํ–ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์šฐ์„  ๋‚ด๊ฐ€ ์Šคํฌ๋กคํ•œ ๋†’์ด์™€ ์ „์ฒด document์˜ ๋†’์ด๋ฅผ ๊ตฌํ•ด๋ณด์•˜๋‹ค.

const QuizCommentsList = () => {
  const {
    data: quizComments,
    isFetchingNextPage,
    isFetchingPreviousPage,
    fetchNextPage,
    fetchPreviousPage,
    hasNextPage,
    hasPreviousPage,
    isRefetching
  } = useQuizCommentsQuery();

  const [scrollY, setScrollY] = useState(0);
  const [documentHeight, setDocumentHeight] = useState(0);
  const docHeight = document.documentElement.scrollHeight;

  const handleScroll = () => {
    setScrollY(window.scrollY);
  };

  const updateDocumentHeight = () => {
    setDocumentHeight(document.documentElement.scrollHeight);
  };

  useEffect(() => {
    // ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก
    window.addEventListener('scroll', handleScroll);

    updateDocumentHeight();
    window.addEventListener('resize', updateDocumentHeight);

    return () => {
      window.removeEventListener('scroll', handleScroll);
      window.removeEventListener('resize', updateDocumentHeight);
    };
  }, []);

  console.log('scrollY =>', scrollY);
  console.log('docHeight =>', docHeight);
  console.log('documentHeight =>', documentHeight);

  return (

์—ฌ๊ธฐ์„œ ๊ถ๊ธˆ์ฆ. 

document์˜ ์ „์ฒด ๋†’์ด? ๋ฅผ ๋ฐ”๋กœ

const docHeight = document.documentElement.scrollHeight;

์ปดํฌ๋„ŒํŠธ ์ƒ๋‹จ์— ์ด๋ ‡๊ฒŒ ์„ ์–ธํ•˜๋Š” ๊ฒƒ๊ณผ

  const [documentHeight, setDocumentHeight] = useState(0);
  
  ...
  
    const updateDocumentHeight = () => {
    setDocumentHeight(document.documentElement.scrollHeight);
  };
  
    useEffect(() => {
    ...
    updateDocumentHeight();
    window.addEventListener('resize', updateDocumentHeight);

    return () => {
      window.removeEventListener('resize', updateDocumentHeight);
    };
  }, []);

์ƒํƒœ๋ฅผ ๋งŒ๋“  ๋’ค useEffect ๋‚ด์—์„œ set ํ•ด์ค€ ๊ฒฐ๊ณผ๊ฐ’์ด ๋‹ฌ๋ž๋‹ค.

์™œ ๋‹ค๋ฅด์ง€? ๋ญ๊ฐ€ ๋” ๋งž๋Š” ๊ฑธ๊นŒ?

ํ›„์ž ์ฆ‰, useEffect ๋‚ด์—์„œ document์˜ ์ „์ฒด ๋†’์ด๋ฅผ ๊ตฌํ•˜๋Š” ๋ฐ์— ๋” ๋งž๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.
document์˜ ๋งˆ์šดํŠธ ๋˜์–ด ๋ Œ๋”๋ง์ด ์™„๋ฃŒ๋˜๋Š” ๊ณผ์ •์—์„œ DOM ์ƒํƒœ๊ฐ€ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

OK. document์˜ ์ „์ฒด ๋†’์ด๋Š” ๊ตฌํ–ˆ๋Š”๋ฐ ๋˜ ๊ถ๊ธˆ์ฆ.

scrollY !== documentHeight?

์ด๋Œ€๋กœ ์Šคํฌ๋กค์„ ๊ฐ€์žฅ ๋ฐ‘์œผ๋กœ ๋‚ด๋ฆฌ๋ฉด, scrollY์™€ documentHeight์˜ ๊ฐ’์ด ๊ฐ™์•„์•ผ ํ•˜๋Š”๋ฐ ๋‹ฌ๋ž๋‹ค.

์•Œ๊ณ ๋ณด๋‹ˆ scrollY๋Š” ์ง€๊ธˆ๊นŒ์ง€ ์Šคํฌ๋กค ํ•œ ๊ณณ๊นŒ์ง€์˜ ๋†’์ด๋Š” ๋งž์ง€๋งŒ, 
์Šคํฌ๋กคํ•œ ๊ณณ์˜ ๊ฐ€์žฅ ์ƒ๋‹จ๊นŒ์ง€์˜ ๋†’์ด๋ผ๊ณ  ํ•œ๋‹ค.
์ข€ ๋” ์ •ํ™•ํ•˜๊ฒŒ๋Š” mdn ๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด scroll Y๋Š” ํ˜„์žฌ ๋ทฐํฌํŠธ ์œ„์ชฝ ๋ชจ์„œ๋ฆฌ์˜ Y์ขŒํ‘œ๋ฅผ ๋ฐ˜ํ™˜ ํ•œ๋‹ค๊ณ  ๋˜์–ด์žˆ๋‹ค.
https://developer.mozilla.org/ko/docs/Web/API/Window/scrollY

 

Window.scrollY - Web API | MDN

Window ์ธํ„ฐํŽ˜์ด์Šค์˜ scrollY ์ฝ๊ธฐ ์ „์šฉ ์†์„ฑ์€ ๋ฌธ์„œ๊ฐ€ ์ˆ˜์ง์œผ๋กœ ์–ผ๋งˆ๋‚˜ ์Šคํฌ๋กค๋๋Š”์ง€ ํ”ฝ์…€ ๋‹จ์œ„๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ตœ์‹  ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ๊ฐ’์˜ ์ •๋ฐ€๋„๊ฐ€ ํ”ฝ์…€๋ณด๋‹ค ์ž‘์œผ๋ฏ€๋กœ ๋ฐ˜๋“œ์‹œ ์ •์ˆซ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š”

developer.mozilla.org

๋”ฐ๋ผ์„œ, scrollY์™€ documentHeight๋ฅผ ๊ฐ™๊ฒŒ ํ•˜๋ ค๋ฉด scrollY + ๋ทฐํฌํŠธ์˜ ๋†’์ด๋ฅผ ๋”ํ•ด์ค˜์•ผ ํ•œ๋‹ค.


์‹œํ–‰์ฐฉ์˜ค 2

๊ทธ๋ž˜์„œ documentHeight๋ฅผ ์ œ๋Œ€๋กœ ๊ตฌํ•˜๊ธฐ๋กœ ํ•˜๊ณ , scrollY + viewportHeihgt === documentHeight๊ฐ€ ๋˜๋„๋ก ์ˆ˜์ •ํ–ˆ๋‹ค.

const handleScroll = () => {
    const scrollPosition = window?.scrollY;
    setScrollY(scrollPosition);

  };

  const updateDocumentHeight = () => {
    setDocumentHeight(document.documentElement.scrollHeight);
    setViewportHeight(window?.innerHeight);
  };

  const [scrollY, setScrollY] = useState(0);
  const [viewportHeight, setViewportHeight] = useState(0);
  const [documentHeight, setDocumentHeight] = useState(0);

 useEffect(() => {
    handleScroll();
    // ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก
    window.addEventListener('scroll', handleScroll);

    updateDocumentHeight();
    window.addEventListener('resize', updateDocumentHeight);

    return () => {
      window.removeEventListener('scroll', handleScroll);
      window.removeEventListener('resize', updateDocumentHeight);
    };
  }, []);

  console.log('scrollY =>', scrollY);
  console.log('viewportHeight =>', viewportHeight);
  console.log('scrollY + viewportHeight =>', scrollY + viewportHeight);
  console.log('documentHeight =>', documentHeight);
  console.log('------------------------------------');

์•„๋‹ˆ ๊ทธ๋Ÿฐ๋ฐ๋„ ์Šคํฌ๋กค์„ ์ตœ์ƒ๋‹จ์œผ๋กœ ๋‚ด๋ ธ์„ ๋•Œ, documentHeight์™€ scrollY + viewportHeight๊ฐ€ ๋‹ค๋ฅธ ๊ฒƒ ์•„๋‹Œ๊ฐ€ใ… 

2100 !== 1629....

๋ญ๊ฐ€ ๋ฌธ์ œ์ธ์ง€ ํ•œ์ฐธ์„ ์ณ๋‹ค๋ณด๋‹ˆ ๋‹น์—ฐํ•œ ๊ฒฐ๊ณผ์˜€๋‹ค.
๋‚˜๋Š” ์ง€๊ธˆ viewportHeight์™€ documentHeight ๊ฐ’์„ ๋งˆ์šดํŠธ ๋œ ํ›„์™€ resize ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ๋งŒ ์—…๋ฐ์ดํŠธ ํ•˜๊ณ  ์žˆ์—ˆ๋‹ค..
scroll ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ scrollY์™€ ํ•œ๊บผ๋ฒˆ์— ์—…๋ฐ์ดํŠธ ํ•ด์•ผํ•˜๋Š”๋ฐ ๋ง์ด๋‹ค. U hoxy ๋ฐ”์นด??

๋‹ค์‹œ ๋ฐ”๊ฟจ๋‹ค.

const {
    data: quizComments,
    isFetchingNextPage,
    isFetchingPreviousPage,
    fetchNextPage,
    fetchPreviousPage,
    hasNextPage,
    hasPreviousPage,
    isRefetching
  } = useQuizCommentsQuery();

  const [scrollY, setScrollY] = useState(0);
  const [viewportHeight, setViewportHeight] = useState(0);
  const [documentHeight, setDocumentHeight] = useState(0);

  const handleScroll = () => {
  // ํ•œ๊บผ๋ฒˆ์— ์—…๋ฐ์ดํŠธ
    const scrollPosition = window?.scrollY;
    setScrollY(scrollPosition);
    setDocumentHeight(document.documentElement.scrollHeight);
    setViewportHeight(window?.innerHeight);
  };

  useEffect(() => {
    handleScroll();
    // ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก
    window.addEventListener('scroll', handleScroll);
    window.addEventListener('resize', handleScroll);

    return () => {
      window.removeEventListener('scroll', handleScroll);
      window.removeEventListener('resize', handleScroll);
    };
  }, []);

  console.log('scrollY =>', scrollY);
  console.log('viewportHeight =>', viewportHeight);
  console.log('scrollY + viewportHeight =>', scrollY + viewportHeight);
  console.log('documentHeight =>', documentHeight);
  console.log('------------------------------------');

๋‹คํ–‰ํžˆ ์ด๋ ‡๊ฒŒ ํ–ˆ๋”๋‹ˆ ์Šคํฌ๋กค์„ ๊ฐ€์žฅ ํ•˜๋‹จ์œผ๋กœ ๋‚ด๋ ธ์„ ๋•Œ

scrollY + viewportHeight ๊ฐ€ documentHeight์™€ ๊ฑฐ์˜ ๋น„์Šทํ•œ ์ˆซ์ž๊ฐ€ ๋˜์—ˆ๋‹ค. ํœด..

์ž ๊ทธ๋Ÿผ ์ด๋Ÿฐ ๊ฐœ๋…๋“ค์„ ์•Œ์•˜์œผ๋‹ˆ ๋“œ๋””์–ด!! ๋ฌดํ•œ์Šคํฌ๋กค์„ ๊ตฌํ˜„ํ•ด๋ณด์ž!

  const handleScroll = useCallback(() => {
    const scrollPosition = window?.scrollY;
    const docHeight = document.documentElement.scrollHeight;
    const viewHeight = window.innerHeight;

    if (scrollPosition + viewHeight >= docHeight * 0.8) {
      if (hasNextPage && !isFetchingNextPage) {
        fetchNextPage();
      }
    }
  }, [fetchNextPage, hasNextPage, isFetchingNextPage]);
  
    useEffect(() => {
    handleScroll();
    // ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก

    window.addEventListener('scroll', handleScroll);
    window.addEventListener('resize', handleScroll);

    return () => {
      window.removeEventListener('scroll', handleScroll);
      window.removeEventListener('resize', handleScroll);
    };
  }, [handleScroll]);

์ด๋ ‡๊ฒŒ ํ•œ ๋’ค์— ๊ตฌํ˜„๋œ ๊ฑธ ๋ณด๋ฉด ์ด๋ ‡๊ฒŒ ๋œ๋‹ค.


์‹œํ–‰์ฐฉ์˜ค 3

๊ทธ๋Ÿฐ๋ฐ ์Œ... ์ด๋ ‡๊ฒŒ ํ•˜๋‹ˆ๊นŒ
๋ณด๋‹ค์‹œํ”ผ Devtools์—๋Š” ์Šคํฌ๋กค ๋‹ค์šด์— ๋”ฐ๋ผ์„œ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋“ค์„ ๋ˆ„์ ํ•˜์—ฌ ๋ฐ›์•„์ง€๋Š” ๊ฒƒ์€ ๋˜์ง€๋งŒ,  
์œ ์ €๋กœ ํ•˜์—ฌ๊ธˆ ์–ธ์ œ ์ •ํ™•ํžˆ ๋ฐ์ดํ„ฐ๋ฅผ ๋” fetching ํ•˜๋Š” ๊ฒƒ์ธ์ง€ ์•Œ ์ˆ˜๊ฐ€ ์—†์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๋‘ ๋ฒˆ์งธ ๋ฌดํ•œ์Šคํฌ๋กค ๋ฐฉ๋ฒ•์ธ
intersection-observer์˜ useInview๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ์™€ ๊ฐ™์ด ์–ด๋–ค ์š”์†Œ์— ref ๊ฐ’์„ ์ค€ ๋’ค
๊ทธ ์š”์†Œ์˜ ์ผ์ • ํผ์„ผํŠธ ์ด์ƒ ์Šคํฌ๋กค ๋‹ค์šด์ด ๋˜๋ฉด fetchNextPage() ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ๋„ ์‹œ๋„ํ•ด๋ณด์•˜๋‹ค.
์ด๋ ‡๊ฒŒ ๋˜๋ฉด ์–ด๋Š์ •๋„๊ฐ€ ๋˜์–ด์•ผ ๋ฐ์ดํ„ฐ๊ฐ€ ์ถ”๊ฐ€์ ์œผ๋กœ fetch ๋˜๋Š”์ง€ ๊ฐ€์‹œ์ ์œผ๋กœ ๋” ๋ช…ํ™•ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์šฐ์„  ๊ธฐ์ค€์ด ๋  ์š”์†Œ์™€ ref๋ฅผ ๋ถ€์—ฌํ•œ๋‹ค.

const QuizCommentsList = () => {
  const {
    data: quizComments,
    isFetchingNextPage,
    isFetchingPreviousPage,
    fetchNextPage,
    fetchPreviousPage,
    hasNextPage,
    hasPreviousPage,
    isRefetching
  } = useQuizCommentsQuery();

  const targetRef = useRef<HTMLDivElement | null>(null);
  ...
  return (
  ...
  <div ref={targetRef} className="w-[300px] h-[300px] bg-gray-300"></div>
  )
  };

๊ทธ๋Ÿผ ๊ทธ ref์˜ ๊ฐ€์žฅ ์ƒ๋‹จ๊นŒ์ง€์˜ ๋†’์ด๋ฅผ ์–ด๋–ป๊ฒŒ ๊ตฌํ• ๊นŒ?
์˜ˆ์ „์— ์Šคํฌ๋กค ํ•  ๋•Œ ์‚ฌ์šฉํ–ˆ๋˜ getBoundingClientRect()๊ฐ€ ์ƒ๊ฐ๋‚ฌ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์—ฌ๊ธฐ์„œ ๋˜ ์ฃผ์˜ํ•  ์ !

getBoundingClientRect().top ์œผ๋กœ ์œ„์—์„œ๋ถ€ํ„ฐ ~ ํ•ด๋‹น ์š”์†Œ์˜ ์ƒ๋‹จ๊นŒ์ง€ ์œ„์น˜๋ฅผ ๊ตฌํ•  ๋•Œ๋„ ๊ทธ '์œ„' ๋ผ๋Š” ๊ธฐ์ค€์ด
์ „์ฒด document๊ฐ€ ์•„๋‹ˆ๋ผ, ๋ทฐํฌํŠธ์˜ ์ƒ๋‹จ์ด ๋œ๋‹ค.
https://developer.mozilla.org/ko/docs/Web/API/Element/getBoundingClientRect

 

Element.getBoundingClientRect() - Web API | MDN

Element.getBoundingClientRect() ๋ฉ”์„œ๋“œ๋Š” ์—˜๋ฆฌ๋จผํŠธ์˜ ํฌ๊ธฐ์™€ ๋ทฐํฌํŠธ์— ์ƒ๋Œ€์ ์ธ ์œ„์น˜ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š” DOMRect ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

developer.mozilla.org

๊ฒฐ๊ตญ์—๋Š” getBoundingClientRect().top ๋งŒ ํ•ด์„œ๋Š” ์•ˆ ๋˜๊ณ ,
์ง€๊ธˆ๊นŒ์ง€ scrollํ•œ ๊ณณ๊นŒ์ง€์˜ ๋†’์ด + getBoundingClientRect().top ์„ ํ•ด์•ผ 
์‹ค์ œ๋กœ ํ•ด๋‹น ์š”์†Œ์˜ ์ƒ๋‹จ๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ๊ฐ€ ๋‚˜์˜จ๋‹ค.

์ด๊ฑธ ๋ฐ”ํƒ•์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์งœ๋ฉด..!!


what(๊ฒฐ๊ณผ) - To Be

const QuizCommentsList = () => {
  const {
    data: quizComments,
    isFetchingNextPage,
    isFetchingPreviousPage,
    fetchNextPage,
    fetchPreviousPage,
    hasNextPage,
    hasPreviousPage,
    isRefetching
  } = useQuizCommentsQuery();

  const targetRef = useRef<HTMLDivElement | null>(null);

  const handleScroll = useCallback(() => {
    const scrollPosition = window.scrollY;
    const viewportHeight = window.innerHeight;

    if (targetRef.current) {
      const rect = targetRef.current.getBoundingClientRect();
      const targetTop = rect.top + scrollPosition;
      const targetPosition = targetTop + rect.height * 0.5;

      if (targetPosition <= scrollPosition + viewportHeight) {
        if (!hasNextPage || isFetchingNextPage) return;
        fetchNextPage();
      }
    }
  }, [hasNextPage, isFetchingNextPage, fetchNextPage]);

  useEffect(() => {
    handleScroll();

    window.addEventListener('scroll', handleScroll);
    window.addEventListener('resize', handleScroll);

    return () => {
      window.removeEventListener('scroll', handleScroll);
      window.removeEventListener('resize', handleScroll);
    };
  }, [handleScroll]);

  return (
  ...


๐Ÿ’ก ์ƒˆ๋กญ๊ฒŒ ์•Œ๊ฒŒ๋œ ์ 

์Šคํฌ๋กค์„ ๋‚ด๋ฆด ๋•Œ์˜ ์œ„์น˜๋Š” scrollY์— ๋ทฐํฌํŠธ์˜ ๋†’์ด๋ฅผ ๋”ํ•ด์ค˜์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ๊ณผ
getBoundingClientRect()๋Š” ๋ทฐํฌํŠธ์˜ ์ƒ๋‹จ์„ ๊ธฐ์ค€์œผ๋กœ ์š”์†Œ์˜ ์ƒ๋‹จ๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ๋ผ๋Š” ๊ฒƒ์„ ๋ฐฐ์› ๋‹ค!