설정
이미지가 내려받아지는 동안 표시할 플레이스홀더를 생성합니다. 간결한 BlurHash 또는 ThumbHash 문자열, data URI 썸네일, 바로 삽입할 수 있는 블러 SVG, 주요 색상을 제공합니다. HTML, React, Next.js, Angular, Vue용 코드도 포함됩니다. 모든 처리는 브라우저에서 이루어지며 파일을 업로드하지 않습니다.

이미지를 끌어다 놓거나 클릭하여 선택하세요

PNG, JPEG, WebP, GIF, BMP, AVIF

작동 방식

플레이스홀더는 이미지가 내려받아지는 동안 그 자리를 대신 차지하는 요소입니다. 두 가지 역할을 합니다. 이미지가 차지할 정확한 공간을 미리 확보해 이미지가 나타날 때 주변 텍스트가 밀리지 않게 하고, 최종 내용과 비슷한 무언가를 보여 주어 회색 사각형을 보고 있을 때보다 대기 시간이 짧게 느껴지도록 합니다.

해법은 세 갈래입니다. 압축 해시(BlurHash와 ThumbHash)는 이미지를 매우 짧은 문자열로 요약하므로 레코드의 나머지 항목과 함께 데이터베이스 컬럼에 저장할 수 있습니다. 브라우저가 이를 디코딩해 블러를 그립니다. 데이터 URI(LQIP 썸네일과 SVG)는 이미 인코딩된 이미지를 HTML이나 CSS 안에 담고 있어 자바스크립트가 전혀 필요 없습니다. 그리고 단색은 가장 저렴한 플레이스홀더로 단 일곱 글자입니다.

무엇이 적합한지는 HTML이 어디서 만들어지는지에 달려 있습니다. 이미지가 데이터베이스에서 오고 수백 개씩 나열한다면 레코드당 가장 가벼운 것은 해시입니다. 정적 페이지를 생성하거나 이미 플레이스홀더를 지원하는 프레임워크를 쓴다면, 데이터 URI를 쓰면 디코딩 코드를 추가하지 않아도 됩니다. 여기 보이는 모든 값은 브라우저에서 계산되며, 이미지는 어떤 서버에도 업로드되지 않습니다.

예시

blurhash 4 × 3LGHC162?|cKNqSWDjtaghVfjfQfjBlurHash의 길이는 성분 개수에만 좌우되며 이미지의 크기나 내용과는 무관합니다. 4 × 3이면 항상 28자, 3 × 3이면 22자, 5 × 4면 44자입니다.
thumbhash3wcKNZpwh3eAiIh3iHiIiHCAB/eHThumbHash는 17~25바이트, 즉 Base64로 24~36자를 차지합니다. 정확한 크기는 이미지의 종횡비와 투명도 유무에 따라 달라지며, 이 형식이 알파 채널까지 인코딩하기 때문입니다.
lqip 24 px webpdata:image/webp;base64,UklGRi…너비 24 px의 WebP 썸네일은 대개 1 KB 미만입니다. HTML에 삽입되므로 추가 요청이 발생하지 않지만, 별도로 캐시되지 않는 문서 자체를 무겁게 만듭니다.

활용 사례

  • 이미지 테이블에 BlurHash나 ThumbHash를 저장해, 목록이나 무한 스크롤 피드가 첫 순간부터 무언가를 보여 주도록 한다.
  • 이미지가 정적 임포트가 아닐 때 next/image의 blurDataURL 속성이나 NgOptimizedImage의 placeholder를 채운다.
  • 플레이스홀더를 width, height 속성과 함께 사용해 상품 카드나 기사 카드의 레이아웃 이동(CLS)을 방지한다.
  • 디코딩 자바스크립트를 실행할 수 없는 정적 HTML, 이메일, 템플릿에 플레이스홀더를 삽입한다.
  • 웹과 모바일 앱에서 같은 BlurHash를 재사용한다. 모바일에는 Swift, Kotlin, Flutter용 디코더가 있다.
  • 바이트 예산이 썸네일조차 감당하지 못할 때 평균 색상을 컨테이너 배경으로 사용한다.

자주 묻는 질문

BlurHash와 ThumbHash 중 무엇을 쓸까요?

ThumbHash는 더 적은 바이트로 이미지를 더 충실하게 재현하고, 투명도를 보존하며, 종횡비까지 저장하므로 그리기 위해 원본 크기를 알 필요조차 없습니다. BlurHash는 더 오래되었고 정확도는 낮지만 훨씬 많은 플랫폼과 언어에 구현체가 있습니다. 처음부터 시작한다면 ThumbHash를, 이미 스택에 BlurHash 디코더가 있다면 굳이 옮길 이유는 없습니다.

이렇게 하면 LCP가 좋아지나요?

직접적으로는 아닙니다. 플레이스홀더는 체감 속도를 높이고 width, height와 함께 쓰면 레이아웃 이동을 없애지만, LCP 지표는 여전히 실제 이미지가 그려지는 시점에 측정됩니다. 더 나아가 주요 이미지가 LCP 요소라면 loading="lazy"를 붙이거나 플레이스홀더 뒤로 미루지 마세요. 브라우저가 더 일찍 요청하도록 fetchpriority="high"를 지정하는 편이 좋습니다. 플레이스홀더는 첫 화면 밖에 있는 이미지를 위한 것입니다.

BlurHash에는 성분을 몇 개나 쓰는 게 좋나요?

가로 이미지에는 4 × 3, 세로 이미지에는 3 × 4가 잘 맞습니다. 성분을 하나 늘릴 때마다 두 글자와 약간의 디테일이 더해지지만, 여섯이나 일곱을 넘어서면 육안으로는 차이를 알기 어렵습니다. 어차피 결과는 흐리게 표시되기 때문입니다.

해시와 데이터 URI 중 무엇을 쓸까요?

해시는 이미지당 수십 바이트를 차지해 한 페이지에 이미지가 많을 때 이상적이지만, 이를 디코딩할 클라이언트 코드가 필요합니다. 데이터 URI는 아무것도 필요 없지만 용량이 10~50배 더 크고, 별도로 캐시되지 않는 HTML 안에 실려 전달됩니다. 강조할 이미지가 몇 개뿐이라면 데이터 URI가, 목록이 길다면 해시가 낫습니다.

썸네일을 그대로 쓰지 않고 왜 SVG를 사용하나요?

블러를 적용하는 주체가 SVG를 그리는 브라우저이기 때문입니다. 덕분에 훨씬 작은 썸네일에서 출발해도 확대할 때 픽셀 블록이 보이지 않고, 어떤 크기에서도 똑같이 부드럽게 보입니다. 또한 SVG는 알파를 불투명으로 고정하므로 블러 때문에 가장자리가 투명해지지 않습니다.

플레이스홀더에 크기 제한이 있나요?

도구에 따라 다릅니다. Angular의 NgOptimizedImage는 플레이스홀더 데이터 URI가 4000자를 넘으면 콘솔에 경고를 출력합니다. 그 지점부터는 HTML이 무거워지는 비용이 이점을 넘어서기 때문입니다. 일반적인 기준으로도 좋습니다. 썸네일이 이 값을 넘으면 너비나 품질을 낮추세요.