본문 바로가기
프론트엔드 HTML CSS JAVASCRIPT

[HTML, JAVASCRIPT] 요소가 화면에 들어왔는지 확인하는 방법, Intersection Observer API

by jaewooojung 2024. 1. 23.

HTML JAVASCRIPT


Intersection Observer API

Intersection Observer는 보통 스크롤 이벤트에 반응하는 Lazy loading이나 애니메이션 트리거링을 할 때 주로 사용됩니다. 하지만 이것 자체로는 스크롤 이벤트를 직접 처리하는 도구는 아닙니다. 본질적인 기능은 훨씬 더 단순합니다.

 

Intersection Observer API의 기능은 단순히 특정 요소가 사용자의 뷰포트(화면) 혹은 다른 요소 안에 들어왔는지 감지하는 것입니다.

 

lazy loading에서 어느 시점에 데이터를 추가로 요청할 것인지 판단하는 것과 애니메이션 트리거링에서 언제 애니메이션을 시작할지 판단하는 것은, 모두 특정 요소를 적절한 곳에 위치시킨 후 Intersection Observer를 활용하여 그 요소가 화면에 들어왔는지 감지하면서 이루어집니다.

 

성능

요소 간의 교차점을 비동기적으로 관찰합니다. 이 API가 등장하기 전에는 뷰포트에 특정 요소가 들어왔는지 확인하기 위해서 스크롤 이벤트 리스너를 사용했는데요. 이 방법은 스크롤을 할 때마다 브라우저의 메인 스레드에서 이벤트 리스너가 실행되기 때문에 성능에 좋지 않습니다.

 

반면 Intersection Observer는 메인 스레드와 무관하게 요소를 감지하기 때문에 성능에 유리합니다. 또한 요소가 처음 교차되는 순간과 나가는 순간에만 콜백함수를 실행할 수 있기 때문에 throttle이나 debounce기법 없이도 좋은 성능을 낼 수 있습니다.

 

예시

1) 기본

box 컨테이너와 그 안에 스크롤링 컨테이너를 생성했습니다. 스크롤 도중 target이 화면에 들어오면 하단에 message로 출력해 주는 기능을 구현해 보겠습니다.

 

HTML

<div id="box">
  <div id="box-scroll">
    <span id="target">target</span>
  </div>
</div>
<div id="message"></div>

 

CSS

#box {
  height: 200px;
  overflow: auto;
  background-color: beige;
}

#box-scroll {
  height: 3000px;
  display: flex;
  align-items: center;
}

 

 

JAVASCRIPT

자바스크립트에서는 Intersection Observer를 생성한 후 observe 메서드를 통해 target을 관찰합니다. 콜백함수에서는 인자로 받는 entries 배열을 통해 관찰 대상 요소들 중 화면에 나타난 요소를 감지할 수 있습니다.

 

요소가 감지된 경우 message div에 문자열을 기록하도록 구현하였습니다.

const box = document.getElementById("box");
const target = document.getElementById("target");
const message = document.getElementById("message");

const observer = new IntersectionObserver(function (entries) {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      message.textContent = "INTERSECTION!!!";
    } else {
      message.textContent = "";
    }
  });
});

observer.observe(target);

 

스크롤 도중 target이 화면에 나타났을 때에 message가 기록됩니다.

(직접 스크롤링해 보세요)

target

 

 

2) 활용사례

연봉 실수령액을 계산하는 웹툴입니다. 연봉란에 금액을 입력하면 하단의 보라색 박스에 계산된 실수령액이 출력됩니다.

연봉 계산기

 

 

그런데 화면을 확대하거나, 작은 기기에서 웹사이트에 접속하면 아래와 같이 입력란과 출력란이 한 화면에 보이지 않는 경우가 있습니다.

출력란 짤림

 

실수령액은 연봉이 입력되면 자동으로 바로 계산이 되도록 구현했는데, 위와 같은 화면에서는 계산된 결과가 한눈에 보이지 않습니다.

 

이를 개선하기 위해 Intersection Observer를 사용해서 실수령액 출력란을 감지하도록 구현했습니다. 실수령액의 계산이 끝나는 시점에 출력란이 현재 화면에 보이지 않으면 우측 하단에 팝업창을 띄워 사용자에게 결과를 즉시 보여주는 방식입니다.

팝업으로 보여줌

 

 

입력란과 출력란이 한 화면에 보이거나, 팝업창이 뜬 상태에서 출력란이 화면 안으로 들어오는 경우 팝업창은 보이지 않습니다.

팝업창이 보이지 않는 경우

 

이처럼 lazy loading이나 애니메이션 트리거 외에도 Intersection observer를 활용해서 다양한 기능을 구현할 수 있습니다. (연봉계산기 - https://www.yeonbongtools.com/)

 

3) 애니메이션 트리거

마지막으로 화면에 요소가 나타났을 때 애니메이션을 실행하는 간단한 예제를 구현해 보겠습니다.

 

HTML

<div id="box">
  <div id="box-scroll">
    <div><span id="target-hello">Hello</span></div>
    <div><span id="target-world">World</span></div>
    <div><span id="target-again">AGAIN!</span></div>
  </div>
</div>

 

CSS

#box {
  height: 200px;
  overflow: auto;
  background-color: beige;
}

#box-scroll {
  height: 3000px;
}

#box-scroll > div {
  height: 1000px;
  display: flex;
  align-items: center;
}

#box-scroll > div > span {
  font-size: 100px;
  font-weight: bold;
  opacity: 0;
  text-shadow: 2px 4px blueviolet;
}

.intersection {
  animation: fadein 1s forwards;
}

@keyframes fadein {
  0% {
    opacity: 0;
    transform: translateY(50px);
  }
  80% {
    color: black;
  }
  100% {
    opacity: 1;
    transform: translateY(0px);
    color: burlywood;
  }
}

 

JAVASCRIPT

const targetHello = document.getElementById("target-hello");
const targetWorld = document.getElementById("target-world");
const targetAgain = document.getElementById("target-again");

const observer = new IntersectionObserver(function (entries) {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      entry.target.classList.add("intersection");
    } else {
      entry.target.classList.remove("intersection");
    }
  });
});

observer.observe(targetHello);
observer.observe(targetWorld);
observer.observe(targetAgain);

 

3개의 target 요소를 감지하며 스크롤에 의해 target이 화면에 나타났을 때 애니메이션을 트리거합니다.

(직접 스크롤링해 보세요)

Hello
World
AGAIN!

 

4) 리액트 예시

리액트에서도 간단하게 구현할 수 있습니다. 요소에 접근할 때는 id보다는 ref를 활용해 주고, 첫 마운트 이후에 reference를 달아주기 위해 useEffect에서 구현해 줍니다. 언마운트시에는 disconnect 메서드를 활용하여 연결을 끊어줍니다.

import "./style.css";
import { useEffect, useRef } from "react";

export default function App() {
  const helloRef = useRef(null);
  const worldRef = useRef(null);
  const againRef = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(function (entries) {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          entry.target.classList.add("intersection");
        } else {
          entry.target.classList.remove("intersection");
        }
      });
    });

    observer.observe(helloRef.current);
    observer.observe(worldRef.current);
    observer.observe(againRef.current);

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

  return (
    <div id="box">
      <div id="box-scroll">
        <div>
          <span ref={helloRef}>Hello</span>
        </div>
        <div>
          <span ref={worldRef}>World</span>
        </div>
        <div>
          <span ref={againRef}>AGAIN!</span>
        </div>
      </div>
    </div>
  );
}

 

 



        
답변을 생성하고 있어요.