IntersectionObserver threshold - 가시성 감지 임계값 완벽 가이드

한 줄 요약

threshold는 IntersectionObserver가 콜백을 실행할 시점을 결정하는 가시성 비율 임계값입니다.

요소가 화면에 보이는 순간을 “정확히 언제” 감지할지 정의하는 옵션으로, 0.0(1픽셀만 보여도)부터 1.0(완전히 다 보일 때)까지 설정할 수 있으며, 배열로 여러 시점을 동시에 감지할 수도 있습니다.

먼저, 기초부터 이해하기

왜 threshold가 필요한가?

IntersectionObserver는 요소가 뷰포트에 들어오고 나가는 것을 감지합니다. 하지만 “들어온다”는 게 정확히 무엇을 의미할까요?

상황 1: 요소의 1픽셀만 보임
┌──────────────┐
│ 뷰포트        │
│ ┌─┐          │  ← 이것도 "보인다"고 할까?
└─┴─┴──────────┘
  └─┘ 요소

상황 2: 요소의 50%가 보임
┌──────────────┐
│ 뷰포트        │
│ ┌──────┐    │  ← 이제야 "보인다"고 할까?
│ │      │    │
└─┴──────┴─────┘
  └──────┘ 요소

상황 3: 요소 전체가 보임
┌──────────────┐
│ ┌──────┐     │  ← 완전히 보여야 "보인다"고 할까?
│ │  요소 │     │
│ └──────┘     │
└──────────────┘

세 가지 상황 모두 요소가 “교차”하고 있지만, 우리가 원하는 시점은 다를 수 있습니다.

  • 이미지 lazy loading: 1픽셀만 보여도 바로 로드를 시작해야 끊김이 없습니다
  • 광고 노출 측정: 50% 이상 보여야 “노출”로 인정됩니다
  • 애니메이션 트리거: 완전히 보일 때만 실행하고 싶을 수 있습니다

threshold는 바로 이 “언제”를 정의하는 옵션입니다.

근본 원리: threshold는 어떻게 작동하는가?

MDN 공식 문서에 따르면:

“Rather than reporting every infinitesimal change in how much a target element is visible, the Intersection Observer API uses thresholds.”

브라우저는 요소의 가시성이 변할 때마다 매번 콜백을 실행하지 않습니다. 대신, 우리가 지정한 threshold 값을 넘을 때만 콜백을 실행합니다. 이는 성능을 위한 설계입니다.

// threshold: 0.5일 때의 동작

// 가시성 0% → 49%: 콜백 실행 안 됨 (threshold 미달)
// 가시성 49% → 50%: ✅ 콜백 실행! (threshold 통과)
// 가시성 50% → 100%: 콜백 실행 안 됨 (이미 통과함)
// 가시성 100% → 49%: ✅ 콜백 실행! (threshold 다시 통과)

중요한 점은 threshold를 “통과하는 순간”에만 콜백이 실행된다는 것입니다.

threshold의 구조

1. 값의 타입과 범위

MDN 문서에서 threshold는 다음과 같이 정의됩니다:

“Either a single number or an array of numbers between 0.0 and 1.0, specifying a ratio of intersection area to total bounding box area”

타입:

  • 단일 숫자 (number)
  • 숫자 배열 (number[])

범위:

  • 0.0 ~ 1.0
  • 이 범위를 벗어나면 RangeError 발생

기본값:

  • 0 (MDN: “The default is a threshold of ‘0’”)
// 유효한 threshold 값들
{ threshold: 0 }           // ✅ 단일 숫자
{ threshold: 0.5 }         // ✅ 소수점
{ threshold: 1.0 }         // ✅ 완전한 가시성
{ threshold: [0, 0.5, 1] } // ✅ 배열

// 유효하지 않은 값들
{ threshold: -0.1 }        // ❌ RangeError (0 미만)
{ threshold: 1.5 }         // ❌ RangeError (1 초과)
{ threshold: '0.5' }       // ❌ TypeError (문자열)

2. 단일 값 설정

가장 기본적인 사용법입니다.

// 예제 1: 기본값 (threshold: 0)
const observer1 = new IntersectionObserver(callback);
// 1픽셀만 보여도 콜백 실행

// 예제 2: 50% 보일 때
const observer2 = new IntersectionObserver(callback, {
  threshold: 0.5
});

// 예제 3: 완전히 보일 때만
const observer3 = new IntersectionObserver(callback, {
  threshold: 1.0
});

threshold 값의 의미

각 값이 실제로 무엇을 의미하는지 명확히 이해해봅시다:

threshold 의미 콜백 실행 시점
0 1픽셀이라도 교차 요소가 뷰포트에 진입/이탈하는 순간
0.25 25% 교차 요소의 1/4이 보이거나 숨겨지는 순간
0.5 50% 교차 요소의 절반이 보이거나 숨겨지는 순간
0.75 75% 교차 요소의 3/4이 보이거나 숨겨지는 순간
1.0 100% 교차 요소 전체가 보이거나 숨겨지는 순간

실전 예제: 광고 노출 측정

// IAB(Interactive Advertising Bureau) 표준: 광고의 50% 이상이 1초 동안 보여야 "노출"
const adObserver = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      // 50% 이상 보임
      setTimeout(() => {
        // 1초 후에도 여전히 보이는지 확인
        if (entry.target.isIntersecting) {
          trackAdImpression(entry.target);
        }
      }, 1000);
    }
  }
}, {
  threshold: 0.5  // 50% 임계값
});

const ads = document.querySelectorAll('.advertisement');
for (const ad of ads) {
  adObserver.observe(ad);
}

3. 배열로 여러 threshold 설정

여러 시점을 동시에 감지하고 싶을 때 배열을 사용합니다.

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    console.log('현재 가시성:', entry.intersectionRatio);
    // 각 threshold를 통과할 때마다 이 콜백이 실행됨
  }
}, {
  threshold: [0, 0.25, 0.5, 0.75, 1.0]
});

배열 사용 시 동작 원리

MDN 문서에 따르면, 배열로 지정된 각 threshold 값을 통과할 때마다 개별적으로 콜백이 실행됩니다.

// threshold: [0, 0.5, 1]로 설정했을 때

// 스크롤 다운 (요소가 점점 보임):
// 가시성 0% → 1%   ✅ 콜백 실행 (threshold 0 통과)
// 가시성 1% → 49%    콜백 실행 안 됨
// 가시성 49% → 50% ✅ 콜백 실행 (threshold 0.5 통과)
// 가시성 50% → 99%   콜백 실행 안 됨
// 가시성 99% → 100% ✅ 콜백 실행 (threshold 1.0 통과)

// 스크롤 업 (요소가 점점 숨겨짐):
// 가시성 100% → 99% ✅ 콜백 실행 (threshold 1.0 역방향 통과)
// 가시성 99% → 50%    콜백 실행 안 됨
// 가시성 50% → 49%  ✅ 콜백 실행 (threshold 0.5 역방향 통과)
// 가시성 49% → 0%     콜백 실행 안 됨
// 가시성 0%          ✅ 콜백 실행 (threshold 0 역방향 통과)

실전 예제: 부드러운 fade-in 애니메이션

// 요소가 보이는 정도에 따라 투명도를 점진적으로 조절
const fadeObserver = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    // intersectionRatio: 실제 교차 비율 (0.0 ~ 1.0)
    const opacity = entry.intersectionRatio;
    entry.target.style.opacity = opacity;
  }
}, {
  // 0.1 단위로 11개의 threshold 설정
  threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
});

const fadeElements = document.querySelectorAll('.fade-element');
for (const el of fadeElements) {
  fadeObserver.observe(el);
}

배열 생성 헬퍼

많은 threshold를 설정할 때 유용한 패턴입니다:

// 예제 1: 0.05 단위로 20개 생성 (0, 0.05, 0.1, ..., 0.95, 1.0)
function createThresholds(count = 20) {
  const thresholds = [];
  for (let i = 0; i <= count; i++) {
    thresholds.push(i / count);
  }
  return thresholds;
}

const observer = new IntersectionObserver(callback, {
  threshold: createThresholds(20)
});

// 예제 2: Array.from 사용
const thresholds = Array.from({ length: 21 }, (_, i) => i * 0.05);
// [0, 0.05, 0.1, 0.15, ..., 0.95, 1.0]

// 예제 3: 특정 구간만 세밀하게
const customThresholds = [
  0,           // 진입 감지
  ...Array.from({ length: 10 }, (_, i) => (i + 1) * 0.1),  // 0.1 ~ 1.0
  1.0          // 완전 가시성
];

threshold와 관련 속성들의 관계

1. threshold vs intersectionRatio

이 둘은 혼동하기 쉬우므로 명확히 구분해야 합니다.

threshold:

  • 옵션으로 설정하는 값
  • “언제 콜백을 실행할지”를 정의
  • 고정된 값

intersectionRatio:

  • 콜백에서 읽는 값 (IntersectionObserverEntry 속성)
  • “실제로 얼마나 보이는지”를 나타냄
  • 실시간으로 변하는 값
const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    // threshold는 0.5로 설정했지만
    // intersectionRatio는 0.5, 0.51, 0.6, ... 등 다양한 값이 될 수 있음
    console.log('설정한 threshold:', 0.5);
    console.log('실제 가시성:', entry.intersectionRatio);
  }
}, {
  threshold: 0.5  // 이 값을 통과할 때만 콜백 실행
});

실제 동작 예시

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    console.log({
      isIntersecting: entry.isIntersecting,
      intersectionRatio: entry.intersectionRatio
    });
  }
}, {
  threshold: 0.5
});

// 스크롤하면서 요소가 점점 보일 때:

// 가시성 49%일 때: 콜백 실행 안 됨
// 가시성 50%일 때:
// 출력: { isIntersecting: true, intersectionRatio: 0.5 }

// 가시성 60%일 때: 콜백 실행 안 됨 (이미 threshold 통과함)
// 가시성 100%일 때: 콜백 실행 안 됨

// threshold: [0.5, 1.0]으로 설정했다면:
// 가시성 50%일 때: { isIntersecting: true, intersectionRatio: 0.5 }
// 가시성 100%일 때: { isIntersecting: true, intersectionRatio: 1.0 }

2. threshold vs isIntersecting

isIntersecting:

  • boolean 값 (true/false)
  • threshold를 넘었는지 여부
// threshold: 0.5일 때

// intersectionRatio: 0.49 → isIntersecting: false
// intersectionRatio: 0.50 → isIntersecting: true
// intersectionRatio: 0.51 → isIntersecting: true
// intersectionRatio: 1.00 → isIntersecting: true

주의: threshold가 0일 때의 특별한 동작

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    // threshold: 0일 때는 1픽셀만 보여도 true
    console.log(entry.isIntersecting);
  }
}, {
  threshold: 0  // 기본값
});

// 요소가 완전히 안 보임: isIntersecting: false
// 1픽셀이라도 보임: isIntersecting: true

실전 활용 패턴

1. 이미지 Lazy Loading (threshold: 0 또는 작은 값)

화면에 진입하기 전에 미리 로드해야 끊김이 없습니다.

const imageObserver = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      imageObserver.unobserve(img);
    }
  }
}, {
  threshold: 0,  // 1픽셀만 보여도 로드 시작
  rootMargin: '50px'  // 실제로는 50px 전에 로드
});

const lazyImages = document.querySelectorAll('img[data-src]');
for (const img of lazyImages) {
  imageObserver.observe(img);
}

2. 스크롤 애니메이션 (threshold: 0.1 ~ 0.3)

요소가 충분히 보일 때 애니메이션을 시작합니다.

const animateObserver = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      entry.target.classList.add('animate-in');
      animateObserver.unobserve(entry.target);  // 한 번만 실행
    }
  }
}, {
  threshold: 0.2  // 20% 보일 때 시작
});

const animatedElements = document.querySelectorAll('.animate-on-scroll');
for (const el of animatedElements) {
  animateObserver.observe(el);
}
.animate-on-scroll {
  opacity: 0;
  transform: translateY(30px);
  transition: opacity 0.6s, transform 0.6s;
}

.animate-on-scroll.animate-in {
  opacity: 1;
  transform: translateY(0);
}

3. 광고 가시성 측정 (threshold: 0.5)

IAB 표준에 따라 50% 이상 보여야 노출로 인정합니다.

const adObserver = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
      // 50% 이상 보임
      const adId = entry.target.dataset.adId;
      trackAdImpression(adId);
      adObserver.unobserve(entry.target);
    }
  }
}, {
  threshold: 0.5
});

const advertisements = document.querySelectorAll('.advertisement');
for (const ad of advertisements) {
  adObserver.observe(ad);
}

4. 완전 가시성 체크 (threshold: 1.0)

요소 전체가 화면에 보일 때만 실행합니다.

// 사용 사례: 중요한 공지사항이 완전히 보였는지 확인
const noticeObserver = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting && entry.intersectionRatio === 1.0) {
      // 공지사항 전체를 봤음을 기록
      markAsRead(entry.target.dataset.noticeId);
      noticeObserver.unobserve(entry.target);
    }
  }
}, {
  threshold: 1.0  // 100% 보여야 함
});

5. 진행률 추적 (threshold: 배열)

요소가 얼마나 보이는지 단계별로 추적합니다.

// 긴 아티클을 얼마나 읽었는지 추적
const readingObserver = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      const progress = Math.round(entry.intersectionRatio * 100);
      console.log(`아티클 ${progress}% 읽음`);

      // 분석 이벤트 전송
      if (progress === 25 || progress === 50 || progress === 75 || progress === 100) {
        trackReadingProgress(progress);
      }
    }
  }
}, {
  threshold: [0, 0.25, 0.5, 0.75, 1.0]  // 25% 단위로 추적
});

const article = document.querySelector('.article-content');
readingObserver.observe(article);

6. 부드러운 색상 전환 (threshold: 세밀한 배열)

// MDN 문서의 예제와 유사한 패턴
const colorObserver = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    const ratio = entry.intersectionRatio;
    const target = entry.target;

    // 가시성에 따라 배경색 변경
    const opacity = Math.min(ratio * 2, 1);  // 0.5에 완전 불투명
    target.style.backgroundColor = `rgba(40, 40, 190, ${opacity})`;

    // 가시성에 따라 크기 변경
    const scale = 0.5 + (ratio * 0.5);  // 0.5 ~ 1.0 scale
    target.style.transform = `scale(${scale})`;
  }
}, {
  threshold: Array.from({ length: 100 }, (_, i) => i / 100)  // 0.01 단위
});

const colorBoxes = document.querySelectorAll('.color-box');
for (const box of colorBoxes) {
  colorObserver.observe(box);
}

주의사항과 함정

1. threshold는 정확한 픽셀값이 아님

MDN 문서에 따르면, threshold는 “가시성 비율”을 의미합니다:

“Specifying a ratio of intersection area to total bounding box area”

// ❌ 잘못된 이해
// threshold: 0.5 → "500px 보일 때" (X)

// ✅ 올바른 이해
// threshold: 0.5 → "요소 전체 면적의 50%가 보일 때" (O)

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    // 정확한 픽셀 높이가 필요하다면:
    const visibleHeight = entry.intersectionRect.height;
    const totalHeight = entry.boundingClientRect.height;
    const visiblePercentage = (visibleHeight / totalHeight) * 100;

    console.log(`${visibleHeight}px / ${totalHeight}px (${visiblePercentage}%)`);
  }
}, {
  threshold: 0.5
});

2. 요소 크기가 다르면 같은 threshold도 다른 픽셀값

// 작은 요소 (100px 높이)
// threshold: 0.5 = 50px 보일 때

// 큰 요소 (1000px 높이)
// threshold: 0.5 = 500px 보일 때

// 같은 threshold라도 요소 크기에 따라 실제 픽셀값은 다름!

3. threshold: 1.0의 특별한 제약

threshold: 1.0은 요소 전체가 보여야 하므로, 요소가 뷰포트보다 크면 절대 isIntersecting: true가 될 수 없습니다.

// ❌ 문제 상황
const hugeElement = document.querySelector('.huge');
// 요소 높이: 2000px
// 뷰포트 높이: 800px

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    // 이 코드는 절대 실행되지 않음!
    if (entry.isIntersecting) {
      console.log('완전히 보임');
    }
  }
}, {
  threshold: 1.0  // 2000px가 800px 안에 다 들어올 수 없음
});

observer.observe(hugeElement);

// ✅ 해결책: 적절한 threshold 사용
const betterObserver = new IntersectionObserver(callback, {
  threshold: 0.8  // 80% 정도면 "충분히 보인다"고 판단
});

4. 범위를 벗어난 값은 에러 발생

MDN 문서에 따르면, threshold는 반드시 0.0 ~ 1.0 범위여야 하며, 그렇지 않으면 RangeError가 발생합니다.

// ❌ RangeError 발생
try {
  const observer = new IntersectionObserver(callback, {
    threshold: -0.1  // 0 미만
  });
} catch (e) {
  console.error(e);  // RangeError
}

try {
  const observer = new IntersectionObserver(callback, {
    threshold: 1.5  // 1 초과
  });
} catch (e) {
  console.error(e);  // RangeError
}

// ✅ 올바른 범위
const validThresholds = [0, 0.5, 1.0];  // 모두 0.0 ~ 1.0

5. 너무 많은 threshold는 성능 영향

// ❌ 과도한 threshold (성능 저하 가능)
const observer = new IntersectionObserver(callback, {
  threshold: Array.from({ length: 1000 }, (_, i) => i / 1000)
  // 0.001 단위로 1000개 설정 - 콜백이 너무 자주 실행됨
});

// ✅ 필요한 만큼만 설정
const observer = new IntersectionObserver(callback, {
  threshold: [0, 0.25, 0.5, 0.75, 1.0]
  // 5개의 threshold로도 대부분의 경우 충분함
});

성능 가이드라인:

  • 단순 진입/이탈 감지: threshold: 0 (1개)
  • 단계별 추적: threshold: [0, 0.25, 0.5, 0.75, 1] (5개)
  • 부드러운 애니메이션: threshold: 0.01 ~ 0.05 단위 (20 ~ 100개)
  • 1000개 이상: 거의 필요 없으며 성능 저하 가능

6. 배열의 순서는 중요하지 않음

// 이 두 설정은 완전히 동일하게 작동함
const observer1 = new IntersectionObserver(callback, {
  threshold: [0, 0.5, 1.0]
});

const observer2 = new IntersectionObserver(callback, {
  threshold: [1.0, 0, 0.5]  // 순서가 달라도 결과는 같음
});

// 브라우저가 내부적으로 정렬하여 처리함

디버깅 팁

threshold 동작 확인하기

const debugObserver = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    console.group('Threshold Debug');
    console.log('isIntersecting:', entry.isIntersecting);
    console.log('intersectionRatio:', entry.intersectionRatio);
    console.log('Time:', entry.time);

    // 어떤 threshold를 통과했는지 역추적
    const passedThreshold = [0, 0.25, 0.5, 0.75, 1.0].find(t =>
      Math.abs(t - entry.intersectionRatio) < 0.01
    );
    console.log('Likely triggered by threshold:', passedThreshold);
    console.groupEnd();
  }
}, {
  threshold: [0, 0.25, 0.5, 0.75, 1.0]
});

const element = document.querySelector('.test-element');
debugObserver.observe(element);

시각적 피드백 추가

const visualObserver = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    const ratio = entry.intersectionRatio;
    const percentage = Math.round(ratio * 100);

    // 요소에 현재 가시성 표시
    entry.target.dataset.visibility = `${percentage}%`;
    entry.target.style.setProperty('--visibility', ratio);

    // 콘솔에도 표시
    console.log(`${entry.target.className}: ${percentage}% visible`);
  }
}, {
  threshold: Array.from({ length: 11 }, (_, i) => i * 0.1)
});
/* CSS에서 --visibility 변수 활용 */
.test-element {
  opacity: var(--visibility);
}

.test-element::after {
  content: attr(data-visibility);
  position: absolute;
  top: 0;
  right: 0;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 4px 8px;
  font-size: 12px;
}

프레임워크에서의 threshold 사용

React Custom Hook

import { useEffect, useRef, useState } from 'react';

function useVisibility(threshold = 0) {
  const ref = useRef(null);
  const [isVisible, setIsVisible] = useState(false);
  const [ratio, setRatio] = useState(0);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;

    const observer = new IntersectionObserver(
      ([entry]) => {
        setIsVisible(entry.isIntersecting);
        setRatio(entry.intersectionRatio);
      },
      { threshold }
    );

    observer.observe(element);

    return () => {
      observer.disconnect();
    };
  }, [threshold]);

  return { ref, isVisible, ratio };
}

// 사용 예제
function LazyImage({ src, alt }) {
  const { ref, isVisible } = useVisibility(0.1);

  return (
    <img
      ref={ref}
      src={isVisible ? src : 'placeholder.jpg'}
      alt={alt}
    />
  );
}

// 여러 threshold 사용
function ProgressTracker({ children }) {
  const { ref, ratio } = useVisibility([0, 0.25, 0.5, 0.75, 1]);

  return (
    <div ref={ref} style={{ opacity: ratio }}>
      {children}
    </div>
  );
}

Vue Composable

import { ref, onMounted, onUnmounted } from 'vue';

export function useVisibility(thresholdValue = 0) {
  const targetRef = ref(null);
  const isVisible = ref(false);
  const ratio = ref(0);
  let observer = null;

  onMounted(() => {
    if (!targetRef.value) return;

    observer = new IntersectionObserver(
      ([entry]) => {
        isVisible.value = entry.isIntersecting;
        ratio.value = entry.intersectionRatio;
      },
      { threshold: thresholdValue }
    );

    observer.observe(targetRef.value);
  });

  onUnmounted(() => {
    if (observer) {
      observer.disconnect();
    }
  });

  return { targetRef, isVisible, ratio };
}
<template>
  <div ref="targetRef" :style="{ opacity: ratio }">
    {{ isVisible ? 'Visible!' : 'Hidden' }}
  </div>
</template>

<script setup>
import { useVisibility } from './useVisibility';

const { targetRef, isVisible, ratio } = useVisibility(0.5);
</script>

요약

threshold 핵심 정리

  1. 정의: 콜백 실행 시점을 결정하는 가시성 비율 (0.0 ~ 1.0)
  2. 타입: 단일 숫자 또는 숫자 배열
  3. 기본값: 0 (1픽셀만 보여도 실행)
  4. 동작: threshold를 통과하는 순간 콜백 실행
  5. 배열: 각 값을 통과할 때마다 개별 실행

적절한 threshold 선택 가이드

용도 권장 threshold 이유
이미지 lazy loading 0 또는 작은 값 (0.01) 끊김 없이 미리 로드
스크롤 애니메이션 0.1 ~ 0.3 충분히 보일 때 시작
광고 노출 측정 0.5 IAB 표준 (50%)
완전 가시성 체크 1.0 전체가 보여야 함
진행률 추적 [0, 0.25, 0.5, 0.75, 1] 단계별 추적
부드러운 전환 0.01 ~ 0.05 단위 배열 세밀한 제어

체크리스트

  • threshold 범위는 0.0 ~ 1.0인가?
  • 요소가 뷰포트보다 크면 threshold: 1.0은 작동 안 함
  • 너무 많은 threshold (1000개 이상)는 성능 저하 가능
  • threshold는 비율이지 픽셀값이 아님
  • 배열 사용 시 각 값마다 콜백이 실행됨을 고려
  • intersectionRatio로 실제 가시성 확인 가능

참고 자료

MDN 공식 문서:

관련 문서:

댓글