요약: 메인 카드 썸네일에
transform: scale(1.04)호버를 걸자, 1~2px 펜 선화가 호버할 때마다 지글거리고 뭉개졌다. 이미 축소된 텍스처를 compositor가 한 번 더 bilinear로 늘리기 때문이었다. 호버 효과를 이미지가 아닌 박스의box-shadow와filter: brightness()로 바꿔 리샘플링을 없앴다.
증상
그림일기 서비스(picto.aivance.kr) 메인에는 정사각 카드 썸네일이 4열로 깔린다. 콘텐츠가 대부분 1~2px 굵기의 펜 선화다. 평소에도 선이 다소 거칠었는데, 카드에 마우스를 올리면 선이 더 지글거리고 뭉개졌다. 호버를 풀면 다시 돌아왔다.
원인
- 썸네일 박스는 255×255 CSS px이다. 레티나(DPR 2)에서는 510px이다.
- 원본 JPG는 약 1300×915인데, 서버에서 줄이지 않고
object-fit: cover로 브라우저가 그대로 축소해 보여준다. - 호버 규칙이 이미지에 걸려 있었다.
.gg-blogcard-thumb img {
width: 100%; height: 100%; object-fit: cover; display: block;
transition: transform .25s var(--ease-out);
}
.gg-blogcard:hover .gg-blogcard-thumb img { transform: scale(1.04); }
transform이 애니메이션되면 Chrome은 이미지를 별도 GPU compositor 레이어로 올린다. 이때 이미 510px로 축소·래스터화된 텍스처를 bilinear로 다시 확대한다. 원본에서 한 번, 축소된 텍스처에서 또 한 번, 보간이 두 번 겹친다.

1~2px 선은 보간 한 번에도 가장자리가 흐려지기 쉬운데, 두 번째 보간에서 선 끊김과 shimmer가 가장 먼저 드러난다. 그래서 면이 큰 사진보다 선화에서 훨씬 눈에 띄었다.
해결
호버 효과를 이미지에서 떼어 박스로 옮겼다. 이미지에는 transform을 주지 않는다.
.gg-blogcard-thumb {
display: flex; align-items: center; justify-content: center;
aspect-ratio: 1/1; overflow: hidden;
transition: box-shadow .25s var(--ease-out);
}
.gg-blogcard-thumb img {
width: 100%; height: 100%; object-fit: cover; display: block;
transition: filter .25s var(--ease-out);
}
.gg-blogcard:hover .gg-blogcard-thumb { box-shadow: var(--shadow-md); }
.gg-blogcard:hover .gg-blogcard-thumb img { filter: brightness(1.03); }
.gg-blogcard-thumb:focus-visible {
outline: 2px solid var(--brand-text); outline-offset: 3px;
box-shadow: var(--shadow-md);
}
@media (prefers-reduced-motion: reduce) {
.gg-blogcard-thumb, .gg-blogcard-thumb img { transition: none; }
}
- 지시서는
rgba(0,0,0,.12)그림자를 제안했지만 기존 디자인 토큰--shadow-md를 썼다. - 박스에도
translate로 위아래 이동 효과를 주지 않았다. 박스가 움직이면 내부 이미지가 다시 compositor 레이어로 올라갈 수 있다. will-change: transform도 넣지 않았다. 평소에도 이미지가 흐려질 수 있다.scale(을 프로젝트 전체에서 검색했다. 이미지에 걸린 다른 호버는 없었다..gg-btn:active(버튼 눌림)와.gg-cover-fill(블러 배경)은 대상이 아니어서 두었다.
전후 비교
아래는 실제 사이트 캡처가 아니라 재현용이다. 사이트와 같은 조건(1300×915 원본, 255px 박스, DPR 2)으로 합성 펜 선화를 만들어 Chrome headless로 찍었다. 로컬 DB의 카드 썸네일이 비어 있어 실제 그림으로는 비교하지 못했다. 정지 캡처에서는 레이어 승격이 일어나지 않아, will-change: transform으로 승격을 강제해 호버 애니메이션 중 상태를 흉내냈다.

가운데(scale(1.04) 레이어)만 선이 두껍게 번지고 농도가 옅어진다. 호버 전과 수정 후는 같은 선명도를 보인다.
검증과 남은 것
- 로컬 서버에서 호버 후 computed style을 확인했다.
transform: none, 이미지 폭은 박스 폭과 같음(크기 불변),box-shadow와brightness(1.03)적용. - 실제 선화로 하는 레티나 Safari 육안 비교와 DevTools의 Layer borders 확인은 하지 못했다.
- 썸네일 링크가
tabindex="-1"이라 키보드로 포커스되지 않는다. 포커스 링 규칙은 넣었지만 지금은 동작하지 않는다.
배운 점
- 호버 확대 효과는 이미지가 아니라 박스 장식(그림자, 테두리)으로 낸다. 이미지에
transform을 주는 순간 보간이 한 번 더 늘어난다. - 선화처럼 얇은 선이 콘텐츠인 서비스는 리샘플링 횟수에 특히 민감하다.
- 평소 화질의 뿌리는 원본을 그대로 줄여 쓰는 데 있다. 업로드 시 서버에서 640×640 썸네일을 Imagick(Lanczos)으로 만들어 쓰는 후속 작업이 필요하다. 이번 수정은 호버 때 더 나빠지는 부분만 없앤 것이다.

댓글 1개
결론적으로는 효과를 다 없애고 파일 업로드시 썸네일 만들어서 사용하는 것으로 처리했다.
기존 파일들 마이그레이션 포함. php spark 이용해서 한방에 썸네일화 완료