🟨 2-38. 이미지·폰트·애니메이션 최적화 — 시각적 성능 튜닝의 모든 것
좋아, 이번 편은 시각적인 성능 최적화의 결정판이다.
이전까지는 JS와 렌더링, 코드 구조를 다뤘다면
이번엔 이미지, 폰트, 애니메이션 — 즉 “화면이 실제로 보이는 속도와 부드러움”을 다룬다.
이 영역은 사용자가 “느끼는 체감 품질”을 결정짓는 부분이야.
페이지가 빨리 떠도, 폰트가 늦게 바뀌거나 이미지가 깜박이면 “느리다”는 인식을 준다.
1. 시각적 성능이 중요한 이유
브라우저는 렌더링할 때 다음 순서를 따른다.
HTML 파싱 → CSS 계산 → 레이아웃 → 페인트(Paint) → 컴포지팅
이 과정 중 하나라도 막히면
✅ 화면이 늦게 보이거나,
✅ 애니메이션이 끊기거나,
✅ 글자가 늦게 바뀌는 현상이 생긴다.
즉, 시각적 성능은 “눈에 보이는 퍼포먼스”이며
실제 로딩 속도보다 사용자가 느끼는 쾌적함에 더 큰 영향을 준다.
2. 이미지 최적화의 기본 원칙
2-1. 적절한 포맷 선택
포맷 용도 특징
| WebP | 대부분의 현대 브라우저 | 용량이 JPEG보다 25~35% 작음 |
| AVIF | 최신 브라우저 | WebP보다도 약 10~20% 추가 절감 |
| JPEG | 사진 중심 콘텐츠 | 호환성 높음 |
| PNG | 투명도, 아이콘 | 용량 큼. 필요할 때만 사용 |
| SVG | 벡터 아이콘, 로고 | 해상도 무제한, 텍스트처럼 렌더링 |
✅ 사진류 → WebP 또는 AVIF
✅ 로고, 아이콘 → SVG
2-2. Responsive Image (srcset, sizes)
반응형 페이지에서는 기기별로 다른 해상도의 이미지를 로드해야 한다.
<img
src="img-800.jpg"
srcset="
img-400.jpg 400w,
img-800.jpg 800w,
img-1200.jpg 1200w
"
sizes="(max-width: 600px) 400px, (max-width: 1024px) 800px, 1200px"
alt="제품 이미지"
/>
✅ 모바일에서는 작은 이미지만 요청
✅ 데스크톱에서는 고해상도 이미지 로드
✅ 불필요한 대용량 네트워크 낭비 방지
2-3. Lazy Loading
이미지를 화면에 보여야 할 때만 로딩한다.
<img src="thumb.webp" loading="lazy" alt="썸네일" />
✅ 스크롤 아래 이미지는 나중에 불러오기
✅ 초기 렌더링 시 LCP 향상
고급 방식으로는 IntersectionObserver를 활용한 커스텀 로더도 있다.
2-4. CDN + 자동 변환
Cloudflare Images, AWS CloudFront, Vercel Image Optimization 등을 사용하면,
브라우저가 지원하는 포맷에 따라 자동으로 변환·전송된다.
Next.js 예시 👇
import Image from 'next/image';
<Image
src="/photo.jpg"
alt="프로필"
width={600}
height={400}
priority
/>
✅ WebP, AVIF 자동 변환
✅ Lazy Load 기본 적용
✅ priority 속성으로 LCP 개선
3. 폰트 최적화
폰트는 사용자 눈에 “글자가 안 보이는 시간”을 직접 만든다.
3-1. 폰트 로딩 문제
문제 현상:
- FOIT (Flash of Invisible Text): 글자가 안 보였다가 나타남
- FOUT (Flash of Unstyled Text): 기본 폰트로 떴다가 교체됨
이건 브라우저가 웹폰트를 늦게 다운로드할 때 생긴다.
3-2. 해결 방법
✅ font-display
@font-face {
font-family: 'Pretendard';
src: url('/fonts/pretendard.woff2') format('woff2');
font-display: swap;
}
- swap: 기본 폰트 먼저 표시, 이후 교체 (권장)
- optional: 저속 네트워크에선 기본 폰트만 유지
💡 “보여주고 바꾸는 게 안 보여주는 것보다 낫다.”
✅ Preload 폰트
폰트를 미리 로드해 초기 표시를 빠르게 한다.
<link rel="preload" as="font" type="font/woff2" crossorigin href="/fonts/pretendard.woff2" />
✅ 핵심 폰트를 미리 받아두면 FOUT/FOIT 문제 대폭 완화
✅ 시스템 폰트 스택
웹폰트 대신 OS 기본 폰트를 활용하는 것도 실용적인 대안이다.
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
✅ 각 플랫폼의 기본 폰트 사용
✅ 별도의 폰트 다운로드 없음
✅ 대형 서비스(예: GitHub, Medium)도 적극 활용 중
4. 애니메이션 최적화
애니메이션은 UX에 생동감을 주지만, 잘못 구현하면 GPU를 혹사시켜 프레임 드랍을 유발한다.
4-1. Transform과 Opacity만 사용하라
position, width, height, top, left 등을 바꾸면 “Reflow”가 발생한다.
브라우저가 다시 레이아웃을 계산해야 하기 때문이다.
✅ 올바른 예시
.box {
transition: transform 0.4s ease, opacity 0.3s ease;
}
.box:hover {
transform: scale(1.05);
opacity: 0.9;
}
💡 transform/opacity는 GPU 가속이 적용되어 프레임 드랍이 거의 없다.
4-2. will-change로 미리 최적화 힌트
.card {
will-change: transform, opacity;
}
→ 브라우저에게 “이 속성이 곧 바뀔 거야” 라는 힌트를 줘서
렌더링 파이프라인을 미리 준비시킬 수 있다.
⚠️ 단, 너무 많은 요소에 적용하면 오히려 메모리 낭비. (적절히 사용)
4-3. requestAnimationFrame으로 JS 애니메이션 제어
let pos = 0;
function move() {
pos += 2;
box.style.transform = `translateX(${pos}px)`;
if (pos < 300) requestAnimationFrame(move);
}
move();
✅ setInterval보다 부드럽고 정확한 프레임 제어 가능
✅ 브라우저의 리페인트 주기(60fps)에 맞춰 실행
5. Core Web Vitals와 시각적 성능
이전 편에서 다룬 Core Web Vitals(LCP, CLS, FID)는
이미지, 폰트, 애니메이션 품질에 직접적인 영향을 받는다.
지표 관련 요소 개선 방법
| LCP | Hero 이미지, 폰트 로딩 | Preload, Lazy Load |
| CLS | 레이아웃 이동 | width/height 지정, 폰트 교체 안정화 |
| FID | 애니메이션, 이벤트 응답 | GPU 가속, Transition 최적화 |
6. 시각적 안정성 유지 — CLS (Cumulative Layout Shift)
이미지, 광고, 폰트 교체로 인해 화면이 밀리는 현상은 CLS 점수를 망친다.
✅ 반드시 width/height 속성 지정
<img src="banner.webp" width="800" height="400" alt="배너" />
✅ 동적 콘텐츠 공간 예약
.banner-placeholder {
min-height: 300px;
}
✅ 웹폰트 교체 시 font-display: swap
→ 폰트 교체 중 레이아웃 밀림 방지
7. 실전 튜닝 예시 (Before vs After)
항목 개선 전 개선 후
| Hero 이미지 | JPEG 1.8MB, 즉시 로딩 | WebP 480KB + Lazy Load |
| 폰트 | 3종 웹폰트, FOIT 발생 | Pretendard 1종 + swap |
| 애니메이션 | top/left 이동 | transform + GPU 가속 |
| CLS | 0.25 | 0.03 |
| LCP | 3.4초 | 1.7초 |
➡ 단 3가지(이미지, 폰트, 애니메이션)만 조정해도 성능 지표 2배 향상
8. 마무리
시각적 성능 최적화는 “프레임을 줄이는 기술”이 아니라,
사용자에게 안정적이고 부드러운 화면을 제공하는 기술이다.
느려도 끊기지 않고,
늦게 보여도 안정적이며,
움직여도 흔들리지 않게.
이 감각이 바로 프론트엔드 UX 품질의 핵심이다.