React - Intersection Observer API를 이용한 lazy loading

Shin Yeongjae·2020년 8월 2일
1

Wecode

목록 보기
21/26

이번에 1차 프로젝트를 하면서 가장 해보고 싶었던 Image Lazy Loading을 구현해봤다. 처음에는 라이브러리를 사용하려고 했으나 멘토 종택님께서 Intersection Observer API라는것을 알려주셔서 한 번 적용해봤다. 이게 없었다면 아마 하루종일 scroll 수치 계산을 하고 있었지 않을까 생각한다. 그만큼 간단하게 lazy loading을 구현할 수 있었다.

간단한 예제

const options = { threshold: 1.0 };
const callback = (entries, observer) => {
    entries.forEach((entry) => {
        if (entry.isIntersecting) {
            observer.unobserve(entry.target);
            console.log('화면에서 노출됨');
        } else {
            console.log('화면에서 제외됨');
        }
    });
}
const observer = new IntersectionObserver(callback, options);
observer.observe(
    document.getElementById('id')
);

해당 과정을 살펴보면

  1. Intersection Observer 객체를 생성하면서, Callback Functionoption 을 전달한다.
  2. Intersection Observer 에서 observe 로 구독할 Target Element 를 추가한다.
  3. Target Elementoptions.threshold 로 정의한 Percent(%) 만큼 화면에 노출 혹은 제외 되면, entries 배열 에 추가하고, Callback Function 을 호출한다.
  4. Callback Function 에서 전달 받은 entries 배열을 확인하면서, isIntersecting 으로 노출 여부를 확인한다.
  5. 만약 더이상 Target Element 를 구독할 필요가 없다면, IntersectionObserver 에서 unobserve 로 제거 할 수 있다.

Intersection Observer

Intersection Observer를 생성할 때는 옵션을 설정할 수 있다.

옵션에는 root, rootMargin, threshold가 있다.

1. root

root 로 정의된 Element 기준으로 Target Element 의 노출, 비노출 여부를 결정한다.

기본 값은 Browser ViewPort 이다.

만약 root 로 정의한 Element 의 Children 에 Target Element 가 존재하지 않는다면, 화면에 노출 되더라도, 노출로 보지 않는다.

만약 Target Element 가 Root Element 의 Children 이 되고, 화면에 노출되면, 노출로 보고 Callback Function 을 호출한다.

2. rootMargin

rootMargin 은 ‘0px 0px 0px 0px’ 형태로 정의할 수 있다.

rootMargin 이 있으면, threshold 계산할 때, rootMargin 영역 만큼 더 계산한다.

3. threshold

threshold 를 numberArray<number> 로 정의할 수 있다. 정의하지 않는다면 기본값은 0 이다.

number 로 정의할 경우, Target Element 의 노출 비율에 따라 한번 Callback Function 을 호출할 수 있지만, Array<number> 로 정의할 경우, 각각의 비율로 노출될 때마다 Callback Function 을 호출한다.

const optionOne = { threshold: 1.0 };
const observer = new IntersectionObserver(callBack, optionOne);
// isIntersecting: false, intersectionRatio: 0
// isIntersecting: true, intersectionRatio: 1
// isIntersecting: false, intersectionRatio: 0

const optionArray = { threshold: [0, 0.25, 0.5, 0.75, 1] };
const observer = new IntersectionObserver(callBack, options);
// isIntersecting: false, intersectionRatio: 0
// isIntersecting: true, intersectionRatio: 0.0216450225561857
// isIntersecting: true, intersectionRatio: 0.2532467544078827
// isIntersecting: true, intersectionRatio: 0.5043290257453918
// isIntersecting: true, intersectionRatio: 0.7510822415351868
// isIntersecting: true, intersectionRatio: 1
// isIntersecting: true, intersectionRatio: 0.7467532753944397
// isIntersecting: true, intersectionRatio: 0.49567100405693054
// isIntersecting: true, intersectionRatio: 0.24891774356365204
// isIntersecting: false, intersectionRatio: 0

Intersection Observer Entry

Target Element 의 노출 혹은 비노출 따라 Intersection Observer Entry 배열을 만들고, Callback Function을 호출한다.

이때 처음에는 모든 Element 의 노출, 비노출 여부를 체크하기 때문에 반드시 구독한 전체 Target Element 을 Entry Array 에 넣어 호출한다.

하나의 Intersection Observer Entry 에는 아래 속성값이 정의되어 있다.

  • target: Target Element
  • time: 노출되거나 비노출된 시간
  • isIntersecting: 노출 여부
  • intersectionRatio: 노출된 비율
  • intersectionRect: 노출된 영역
  • boundingClientRect: TargetElement.getBoundingClientRect() 값
  • rootBounds: RootElement의 bound 값 만약 옵션에서 지정하지 않았다면, 화면 크기 이다.

Image Lazy Loading 구현해보기

Intersection Observer 를 이용하면 쉽게 Image Lazy Load 를 구현할 수 있다.

이미지 리스트를 만들고, 이를 작은 이미지로 채운다.

그리고, 실제 노출되고 싶은 이미지를 Data 로 채워 넣는다.

다음은 이번 프로젝트때 사용한 코드이다.

<div className="imgBox">
  <img
    alt="product Image"
    className="product"
    ref={this.imgRef}
    src="/images/preview.gif"
    data-src={main_image_url}
  />
</div>
import React from "react";
import { withRouter, Link } from "react-router-dom";
import API_URL from "../../../Config.js";
import "./Each.scss";

const options = { threshold: 0.5 };

class Each extends React.Component {
  constructor(props) {
    super(props);
    this.imgRef = React.createRef();
    this.state = {
      isHovered: false,
    };
  }

  componentDidMount() {
    const observer = new IntersectionObserver(this.callback, options);
    observer.observe(this.imgRef.current);
  }

  callback = (entries, observer) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        observer.unobserve(entry.target);
        entry.target.src = entry.target.dataset.src;
      }
    });
  };

어때요 참 쉽죠?

profile
문과생의 개발자 도전기

0개의 댓글