이번에 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')
);
해당 과정을 살펴보면
Intersection Observer
객체를 생성하면서, Callback Function
과 option
을 전달한다.Intersection Observer
에서 observe
로 구독할 Target Element
를 추가한다.Target Element
가 options.threshold
로 정의한 Percent(%) 만큼 화면에 노출 혹은 제외 되면, entries 배열 에 추가하고, Callback Function
을 호출한다.Callback Function
에서 전달 받은 entries 배열을 확인하면서, isIntersecting 으로 노출 여부를 확인한다.Target Element
를 구독할 필요가 없다면, IntersectionObserver 에서 unobserve
로 제거 할 수 있다.Intersection Observer를 생성할 때는 옵션을 설정할 수 있다.
옵션에는 root, rootMargin, threshold가 있다.
root 로 정의된 Element 기준으로 Target Element 의 노출, 비노출 여부를 결정한다.
기본 값은 Browser ViewPort 이다.
만약 root 로 정의한 Element 의 Children 에 Target Element 가 존재하지 않는다면, 화면에 노출 되더라도, 노출로 보지 않는다.
만약 Target Element 가 Root Element 의 Children 이 되고, 화면에 노출되면, 노출로 보고 Callback Function 을 호출한다.
rootMargin 은 ‘0px 0px 0px 0px’ 형태로 정의할 수 있다.
rootMargin 이 있으면, threshold 계산할 때, rootMargin 영역 만큼 더 계산한다.
threshold 를 number
나 Array<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
Target Element 의 노출 혹은 비노출 따라 Intersection Observer Entry 배열을 만들고, Callback Function을 호출한다.
이때 처음에는 모든 Element 의 노출, 비노출 여부를 체크하기 때문에 반드시 구독한 전체 Target Element 을 Entry Array 에 넣어 호출한다.
하나의 Intersection Observer Entry 에는 아래 속성값이 정의되어 있다.
target
: Target Elementtime
: 노출되거나 비노출된 시간isIntersecting
: 노출 여부intersectionRatio
: 노출된 비율intersectionRect
: 노출된 영역boundingClientRect
: TargetElement.getBoundingClientRect() 값rootBounds
: RootElement의 bound 값 만약 옵션에서 지정하지 않았다면, 화면 크기 이다.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;
}
});
};
어때요 참 쉽죠?