IntersectionObserver를 어떻게 사용하는 지 알았으니 이제 본격적으로 웹사이트에 적용할 차례이다.
lazy loading을 적용하기 전 웹 사이트의 모습이다. 극단적인 예시를 위해 네트워크 상황을 Fast 3G로 설정하였다.
웹 페이지가 로딩됨과 동시에 페이지의 이미지 파일을 전부 다운로드하고 이들이 순서대로 나타나는 것을 확인할 수 있다.
페이지 내의 모든 이미지 파일에 IntersectionObserve를 사용하기 위해 이미지마다 observer를 부착시킨 뒤 IntersectionObserver를 실행시킨다.
<img src={props.presrc} alt={props.label+' 이미지'} className="cards__item__img" data-lazy={props.src}/>
이미지 태그는 위와 같이 설정하였다. src 속성에는 일종의 placeholder 이미지를 지정하고 실제 나타낼 이미지는 data-lazy 속성에 지정하였다.
const images = document.querySelectorAll("img");
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const image = entry.target;
const src = image.getAttribute("data-lazy");
image.setAttribute("src", src);
observer.unobserve(image);
})
images.forEach((image) => {
observer.observe(image);
})
위와 같이 선언하면 모든 이미지들에 observer가 부착되며 isIntersecting 메서드를 통해 이미지의 src 속성이 바뀌게 된다.
여기서 추가로 발생한 문제점이 바로 react router 때문이었다. router를 사용해 각 페이지를 구현했는데 해당 페이지에서 새로 고침 시 이미지가 로딩되지 않는 문제점이 발생했다.
처음에는 window.addEventListener('load', ~~)
를 통해 해결하려 했으나 해당 이벤트는 최초 실행 시 즉, App.js
가 최초로 실행 시에만 발생하고 라우터 페이지에서 새로 고침을 아무리 해도 해결되지 않았다.
lazy loading을 적용하지 말고 그냥 WebP 이미지를 활용해 최적화할까 생각하던 중 class Component
를 사용해야겠다는 생각이 번쩍 들었다. (진짜 번개처럼 갑자기 생각났음)
기존의 구조
App | - Bread (빵 목록 보여주는 페이지) | | - BreadList (실제 빵 종류들) | | - Footer | - Vegetable | | - VegetableList | | - Footer | - Cheese | | - CheeseList | | - Footer | - Sauce | - SauceList | - Footer
기존에는 위 구조를 모두 function component로 선언했다. 여기서 이미지가 렌더링되는 파일은 ~List
인데 이 파일들을 모두 class component로 바꿔주었다.
이렇게 되면 다양한 lifecycle 메서드의 활용이 가능하다. 실제로도 확인해 보니 새로고침을 해도 componentDidMount 메서드는 항상 호출되는 것을 확인할 수 있었다.
최종 class component의 코드는 대략 다음과 같다.
class BreadList extends React.Component {
// componentDidMount 내에 IntersectionObserver를 선언
// 새로 고침 시에도 이 부분이 항상 실행됨. 문제 해결!
componentDidMount() {
const images = document.querySelectorAll("img");
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const image = entry.target;
const src = image.getAttribute("data-lazy");
image.setAttribute("src", src);
observer.unobserve(image);
})
})
images.forEach((image) => {
observer.observe(image);
})
}
render() {
return (
<div>빵 목록들</div>
)
}
}
이전과 달리 이미지가 로딩되기 전 로딩을 나타내는 이미지가 나타나며, 스크롤을 내릴 시에만 필요한 이미지가 추가로 로딩되는 것을 확인할 수 있다. 또한, 새로 고침 시에도 문제 없이 이미지가 로딩된다.
복잡한 내용은 아니지만 실제 웹 사이트에 적용을 하려니 생각보다 시간이 많이 걸렸다. 더군다나 리액트의 특성까지 문제를 일으켜서 해결책을 찾는데 고심했다.
하지만 이렇게 해결하고 나니 이제 진짜 내 것이 되었다는 생각이 든다!😁