프로젝트를 진행하면서 처음에 로드될필요가 없는 여러 이미지들에 lazy loading을 적용하였고, 결과적으로 초기 로드시간을 단축할수 있었습니다. 하지만 이후 테스트를 통해 확인해보니 ui 적으로 문제가 있었습니다. 추후 로드되는 이미지 때문에 자연스럽지 못한 화면이 나왔던것입니다. 이번포스트에서는 이러한 이미지 렌더링 ui를 preloading을 통해 개선해본 경험을 공유합니다.
문제가 발생한 부분은 포스터 이미지를 보여주는 일종의 캐러셀이었습니다. 이 캐러셀은 5개의 요소를 페이지에 맞게 교체해주면서 동작하고 있습니다. 코드로 보면 다음과 같습니다.
cardList.slice(5 * (page - 1), 5 * page).map((card) => {
return <PopularCard card={card} />;
});
처음 이미지를 로드하고 다음페이지로 이동할경우 이전 페이지의 이미지가 렌더링되다가 다음페이지의 이미지를 다 받아왔을 경우 해당 이미지가 적용됩니다. 캐러셀 아래의 텍스트 정보는 이미 변경되어있으므로 ui적으로 좋지 못한상황입니다. 이를 해결하기위해 텍스트 또한 이미지 렌더링 뒤로 미루는 방법을 사용할수도 있었지만, 사용자가 캐러셀 클릭시 이미지를 로드하는 동안 반응을 하지 않기에 더 좋지 못하다고 생각하여 preloading을 적용하였습니다.
pre loading은 말그대로 미리 로드하는 기술입니다. 사실 lazy loading과 같이 사용되는 경우가 많은데, 이미지에 lazy loading을 적용한뒤 스크롤 혹은 전환을 하여 이미지가 보이는 순간보다 조금 빠른시점에 로드하는경우가 많기 때문입니다.
위에서 문제가 발생한 캐러셀의 경우에도 모든 리스트를 처음에 렌더링 하지 않음으로써 일종의 lazy loading이 적용되어 있는데 이때 이미지를 로드하는데 시간이 오래걸려 ui적으로 좋지 못한 현상이 발생하는것입니다. 따라서 이미지를 일정 페이지 이전에 미리 로드하게 되면 문제를 해결할수 있습니다. 예를들어 1페이지에서 2페이지로 처음 넘어가는순간 3페이지의 이미지를 미리 로드하는것입니다.
이를 코드로 작성해보겠습니다. 처음 데이터를 받아올때는 2페이지의 데이터를 로드해줍니다. 1페이지는 처음 로드할때 알아서 가져오기때문에 따로 로드하지는 않습니다. 이후 페이지를 넘겨줄때 페이지당 한번만 이미지 pre loading이 일어나도록 처리합니다. 여기서 핵심은 로드할때 사용하는 image 객체입니다. 해당 객체는 Image 내장 함수를 사용해서 생성한뒤, src 속성에 주소를 넣어주면 알아서 로드해 캐싱하는 효과를 가지고 있습니다.
const MoviePosterCardList = () => {
const page = useSelector((store) => store.mainPosterPage.page);
const [loadPage, setLoadPage] = useState([]); // 로드된 페이지를 관리하는 상태값 입니다.
const [cardList, setCardList] = useState({ loading: true, error: false });
useEffect(() => {
if (cardList.loading) {
fetch(`https://movieinfoserver.herokuapp.com/home`)
.then((response) => response.json())
.then((data) => {
setCardList({ loading: false, error: false, content: data });
// 처음엔 두페이지를 가져와야합니다.
loadPage[1] = 1;
loadPage[2] = 1;
setLoadPage([...loadPage]);
data.slice(0, 10).forEach(({ posterPath }) => {
const image = new Image(); // 이미지 객체를 선언합니다
image.src = `https://image.tmdb.org/t/p/w500${posterPath}`; // 이런식으로 src 프로퍼티에 주소를 넣으면 이미지를 로드해 캐싱합니다.
});
})
.catch(() => {
setCardList({ loading: false, error: true });
});
}
});
const changePage = (direction) => {
// 페이지별로 한번만 로드하도록 조건처리 합니다.
if (page + 2 !== 1 && page + 2 <= Math.ceil(cardList.content.length / 5)) {
loadPage[page + 2] = 1;
cardList.content
.slice((page + 1) * 5, (page + 2) * 5)
.forEach(({ posterPath }) => {
const image = new Image();
console.log(posterPath);
image.src = `https://image.tmdb.org/t/p/w500${posterPath}`;
});
setLoadPage([...loadPage]);
}
};
};
이번 포스트에서는 사용자 경험을 개선해줄수있는 image preload에서대해서 알아보았습니다. 일반적으로 lazy loading을 보완할때도 많이 사용되기 때문에, 적절하게 이미지를 미리 로드하여 사용자경험을 보장해보는것이 좋은선택이 될수 있을것 같습니다.