[TIL] IntersectionObserver์™€์˜ ์‹ธ์›€..๐Ÿ˜ค

๊ณต์ง€์• ยท2023๋…„ 4์›” 11์ผ

๊ฐ€๋กœ๋กœ ์Šฌ๋ผ์ด๋“œ๊ฐ€ ์ƒ๊ธฐ๋Š” ์ด๋ฏธ์ง€ ์Šฌ๋ผ์ด๋”๋ฅผ ๋งŒ๋“œ๋Š”๋ฐ ์ด๋ฏธ์ง€๋Š” 4๊ฐœ ์ •๋„ ์žˆ๊ณ , ๊ฐ ์ด๋ฏธ์ง€๊ฐ€ ๋ณด์—ฌ์งˆ ๋•Œ๋งˆ๋‹ค ์Šฌ๋ผ์ด๋” ์œ„์— ์žˆ๋Š” ์„ค๋ช…์ด ๋ฐ”๋€Œ๋Š” ๊ฒƒ์„ ๊ตฌํ˜„ํ•ด์•ผ ํ–ˆ๋‹ค.

<ImageSlider>
          <ImageContainer ref={imageRef}>
            {items.map((item, index) => (
              <ImageWrapper key={index}>
                <Img
                  src={`/images/img_renewal${index}.png`}
                />
                <ImageTitle>{item.title}</ImageTitle>
              </ImageWrapper>
            ))}
          </ImageContainer>
        </ImageSlider>

์ฒ˜์Œ์—๋Š” observer.observe(imageRef.current)๋กœ ํ•ด์„œ ImageContainer๋ฅผ ํƒ€๊ฒŸ์š”์†Œ๋กœ ์‚ผ๊ณ  threshold๋ฅผ [0, 0.25, 0.5, 0.75, 1] ์ด๋Ÿฐ ์‹์œผ๋กœ ๋ฐฐ์—ด๋กœ ์ค€ ๋‹ค์Œ threshold์˜ ๋ช‡๋ฒˆ์งธ ์ธ๋ฑ์Šค๋ฅผ ์ง€๋‚˜๊ณ  ์žˆ๋Š”์ง€๋ฅผ ์•Œ์•„๋‚ด์„œ ๊ฐ’์„ ๋ณ€๊ฒฝํ•ด์ฃผ๊ณ ์ž ํ–ˆ์—ˆ๋‹ค. ์ด๋ฏธ์ง€ ํ•˜๋‚˜ํ•˜๋‚˜๋ฅผ ๊ด€์ฐฐํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์ง๊ด€์ ์œผ๋กœ ๋‚ซ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์œผ๋‚˜ ๊ฐ๊ฐ์˜ ์ด๋ฏธ์ง€์— ref๋ฅผ ์ฃผ๋Š” ๋ฐฉ์‹์€ ์•„๋‹ ๊ฑฐ ๊ฐ™๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
๊ทธ๋Ÿฐ๋ฐ threshold์˜ ์ธ๋ฑ์Šค๋ฅผ ์•Œ์•„๋‚ด๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ๋‹น์—ฐํžˆ ์žˆ์„ ๊ฑฐ๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์ง€๋งŒ ๊ทธ๋Ÿฐ ๋ฉ”์„œ๋“œ๋Š” ์กด์žฌํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— intersectionRatio๋ฅผ ์ด์šฉํ•ด ์ธ๋ฑ์Šค๋ฅผ ๊ตฌํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์—ˆ๊ณ ... ๋ฉ€๋ฆฌ ๋Œ์•„๊ฐ€๋Š” ๋А๋‚Œ์ด ๋“ค์—ˆ์œผ๋‚˜ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋Š”๋ฐ...

useEffect(() => {
  const observer = new IntersectionObserver(
    entries => {
      entries.forEach(entry => {
        const { intersectionRatio, isIntersecting } = entry;
        const thresholdIndex = getThresholdIndex(intersectionRatio);
        if (isIntersecting)
          setDesc(items[thresholdIndex]?.desc);
      });
    },
    { threshold: [0, 0.25, 0.5, 0.75, 1] }
  );

  if (imageRef.current) observer.observe(imageRef.current);

  return () => {
    if (imageRef.current) observer.unobserve(imageRef.current);
  };
}, []);

const getThresholdIndex = ratio => {
  const thresholds = [1, 0.75, 0.5, 0.25, 0];
  let minDiff = 1;
  let minDiffIndex = 0;
  for (let i = 0; i < thresholds.length; i++) {
    const diff = Math.abs(ratio - thresholds[i]);
    if (diff < minDiff) {
      minDiff = diff;
      minDiffIndex = i;
    }
  }
  return minDiffIndex;
};

๊ทธ๋Ÿฐ๋ฐ ๋ฌธ์ œ๋Š” ์Šฌ๋ผ์ด๋”์˜ ๋งˆ์ง€๋ง‰ ์š”์†Œ๊ฐ€ ๋ทฐํฌํŠธ ์•ˆ์— ๋“ค์–ด์˜ค๋Š”๋ฐ๋„ isIntersecting์ด false๋กœ ๋œจ๋Š” ๊ฒƒ์ด์—ˆ๋‹ค. ๋‹ค๋ฅธ ์š”์†Œ๋“ค์€ threshold์— ๋”ฐ๋ผ ์ฝœ๋ฐฑ์ด ์‹คํ–‰๋  ๋•Œ๋งˆ๋‹ค true๋กœ ์ž˜ ์ฐํžˆ๋Š”๋ฐ ๋งˆ์ง€๋ง‰ ์š”์†Œ๋งŒ false๋กœ ๋˜๋Š” ๊ฒƒ์€ ์•„๋ฌด๋ฆฌ ๊ณ ๋ฏผํ•ด๋ด๋„ ํ•ด๊ฒฐ์ด ์•ˆ ๋˜์—ˆ๋‹ค.
๊ทธ๋ž˜์„œ ๋‹ค์‹œ ์—ฐ์Šต์žฅ์— ๊ทธ๋ฆผ์„ ๊ทธ๋ฆฌ๋ฉด์„œ ๊ฐœ๋…์„ ์ •๋ฆฌํ•ด๋ณด์•˜๋Š”๋ฐ, ๊ฐ€์žฅ ๊น”๋”ํ•œ ๋ฐฉ๋ฒ•์€ ์—ญ์‹œ ์ด๋ฏธ์ง€ ์š”์†Œ ๊ฐ๊ฐ์„ observeํ•˜๋Š” ๊ฒƒ์ด๋ผ๊ณ  ํŒ๋‹จํ–ˆ๊ณ , ref๋Š” ๊ธฐ์กด์ฒ˜๋Ÿผ Container์—๋งŒ ์ฃผ๋Š” ๋Œ€์‹  querySelectorAll์„ ํ†ตํ•ด ์ด๋ฏธ์ง€ ์š”์†Œ๋ฅผ ๊ฐ€์ ธ์™€ ๊ฐ๊ฐ์„ ํƒ€๊ฒŸ์š”์†Œ๋กœ ์‚ผ์•˜๋‹ค.

useEffect(() => {
    const observer = new IntersectionObserver(
      entries => {
        entries.forEach(entry => {
          if (entry.isIntersecting)
            setDesc(items[entry.target.id]?.desc);
        });
      },
      { root: null, threshold: 0.9 }
    );

    if (imageRef.current) {
      const images = imageRef.current.querySelectorAll('img');
      Array.from(images).forEach((image: HTMLImageElement) =>
        observer.observe(image)
      );
    }

    return () => {
      if (imageRef.current) observer.disconnect();
    };
  }, []);

cleanup ํ•จ์ˆ˜๋Š” unobserve๊ฐ€ ์•„๋‹ˆ๋ผ ๋ชจ๋“  ์š”์†Œ์˜ ๊ด€์ฐฐ์„ ์ค‘์ง€์‹œํ‚ค๋Š” disconnect๋กœ ๋ณ€๊ฒฝํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ธ๋ฑ์Šค๋ฅผ ๊ตฌํ•˜๋Š” ๋ณต์žกํ•œ ํ•จ์ˆ˜ ๋Œ€์‹  ๋ฆฌํ„ด๋ฌธ ์•ˆ์—์„œ ๊ฐ ์ด๋ฏธ์ง€์— index๋ฅผ id๊ฐ’์œผ๋กœ ์ฃผ๊ณ  isIntersecting์ด true๊ฐ€ ๋˜๋Š” ๊ฒฝ์šฐ target์˜ id๋ฅผ ํ†ตํ•ด ์„ค๋ช…์„ ๋ฐ”๊ฟ”์ฃผ์—ˆ๋‹ค. threshold๋Š” ์ด๋ฏธ์ง€๊ฐ€ 90ํผ์„ผํŠธ ์ •๋„ ๊ฐ€์‹œ์„ฑ์ด ํ™•์ธ๋˜์—ˆ์„ ๋•Œ๋งˆ๋‹ค ์ฝœ๋ฐฑ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ์—ˆ๋‹ค. ์ฒ˜์Œํ–ˆ๋˜ ๋ฐฉ์‹์€ ์ด๋ฏธ์ง€๊ฐ€ ์ „์ฒด ์ปจํ…Œ์ด๋„ˆ์—์„œ ๋Œ€๋žต ์ด์ •๋„ ํผ์„ผํŠธ์— ์žˆ๋‹ค๊ณ  ์•ผ๋งค๋กœ ์ •ํ•ด์„œ ๋ฐ”๋€Œ๋Š” ํฌ์ธํŠธ๋„ ์–ด์ƒ‰ํ–ˆ๋Š”๋ฐ, ๊ฐ๊ฐ์˜ ์ด๋ฏธ์ง€๋ฅผ ํƒ€๊ฒŸ์š”์†Œ๋กœ ์‚ผ์œผ๋‹ˆ ์ง๊ด€์ ์ด๊ณ  ๊น”๋”ํ•œ ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„๋˜์—ˆ๋‹ค. ์Šคํฌ๋กค ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ๋•Œ ํƒ€๊ฒŸ์š”์†Œ๋ฅผ ์–ด๋–ค ๊ฑธ๋กœ ํ• ์ง€์— ๋Œ€ํ•ด์„œ ์ •ํ™•ํ•œ ํŒ๋‹จ์ด ํ•„์š”ํ–ˆ๋˜ ๊ฑฐ ๊ฐ™๋‹ค. ์ธ๋ฑ์Šค ๊ตฌํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค ๋•Œ๋ถ€ํ„ฐ ๋ณต์žกํ•˜๊ฒŒ ๋Œ์•„๊ฐ„๋‹ค๋Š” ๋А๋‚Œ์ด ๋“ค์—ˆ๋Š”๋ฐ, ์—ญ์‹œ ๋” ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์€ ์กด์žฌํ–ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€