이미지 리사이징을 통해 웹 성능 개선하기
By 김요한
김요한(Yohan Kim) 님은 Riiid의 Frontend Engineer로 문제풀이 웹뷰 개발과 백오피스 개발을 맡고 있습니다.
1. 이미지 리사이징이 필요한 이유
웹 페이지의 성능은 사용자의 경험, 제품의 매출, SEO 렌더링 등 다양한 영역에 지대한 영향을 끼칩니다. 때문에 주기적으로 모니터링하고 개선하는 것이 프론트엔드 개발자의 필수 과제인데요.
https://web.dev/lighthouse-performance/ 해당 글을 통해 모니터링 해야할 객관적인 성능 지표과 개선을 위해 시도할 수 있는 대표적인 방법들이 잘 설명되어 있으니 한 번 읽어보시는 것을 추천드립니다.
그중에서 이미지에 대한 개선은 웹 사이트 총 리소스를 크게 줄일 수 있고 TTI(Time to interaction)를 개선할 수 있는 쉽고 임팩트 있는 개선점 중 하나입니다. 이미지를 개선하기 위한 방법은 크게 압축과 리사이징 두 가지로 나눌 수 있는데요.
이번 포스팅에서는 이미지 리사이징(https://web.dev/uses-responsive-images/)에 대한 방법을 설명하고자 합니다.
2. 이미지 리사이징 실제로 해보기
첫째, 디바이스의 가로폭에 따라 이미지를 잘라봅시다.
가로가 480px인 모바일 기기에서 어떠한 경우라도 사용자에게 4k 이미지를 보여줄 필요는 없을 겁니다. 그러므로 첫 번째 요구 사항은 “모든 이미지는 디바이스의 가로폭보다 클 필요가 없다”입니다.
img
태그는 srcSet
이라는 속성을 지원합니다. 해당 속성으로 스크린 넓이마다 가져오는 이미지의 src를 다르게 줄 수 있습니다. 문법은 '${src} ${screenSize}’
를 ',’
를 통해 연속적으로 써주면 됩니다.
아래 코드와 함께 설명하겠습니다.
const getSrcSet = (src: string) => {
return `${src}/375xauto 375w,
${src}/750xauto 750w,
${src}/960xauto 960w,
${src}/1440xauto 1440w,
${src}/2048xauto 2048w,
${src}/2880xauto 2880w,
${src}/autoxauto 5120w`;
};
}
해당 코드에서 w는 화면의 가로 넓이와 밀도를 곱한 값입니다. 즉 가로 넓이가 375이면서 픽셀 밀도가 2x라면 750w입니다. 그래서 3번째 라인에 쓰여있는 '${src}/750xauto’
에 해당하는 이미지를 가져오게 됩니다.
스크린 사이즈를 많이 나눌수록 좀 더 핏한 이미지를 얻을 수 있습니다. 하지만 반대로 관리해야 할 이미지가 많아지므로 서버의 부하가 커질 수 있겠죠. 적절한 타협점을 찾으시면 됩니다.
주의할 점은 srcSet
을 src
보다 앞에 써주셔야 합니다. srcSet
이 뒤에 있다면 사파리에서는 src
를 우선시하게 되고 srcSet
은 무시됩니다.
둘째, 디바이스의 가로폭에 따라 각각 다른 width를 줄 수 있습니다.
반응형 그리드에서 모바일일 때 1컬럼이고 데스크탑일 때 4컬럼이면 오히려 모바일에서 가져와야 하는 이미지의 사이즈가 PC일 때보다 클 수도 있겠죠. 이러한 경우를 고려하여 반응형으로 이미지 크기를 넣어줄 수 있도록 개선할 수 있습니다.
airbnb의 한 화면을 예시로 들겠습니다.
small 일 때는 이미지는 293이라는 고정 넓이를 가집니다. medium인 경우 컬럼이 3개여서 화면의 1/3이라는 유동적인 넓이를 가집니다. large인 경우 컬럼이 4개이고 컨테이너의 최대 넓이가 1600이었습니다. 즉 400이라는 고정 넓이를 가집니다.
코드로 표현하면 아래와 같습니다.
<Image src="..." widthForSmall="293" widthForMedium="33vw" widthForLage="400" />
img
태그에서는 sizes
속성을 통해 미디어 쿼리로 넓이를 각각 다르게 줄 수 있습니다. ','
를 통해 구분하고 제일 마지막에 들어간 값은 앞서 들어간 미디어 쿼리의 조건들이 전부 해당이 안될 때 들어갑니다.
export const breakPoints = {
sm: '480',
md: '720',
lg: '1080',
};/*
example.
getSizes({ sm: '293', md: '33vw', lg: '400' })
-> "(min-width: 1080px) 400, (min-width: 720px) 33vw, 293"
*/export const getSizes = (sizeByBreakPoint: {[key in 'sm' | 'md' | 'lg']?: string}) => {
const lgSize = sizeByBreakPoint.lg ? `(min-width: ${breakPoints.lg}px) ${sizeByBreakPoint.lg}` : null;
const mdSize = sizeByBreakPoint.md ? `(min-width: ${breakPoints.md}px) ${sizeByBreakPoint.md}` : null;
const smSize = `${sizeByBreakPoint.sm ?? '100vw'}`;
return [lgSize, mdSize, smSize].filter(size => size != null).join(', ');
};
이제 특정 분기점마다 이미지의 넓이를 다르게 줄 수 있게 되었습니다.
지금까지 사용한 코드들을 정리하면 다음과 같습니다.
import React, {forwardRef} from 'react';export const breakPoints = {
sm: '480',
md: '720',
lg: '1080',
};export const getSizes = (sizeByBreakPoint: {[key in 'sm' | 'md' | 'lg']?: string}) => {
const lgSize = sizeByBreakPoint.lg ? `(min-width: ${breakPoints.lg}px) ${sizeByBreakPoint.lg}` : null;
const mdSize = sizeByBreakPoint.md ? `(min-width: ${breakPoints.md}px) ${sizeByBreakPoint.md}` : null;
const smSize = `${sizeByBreakPoint.sm ?? '100vw'}`;
return [lgSize, mdSize, smSize].filter(size => size != null).join(', ');
};export const getSrcSet = (src: string) => {
return `${src}/375xauto 375w,
${src}/750xauto 750w,
${src}/960xauto 960w,
${src}/1440xauto 1440w,
${src}/autoxauto`;
}type ImgProps = React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>;export interface ImageProps extends ImgProps {
widthForSmall?: string;
widthForMedium?: string;
widthForLarge?: string;
}const Image = React.memo(
forwardRef<HTMLImageElement, ImageProps>(
({src, widthForSmall, widthForMedium, widthForLarge, ...extraProps}, ref) => {
const sizes = getSizes({sm: widthForSmall, md: widthForMedium, lg: widthForLarge}); const srcSet = src ? getSrcSet(src) : src; return <img sizes={sizes} srcSet={srcSet} src={src} ref={ref} {...extraProps} />;
}
)
);Image.displayName = 'Image';export default Image;
3. 결론, 쾌적한 웹 환경 구축에 가장 효율적 엔지니어링, ‘이미지 리사이징’
1600 x 1200인 원본 png 이미지가 있습니다. 이 이미지를 webp로 압축하는 것만으로 1.4mb를 37.7kb로 줄일 수 있습니다. 여기다가 360 정도로 리사이징을 더하면 37.7kb를 6.4kb로 줄일 수 있게 되죠. 한 개의 이미지만 보더라도 1400kb → 6.4kb 굉장한 수치 변화입니다.
뤼이드에서는 해당 작업을 통해 웹사이트의 총 리소스 용량을 약 70% 가까이 줄일 수 있었습니다. 웹사이트의 성능 개선 리스트 중 이미지 개선은 비교적 진입장벽이 낮고 큰 효과를 얻을 수 있습니다.
특히, 이미지를 많이 쓰는 커머스 사이트라면 이러한 이미지 개선이 필수적입니다. 이미지를 핏하게 관리해서 개당 20kb 아래 수준으로 유지하게 된다면 레이지 로딩을 하지 않아도 유저에게 빠른 경험과 좋은 UX를 제공할 수 있습니다. 웹 사이트가 너무 느려서 고민이시라면, 무엇을 개선해야 할지 모르시겠다면 우선 이 포스팅을 따라 해보시길 적극 권장 드립니다.
이번 포스트에서는 클라에서 이미지를 어떻게 리사이징 할 수 있는지에 대한 방법을 알아봤습니다. 다음 포스트에서는 이미지 매니징 서비스를 구축하는 방법이나 아니면 다른 성능 개선 방법에 대한 글로 돌아오겠습니다.