타 사이트들을 돌아다니며, 크롬 개발자 도구의 Ligthouse 를 사용하여 성능 측정을 해보았다. 꽤나 많은 사이트들이 성능 점수에서 낮은 것을 확인할 수 있었고, 이에 대한 이유를 파악해보고자 하였다.
해당 사이트(어디인지는 비밀 ㅎ...)는 성능적으로 매우 아쉬운 점수를 보였다. 체감 상, 이 점수대의 성능에서는 웹 인터렉션이 빠르다 라는 체감을 전혀 받지 못하는 것 같다. 다소 버벅거림이 있는 성능, 주된 원인으로서 이미지라 생각했다. 해당 사이트는 다량의 이미지를 렌더링하는데, lazy loading과 같이 성능적인 설계를 전혀 고려하지 않았었다. 조금 더 근거를 얻기 위해 Lighthouse의 의견을 들어보았다.
이미지 인코딩과 크기, 차세대 형식 및 오프스크린 이미지 지연하기를 제외한 부분들은 모두 어느정도 규모가 있는 사이트에서는 등장할 수 있는 부분이라 생각한다. 아무래도 이미지의 화질 또한 좋아지고 있고, 이러한 이미지들은 kb에서 mb단위가 되기도 하기 때문에, 더더욱 신경 쓸 수 밖에 없을 것이다.
이미지를 효율적으로 렌더링하는 것을 연습해보기 위해 간단한 토이프로젝트를 만들어보고자 한다.
무한 스크롤로 이미지들을 계속 렌더링하고, 해당 이미지들의 렌더링 방식 및 이미지 인코딩마다 다른 성능을 측정해보고자 한다.
해당 프로젝트에서는 다음과 같은 스택을 사용할 것이다.
React
Typescript 아직 매우 어색하다...
markup language
Infinite Scroll
Custom Hook
파일은 https://github.com/EJKim3191/image-loading 에 공유되어있다.
우선 각각의 페이지를 비교할 수 있도록 만들어보고자 한다.
메인 페이지는 각각의 기술이 도입된 페이지의 라우팅을 담고있기 위한 페이지이며
노멀 페이지는 비교하기 위한 무한 스크롤을 제외한 어떠한 기술도 들어가있지 않은 페이지이다.
추후에 기술이 도입될 때 마다 해당 명으로 페이지를 생성하고자 한다.
Custom Hook은 우선 useInfiniteScroll이라는 무한스크롤 커스텀 훅을 생성해주었다.
// src/hooks/useInfiniteScroll.ts
import { useEffect, useRef } from "react";
const useInfiniteScroll = <T extends HTMLDivElement>(
callback: () => void,
) => {
const displayElement = useRef<T>(null);
useEffect(() => {
if (displayElement && displayElement.current) {
const intersectionobserver = new IntersectionObserver(
(entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
observer.unobserve(entry.target);
callback();
}
});
}
);
intersectionobserver.observe(displayElement.current);
return () => intersectionobserver.disconnect();
}
}, [callback, displayElement]);
return displayElement;
};
export default useInfiniteScroll;
//https://dev.rase.blog/21-12-07-intersection-observer/ 를 참고하였습니다.
Intersection Observer API를 활용하였다. 해당 커스텀 훅은 ref를 이용하여 해당 HTML Div Element에 observer를 부착한다. 부착 된 observer는 엘리먼트가 뷰포트에 들어 올 시, 콜백을 invoke하게 된다.
// src/pages/Normal/index.tsx
import axios from "axios";
import React, { useState, useEffect } from "react";
import useInfiniteScroll from '../../hooks/useInfiniteScroll'
type RandomImageType = {
id: string;
author: string;
width: number;
height: number;
url: string;
download_url: string;
};
const Normal = () =>{
const [randomImageList, setRandomImageList] = useState<RandomImageType[]>([])
const ref = useInfiniteScroll<HTMLDivElement>(()=>{
getRandomImages();
});
const getRandomImages = async () =>{
try {
const { data } = await axios.get('https://picsum.photos/v2/list?page=1&limit=10');
setRandomImageList((prev) => prev.concat(data));
} catch (error) {
console.log(error);
}
}
return (
<div>
<h1>노멀 이미지 로딩</h1>
{randomImageList.map((randomImage) => {
return(
<>
<img
alt="random_image"
key={randomImage.id}
src={randomImage.download_url}
style={{width: '500px', height: '500px'}}
/>
<br/>
</>
)
})}
<div ref={ref}>
마지막 부분
</div>
</div>
)
}
export default Normal;
무한스크롤 영역을 위한 div 엘리먼트는 항상 맨 아래 노출 된다(현재로써는). 커스텀 훅은 getRandomImages를 콜백으로 불러온다. 해당 함수는 picsum을 이용하여 랜덤이미지를 불러오게 된다.
페이지는 이미지들을 출력하고, ref를 담은 div가 뷰포트에 들어올 때, 이미지들을 추가하며, 이후 update 된 randomImageList 에 렌더링 되어 아래 추가적인 컨텐츠(이미지)가 노출되게 된다.
'개발일지' 카테고리의 다른 글
[데스크탑 애플리케이션: 01] Firebase + Nextron (Electron + Next.js) (4) | 2022.07.23 |
---|---|
[이미지 렌더링: 02] 웹 성능과 이미지 최적화 (0) | 2022.07.06 |
[크롬 익스텐션: 06] StompJs & SockJs, 디자인 및 구현 - 2 (0) | 2022.06.23 |
[크롬 익스텐션: 05] StompJs & SockJs, 디자인 및 구현 - 1 (0) | 2022.06.23 |
[리펙토링: 나들서울] Debounce & Throttling (0) | 2022.06.14 |