만들고 있는 프로젝트를 반응형 웹 사이트로 만들고 싶었다. 이를 구현하기 위한 방법 중 하나로 반응형 이미지가 있다.
매번 img
태그로 이미지를 보여줬는데, 반응형 이미지란 무엇이며 어떻게 적용할 수 있는지 궁금해졌다.
반응형 이미지는 기기에 맞는 적절한 사이즈의 이미지를 제공한다. 왜그럴까?
mdn에서 반응형 이미지를 사용하지 않으면 발생할 수 있는 문제에 대해 크게 2가지로 나눠서 소개한다.
필자에게 일어났던 문제는 2번으로 실제 이미지의 크기가 2377 x 1507
이지만, 화면에 보여줄 크기는 최대 700px이었다. 즉, 이미지가 들어갈 공간보다 훨씬 큰 이미지를 다운로드 받고 있었다.
그럼 이미지 사이즈만 바꾸면 되겠다고 생각했으나 문뜩 'png 이외의 다른 이미지 형식이 있지 않나?'라는 의문이 생겼다.
기존에 알고 있던 png, jpg 이외의 webp, avif 등 다양한 이미지 형식이 존재한다. 그 중 png를 사용했으나 딱히 선택한 이유는 없었다.
형식에 따라 이미지 압축률이 다르기 때문에 동일한 크기의 이미지더라도 용량이 달라질 수 있다. 즉, 적절한 이미지 형식을 선택하면 이미지 용량을 효과적으로 줄일 수 있다.
손실 압축은 일부 데이터를 삭제하여 완전히 원본 파일로 복원할 수 없다. 퀄리티는 떨어지더라도 파일 크기를 줄일 수 있다.
반대로, 무손실 압축은 압축된 데이터를 원본 데이터로 되돌릴 수 있다.
google에서 만든 squoosh에서 이미지 형식과 사이즈를 변경해보자.
webp로 변환한 결과 73%만큼 파일 크기를 줄일 수 있었다.
일부 브라우저에서 webp를 지원하지 않는다. webp를 지원하지 않는 브라우저에서 사용할 대체 이미지로 jpg를 최적화한 MozJPEG를 사용한다. 기존 png 파일보다 59%만큼 크기가 감소됐다.
현재 프로젝트에서 이미지의 최대 가로 사이즈는 700px
이다. 그러나 레티나 디스플레이에서 이미지 해상도가 표준 디스플레이 기준으로 2배여야 한다.
700 x 400
1400 x 800
왜냐하면 레티나 디스플레이는 고해상도 디스플레이기 때문에 같은 공간에 더 많은 픽셀이 배치된다.
Resize에서 이미지 사이즈를 2377 x 1508
에서 1400 x 888
로 줄여 webp로 변환했을 때보다 9% 더 용량이 감소됐다.
모바일 환경에서 이미지의 최대 가로 길이가 약 500px
이기 때문에 1040 x 660
로 줄여 webp로 변환했을 때보다 16% 더 용량이 감소됐다.
<picture>
, <source>
picture와 source 태그를 이용해 기기의 가로 길이에 따라 다른 이미지를 보여주도록 했다.
600px
이하일 경우: 1040 x 660
601px
이상일 경우: 1400 x 888
source 태그의 속성을 자세히 살펴보자
<picture>
안에 위치할 경우 필수로 명시이미지_경로 원본_크기
로 작성srcSet="이미지1_경로 이미지1_사이즈, 이미지2_경로 이미지2_사이즈"
그리고 webp를 지원하지 않는 브라우저에는 jpg 형식의 이미지를 제공하기 위해 <img>
의 src에 jpg 이미지 경로를 지정한다.
이미지의 최대 가로 사이즈를 제한하기 위해 아래와 같이 스타일 설정했다.
const MainImg = styled.img`
${imageStyles}
width: 100%;
max-width: 700px;
`;
jpg, webp 형식의 이미지를 Import하기 위해 프로젝트 최상단에 declaration.d.ts
을 생성하여 아래와 같이 작성했다.
// declaration.d.ts
declare module '*.jpg';
declare module '*.webp';
그리고 tsconfig.json
의 include
에 declaration.d.ts
를 추가하면 typescript 컴파일러가 jpg, webp 파일을 컴파일 대상으로 삼는다.
반응형 이미지를 적용하기 전에 main.png를 보면 318kb에 로드 시간은 8ms가 걸린 것을 확인할 수 있다.
반응형 이미지 적용 후, 모바일 환경에서 가로 길이가 1040px인 이미지가 다운로드되어 이전보다 파일 크기와 시간이 줄어들었다.
반면, 데스크탑 환경에서 가로 길이가 1400px인 이미지가 로드됐다.