이미지에 여백을 하나도 안 줬는데 아래에 4px 틈이 생긴 이유
문제 발생
상품 카드의 썸네일 자리였습니다. 이미지가 뜨기 전에도 자리를 잡고 있으라고 감싸는 div에 연한 회색 배경을 깔아 뒀습니다.
<div class="thumb">
<img src="/images/p-1021.jpg" width="320" height="200" alt="">
</div>.thumb { background: #eee; }이미지가 다 뜬 뒤에도 이미지 아래로 회색 줄이 4px 남았습니다. 개발자 도구로 보면 img에는 margin도 padding도 없는데 .thumb의 높이만 이미지보다 4px 컸습니다. 둘 다에 margin: 0; padding: 0을 넣어 봐도 그대로였습니다. 더 이상한 건 다른 프로젝트에서 똑같은 마크업을 썼을 때는 멀쩡했다는 점입니다.
원인 분석
img는 기본이 인라인입니다. MDN의 img 문서에 따르면 img는 대체 요소(replaced element)이고 기본 display 값은 inline이지만, 크기는 이미지 고유 크기로 정해져서 마치 inline-block처럼 보입니다. 그러니 글자 한 줄을 배치하는 규칙이 이미지에도 그대로 적용됩니다. 블록처럼 보여서 블록이라고 생각한 게 출발점이었습니다.
이미지는 글자처럼 기준선 위에 올라갑니다. 같은 문서는 img에는 기준선(baseline)이 없기 때문에 기본값인 vertical-align: baseline에서는 이미지의 아래쪽이 글자의 기준선에 놓인다고 적어 둡니다. CSS 2.1 명세의 vertical-align 정의도 같은 말을 합니다. 박스에 기준선이 없으면 아래쪽 margin 가장자리를 부모의 기준선에 맞춘다고요.
기준선 아래에는 글자 꼬리가 들어갈 공간이 항상 있습니다. g, j, p, y 같은 글자는 기준선 아래로 내려갑니다. CSS 2.1 명세는 줄 상자(line box)마다 그 요소의 폰트와 line-height를 가진 폭 0의 인라인 박스가 맨 앞에 있는 것처럼 높이를 계산한다고 하고, 이 보이지 않는 박스를 strut라고 부릅니다. 글자가 한 자도 없어도 strut 때문에 기준선 아래 공간이 생기고, 이미지는 그 위에 얹혀 있으니 이미지 아래에 틈이 남는 겁니다.
그래서 틈의 크기는 이미지가 아니라 폰트가 정합니다. Chromium에서 재 보니 font-size: 16px일 때 4px, 24px이면 6px, 32px이면 8px이었고, line-height: 2를 주자 11px로 늘었습니다. 이미지 아래 틈이 글자 크기를 따라 변하면 이 문제가 맞습니다.
다른 프로젝트에서 멀쩡했던 건 Tailwind 덕분이었습니다. Tailwind의 기본 스타일인 Preflight는 img, svg, video, canvas 같은 대체 요소를 전부 display: block; vertical-align: middle로 바꿔 둡니다. 문서는 그 이유를 브라우저 기본값인 display: inline에서 자주 겪는 예상 밖의 정렬 문제를 피하기 위해서라고 설명합니다. 그 프로젝트는 문제가 없었던 게 아니라 이미 누가 고쳐 둔 상태였던 겁니다.
해결 방안
- 혼자 놓인 이미지는
display: block으로 둡니다. 썸네일, 배너, 카드 이미지처럼 글자 옆에 서지 않는 이미지에는 이게 제일 깔끔합니다. 블록이 되면 줄 상자 자체가 안 생기니 기준선도 strut도 없습니다. 재 보니 틈이 0이 됐습니다.
.thumb img { display: block; }프로젝트 전체에 적용하고 싶다면 Tailwind가 하는 대로 리셋 CSS에 넣어 둡니다. 새로 들어온 사람이 같은 일을 또 겪지 않습니다.
img, svg, video, canvas { display: block; vertical-align: middle; }- 글자 옆에 서는 아이콘은
vertical-align으로 맞춥니다. 버튼 안의 아이콘처럼 텍스트와 한 줄에 있어야 하는 이미지에display: block을 주면 줄이 끊깁니다. 12px 아이콘과 글자를 한 줄에 둔 채로 재 보니display: block을 주자 줄 높이가 21px에서 33px로 늘면서 아이콘이 윗줄로 올라갔습니다. 이럴 땐vertical-align: middle이 맞습니다. MDN 설명대로 요소의 가운데를 부모의 기준선에 x-height의 절반을 더한 위치에 맞춰서, 소문자 높이의 한가운데에 옵니다.
.btn img { vertical-align: middle; }-
부모의
line-height나font-size를 0으로 만드는 방법은 피합니다. 둘 다 strut를 없애서 틈이 사라지기는 합니다. 재 보니 틈이 0이 되는 것도 맞습니다. 하지만 나중에 그 안에 글자가 들어오는 순간 글자가 안 보이거나 줄이 뭉개지고, 왜 0인지 모르는 사람이 지우면 틈이 돌아옵니다. 이미지 자체를 고치는 1, 2번으로 충분합니다. -
부모가 flex나 grid면 이미 사라져 있습니다. flex 아이템과 grid 아이템은 블록 상자로 배치돼서 줄 상자가 생기지 않습니다. 재 보니
.thumb을display: flex나display: grid로 바꾸는 것만으로 틈이 0이었습니다. 레이아웃 때문에 어차피 flex를 쓰는 자리라면 따로 손댈 게 없고, 반대로 이것 때문에 flex를 쓰는 건 과합니다. -
확인은 두 사각형의 아래쪽 차이로 합니다. 개발자 도구 콘솔에서 부모와 이미지의
getBoundingClientRect().bottom을 빼 보면 바로 나옵니다. 이 값이 0이 아니고 글자 크기를 바꾸면 따라 변하면 이 글의 문제이고, 글자 크기와 무관하면 다른 원인입니다.
const wrap = document.querySelector('.thumb');
const img = wrap.querySelector('img');
wrap.getBoundingClientRect().bottom - img.getBoundingClientRect().bottom; // 4
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.