이미지 최적화는 실제로 우리가 서버에서 데이터를 받아올 때 불필요한 요청을 여러번 하지 않아도 되며, 서버의 저장 공간이 적게 필요로 한다. 이는 비용 절감으로도 이어지며, 유저가 서비스에 접근했을 경우 더 빠르고 쾌적한 웹 환경을 제공해주기 때문에 최적화를 해야한다고 생각한다. 또 구글 SEO 순위를 결정할 때 모바일 응답성을 고려하여, 검색 순위에 노출됩니다.
그럼, 최적화를 하지 않으면 어떤 일이 일어날까?
웹 화면에 랜더링을 빠르게 하기 위해선, 이미지 리소스 최적화가 반드시 필요하다.
웹사이트에서 사용하는 이미지는 보통 width값 1,000px을 넘지 않는다. 블로그처럼 좌우에 메뉴바가 존재한다면 800px로도 충분하다.
width값을 줄여주는 것만으로도 10배, 20배, 30배는 더 이미지 용량을 줄여줄 수 있다
<img src="url" width="800" height="300" alt="아주 귀여운 이미지임" />
이미지의 종류에 맞게 포맷을 설정하면 이미지 최적화를 할 수 있다
ex) 아이폰이나 카메라로 찍은 사진 (JPG), 일러스트나 아이패드로 그린 그림(PNG)
JPEG, WdbP, AVIF
이 세가지를 같은 이미지로 압축했을 때 결과적으로 AVIF > WebP > PNG/JPEG 순이었다
브라우저가 AVIF를 지원하면 AVIF를 사용하고, 그렇지 않으면 WebP, 그렇지 않으면 PNG 혹은 JPEG를 사용하는 것이 좋다
HTML에서의 사용법
<picture>
<source srcset="supercar.avif" type="image/avif" />
<source srcset="supercar.webp" type="image/webp" />
<img src="supercar.jpeg" alt="Fast red car" />
</picture>
Reflow를 발생시키는 원인(치수가 없는 이미지) 해결법
치수가 없는 이미지들은 Reflow를 발생시켜 퍼포먼스를 저하시키기 때문에 이를 해결하기 위해 이미지 및 비디오 요소에 width와 height 속성을 항상 포함하거나 또는 CSS를 사용하여 필요한 공간 aspect-ratio를 잡는다. 이 방법을 사용하면 이미지가 로드되는 동안 브라우저가 문서의 공간을 올바르게 할당할 수 있다.
<img src="puppy.jpg" width="640" height="360" alt="Puppy with balloons" />
반응형 웹 디자인의 경우 width와 height를 생략하고 CSS를 사용하여 이미지 크리를 조정하기 시작하는데, 이 접근 방식의 단점은 다운로드가 시작되고 브라우저가 크기를 결정할 수 있는 경우에만 이미지를 위한 공간을 할당할 수 있다는 점이다.
이미지가 로드되어 각 이미지가 화면에 나타나면 reflow 되어 텍스트가 갑자기 화면 아래로 튀어나가는 등의 문제가 발생하였는데 이것을 방지하기 위해 aspect-ratio를 사용한다. 이 속성을 사용하면 복잡한 계산 없이 간단하게 속성으로 레이아웃 이동 방지를 할 수 있다.
일반적으로 3~5개의 서로 다른 크기의 이미지를 제공하고, 더 많은 이미지 크기를 제공하면 성능 향상은 되지만 그만큼 서버에서 더 많은 공간을 차지하고 HTML을 조금 더 작성해야 합니다.
# Before
<img src="flower-large.jpg" />
# After
<img
src="flower-large.jpg"
srcset="flower-small.jpg 480w, flower-large.jpg 1080w"
sizes="50vw"
/>
src 속성
브라우저가 srcset과 sizes 속성을 지원하지 않으면 fall back으로 src 속성이 동작한다
src 속성은 모든 디바이스 크기에서 동작할 수 있을만큼 충분히 커야한다
srcset 속성
srcset 속성은 이미지 파일명과 width 또는 density 설명응ㄹ 쉼표로 구분한다
480w는 480px임을 브라우저에게 말해준다
sharp npm package
const sharp = require("sharp");
const fs = require("fs");
const directory = "./images";
fs.readdirSync(directory).forEach((file) => {
sharp(`${directory}/${file}`)
.resize(200, 100) // width, height
.toFile(`${directory}/${file}-small.jpg`);
});
ImageMagick
원래 크기의 33%로 조절할 경우
convert -resize 33% flower.jpg flower-small.jpg
# macOS/Linux
convert flower.jpg -resize 300x200 flower-small.jpg
# Windows
magick convert flower.jpg -resize 300x200 flower-small.jpg
외에도 Thumbor나 CLoudinary 같은 이미지 서비스도 있다 Thumbor는 서버에 설치하여 설정되고, Cloudinary는 이러한 세부 정보를 처리하며 서버 설정이 필요하지 않다
Image CDN을 사용하는 이유는 이미지 최적화에 탁월하고, 이를 이용해 전환하면 파일 크기를 40%~80% 줄일 수 있고 이미지 변환, 최적화 및 전송을 전문으로 하고, 사이트에서 사용되는 이미지에 대한 접근이나 조작을 위한 API로 생각할 수 있다
웹페이지에 필수적으로 자주 사용되는 아이콘, 버튼 같은 이미지들을 쓸 때마다 여러 이미지들을 불러오는 것이 아니라, 한 이미지 파일로 통합한 후 배경 이미지를 만들어놓고 position 값으로 각각의 이미지를 불러오는 것이 sprite 기법이다
div#sprite {
background: url(/images/sprite.png) no-repeat;
} //한 이미지를 불러옴
// position으로 각 이미지를 불러옴
div#sprite > .first {
background-position: 0 0;
}
div#sprite > .second {
background-position: 0 -15px;
}
div#sprite > .third {
background-position: 0 -30px;
}
페이지를 로드할 때, 모든 이미지를 로드하는 것이 아니라 중요하지 않은 자원 또는 당장 필요 없는 자원의 경우 서버에 요청을 미루고 필요한 경우 해당 자원을 요청 받는 방법을 말한다
이를 사용함으로 인해 데이터의 낭비를 막을 수 있고, 브라우저는 서버로부터 자원을 요청받고 난 뒤에 화면을 랜더링하기 때문에 불필요한 자원의 다운로드를 막는 것만으로도 프로세스 시간이 단축될 수 있다 (브라우저의 랜더링 시간을 줄여준다)
사용 방법
img 태그 내의 속성을 통해 lazy-loading을 지원한다
<img loading="lazy">
<picture>
<source media="(min-width: 800px)" srcset="large.jpg 1x, larger.jpg 2x">
<img src="photo.jpg" loading="lazy">
</picture>
loading의 속성 값은 다음 3가지와 같다.
auto
: 디폴트 값으로, 속성값을 지정하지 않은 것과 동일하다.lazy
: 뷰포트 상에서 해당 이미지의 위치를 계산하여 이미지 자원을 요청함eager
: 어느 위치에 있든지 이미지 자원을 바로 요청받음이미지 영역의 크기를 지정하는 것이 권장되며, 영역 크기에 대한 정보가 없으면 영역을 0*0으로 인식하게 되고, 해당 이미지 영역으로 스크롤 할 경우 이미지가 로드 되면서 layout shift가 일어날 수 있기 때문에 되도록 해당 img 태그에 명시적으로 높이/너비 값을 지정해야 한다
페이지의 첫 시작부터 보이는 이미지에 대해서는 lazy-loading을 사용하지 말아야 하고, background-image에서는 적용 안 된다 <iframe loading="lazy">
은 아직 비표준이다.(링크)
Intersaction Observer API
MDN 문서에 따르면 타겟 요소와 상위 요소 또는 최상위 document의 viewport 사이의 intersection 내의 변화를 비동기적으로 관찰하는 방법 즉, 비동기적으로 사용자의 이벤트를 관찰하는 방법을 제공하는 웹 API로서 사용자가 웹 페이지를 스크롤 할 때 어떤 Element 이미지가 해당 뷰 포트 내에 교차되었는지 판단할 수 있는 방법을 제공한다
사용방법
const io = new IntersectionObserver(callback[, options])
null
, 브라우저의 viewport교차 영역의 기준이 될 root 엘리먼트. observe의 대상으로 등록할 엘리먼트는 반드시 root의 하위 엘리먼트여야 한다.'0px 0px 0px 0px'
0
IntersectionObserverEntry 객체의 배열을 동작시킬 수 있는 속성
IntersectionObserver의 callback 함수를 통해 생성된 객체의 배열의 속성을 확인할 수 있는 방법은 다음과 같다.
사용 예제
<div class="example">
<img src="https://picsum.photos/600/400/?random?0" alt="random image" class="image-default">
<img data-src="https://picsum.photos/600/400/?random?1" alt="random image" class="image">
<img data-src="https://picsum.photos/600/400/?random?2" alt="random image" class="image">
<img data-src="https://picsum.photos/600/400/?random?3" alt="random image" class="image">
</div>
// IntersectionObserver 를 등록한다.
// entries는 위에 data-src로 설정된 img 태그들의 배열
const io = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
// 관찰 대상이 viewport 안에 들어온 경우 image 로드
if (entry.isIntersecting) {
// data-src 정보를 타켓의 src 속성에 설정
entry.target.src = entry.target.dataset.src;
// 이미지를 불러왔다면 타켓 엘리먼트에 대한 관찰을 멈춘다.
observer.unobserve(entry.target);
}
})
}, options)
// 관찰할 대상을 선언하고, 해당 속성을 관찰시킨다.
const images = document.querySelectorAll('.image');
images.forEach((el) => {
io.observe(el);
})
참고 사이트 (여기) 👈🏻 위 글은 벨로그 참고