빌드노트
Grid의 1fr 두 칸인데 긴 문자열이 옆 칸을 밀어낸다
Grid의 1fr은 자동 최소 크기를 포함한다. 세 뷰포트에서 열 너비와 문자열의 scrollWidth를 비교해 minmax(0, 1fr), 아이템의 min-width: 0, 줄바꿈의 적용 범위를 구별한다.
grid-template-columns: 1fr 1fr로 만든 두 칸에 긴 식별자를 넣었더니 첫 칸이 대부분의 너비를 차지한다. 단독 1fr에는 자동 최소 크기가 포함된다. 두 열을 같은 폭으로 유지하려면 Grid의 열 정의에서 minmax(0, 1fr)을 검토하고, 줄어든 칸 안의 문자열을 어떻게 표시할지는 따로 정해야 한다.
MDN의 grid-template-columns 설명 (새 탭에서 열림)은 minmax() 밖의 1fr이 minmax(auto, 1fr)을 뜻한다고 설명한다. 여기서 트랙은 열이 차지하는 공간이다. auto인 최소 크기 때문에 콘텐츠가 열을 붙잡을 수 있으므로, 1fr 두 개를 항상 컨테이너 너비의 절반씩이라고 읽으면 계산이 어긋난다.
먼저 열이 커졌는지, 글자만 넘치는지 읽는다
문제가 있는 페이지의 개발자 도구 Console에서 아래 코드를 실행한다. .grid는 실제 Grid 컨테이너로, .token은 첫 번째 직접 자식 안에서 긴 문자열을 담고 너비가 열 안으로 제한된 블록 상자로 바꾼다. 기본 span처럼 display: inline인 요소라면 그 문자열을 담은 블록 상자를 선택한다. 아래 실험처럼 첫 직접 자식이 그 블록이면 const text = item;으로 바꾼다. 직접 자식 자체에 문자열이 있는 경우도 같다. 이 코드는 열 크기, 첫 아이템 크기, 측정 대상의 display와 콘텐츠 넘침을 함께 읽는다.
(() => {
const grid = document.querySelector('.grid');
const item = grid.firstElementChild;
const text = item.querySelector('.token');
return {
gridWidth: grid.getBoundingClientRect().width,
gridScroll: grid.scrollWidth,
columns: getComputedStyle(grid).gridTemplateColumns,
itemWidth: item.getBoundingClientRect().width,
itemMin: getComputedStyle(item).minWidth,
itemOverflow: getComputedStyle(item).overflowX,
textDisplay: getComputedStyle(text).display,
textClient: text.clientWidth,
textScroll: text.scrollWidth
};
})()
요소를 찾지 못했다는 오류가 나면 선택자와 부모·자식 관계부터 확인한다. columns에는 이와 같은 일반 Grid에서 사용된 열 크기가 px로 나온다. 이 반환 방식은 CSS Grid Level 2의 열 크기 직렬화 규칙 (새 탭에서 열림)에 명시돼 있다. itemMin: "auto"는 최소 너비가 몇 px인지 알려 주는 값이 아니므로, 실제 열과 아이템 너비를 함께 봐야 한다.
textClient와 textScroll이 0/0이라면 정상이라는 판정을 내리지 않는다. 먼저 textDisplay와 측정 대상을 확인한다. MDN의 clientWidth 설명 (새 탭에서 열림)은 인라인 요소의 clientWidth가 0이라고 명시한다. 별도 HTML에서 문자열 태그만 div와 기본 span으로 바꿔 비교했다. 720px Grid의 열을 352px씩으로 줄인 상태에서 div.token은 352/615였지만 span.token은 0/0이었다. 후자의 텍스트 오른쪽은 630.5px, 첫 아이템 오른쪽은 368px여서 실제 넘침이 남았다. const text = item;으로 부모 블록을 읽자 352/615로 검출됐다.
차이를 확인하려고 실제 사이트와 분리된 HTML을 만들었다. Grid에는 테두리와 패딩 없이 gap: 16px를 줬고, 너비는 뷰포트에서 좌우 16px씩을 뺀 값과 720px 중 작은 값으로 제한했다. 첫 칸 안의 블록에 0123456789abcdef를 네 번 이어 붙인 64자 문자열을 넣고, 둘째 칸에는 OK를 넣었다. 글꼴은 16px/20px "Courier New", monospace, 기본 줄바꿈은 normal, 아이템의 overflow는 visible이었다.
1000px 뷰포트에서 Grid 너비는 720px였다. 기본 1fr 1fr을 읽자 columns: "614.5px 89.5px", itemWidth: 614.5, itemMin: "auto", itemOverflow: "visible"이 나왔다. 문자열의 textClient와 textScroll은 모두 615였다. 문자열은 자기 상자 안에 들어갔지만, 그 상자가 첫 열을 크게 만든 상태였다.
이때 gridScroll도 720이었다. 가로 스크롤이 없다는 사실만으로 두 열이 반씩 나뉘었다고 판단할 수 없다. 첫 열 614.5px와 간격 16px를 빼면 둘째 열에 89.5px만 남는다. 같은 입력을 짧은 report.csv로 바꾼 비교에서는 기본 1fr만으로도 두 열이 352px씩 나뉘었다.
두 열을 같은 폭으로 유지해야 한다면
콘텐츠 길이와 관계없이 두 열의 너비를 같게 유지할 화면이라면, 컨테이너의 열 정의를 다음처럼 바꿔 비교한다. 기존 display: grid와 gap은 유지한다.
.grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
같은 HTML에 이 선언만 적용하자 columns: "352px 352px", itemWidth: 352가 됐다. 간격을 제외한 (720 - 16) / 2 = 352와 같다. 앞의 0은 열을 0px로 고정하는 값이 아니라 트랙의 최소 크기다. 콘텐츠가 자동 최소 크기를 통해 열 너비를 키우는 것을 풀고, 두 트랙에 같은 비율로 공간을 배분하게 한다.
좁은 뷰포트에서도 같은 비교를 실행했다. 아래 값은 모두 CSS px이며, 열 크기는 getComputedStyle()로 읽고 아이템의 getBoundingClientRect().width와 대조했다.
- 뷰포트 1000, Grid 720: 기본 열은
614.5 / 89.5, 변경 후352 / 352였다. - 뷰포트 640, Grid 608: 기본 열은
614.5 / 19.2031, 변경 후296 / 296이었다. - 뷰포트 390, Grid 358: 기본 열은
614.5 / 19.2031, 변경 후171 / 171이었다.
좁은 두 조건에서는 기본 열 너비와 간격의 합이 Grid 자체의 너비를 넘었다. 컨테이너가 늘어난 것이 아니라, 그 안의 트랙이 바깥으로 나온 것이다.
그런데 변경 뒤에도 itemMin은 "auto"였다. CSS Grid Level 2의 아이템 자동 최소 크기 절 (새 탭에서 열림)은 콘텐츠 기반 최소 크기를 적용할 조건에 트랙의 최소 크기가 auto인지도 포함한다. 이번처럼 아이템이 한 열만 차지하고 overflow: visible인 구조에서, 트랙 최소를 0으로 바꾸면 아이템의 자동 최소 크기도 같은 방식으로 콘텐츠를 붙잡지 않는다. 계산된 min-width 문자열이 그대로라고 해서 수정이 적용되지 않은 것은 아니다.
아이템 쪽을 수정하는 방법도 비교했다. 기본 1fr 1fr을 유지하고 긴 문자열을 가진 Grid의 직접 자식에만 min-width: 0을 주어도 세 뷰포트에서 각각 352px, 296px, 171px씩 나뉘었다. 특정 아이템의 축소만 허용하려면 이 방법을 검토한다. 두 열 전체의 크기 규칙이 요구 사항이라면 트랙 정의에 그 의도를 쓴다. 여러 행이 있는 Grid에서는 같은 열의 다른 아이템이 크기에 기여하는지도 다시 확인한다.
명시적인 최소 너비는 별도로 남는다. minmax(0, 1fr) 상태에서 첫 아이템에 min-width: 400px를 준 비교에서는 열이 여전히 352px 352px여도 첫 아이템은 400px였다. 트랙을 고쳤는데 상자가 계속 튀어나온다면 아이템의 최소 너비와 고정 너비를 살핀다. 그 400px가 꼭 필요한 UI라면 최소값을 일괄 제거하기보다 좁은 화면에서 두 열을 유지할지부터 정해야 한다.
열을 줄여도 문자열은 저절로 접히지 않는다
트랙만 바꾼 720px Grid에서 문자열의 textClient는 352, textScroll은 615였다. 첫 칸은 줄었지만 끊을 지점 없는 문자열은 여전히 옆 칸 쪽으로 나왔다. 이때도 Grid 전체의 gridScroll은 720이었다. 넘친 글자가 컨테이너 안의 둘째 칸 영역에 머물 수 있기 때문이다.
scrollWidth 설명 (새 탭에서 열림)에 따라 열 너비로 제한된 문자열의 블록 상자에서 scrollWidth와 clientWidth를 비교해야 이 넘침을 찾을 수 있다. 문자열이 인라인이면 앞서 설명한 부모 블록을 읽는다. 두 값은 정수이고, getBoundingClientRect()의 너비 (새 탭에서 열림)는 테두리와 패딩까지 포함한다. 앞의 614.5와 615 차이를 레이아웃 오류로 해석하지 않는다. 이번 수치는 테두리·패딩·변형이 없는 상자에서 읽었다.
식별자 전체를 여러 줄로 보여 줄 화면이라면 문자열 요소에 다음을 적용한다. .token은 긴 문자열을 담은 요소의 선택자다. 인라인 요소여서 부모 블록을 측정했더라도 줄바꿈 규칙은 문자열 요소에 적용하고, 넘침은 앞서 고른 블록에서 다시 읽는다.
.grid .token {
white-space: normal;
overflow-wrap: anywhere;
}
720px Grid에서 이 표시 규칙까지 적용하자 문자열 상자의 textClient와 textScroll이 모두 352가 됐고, 높이는 20px에서 40px로 늘었다. 390px 뷰포트에서는 두 값이 모두 171, 높이는 80px였다. 화면에서도 문자열이 첫 칸 안에서 여러 줄로 표시되고 둘째 칸의 OK가 제자리에 있는 것을 확인했다. 한 줄 표시가 필요한 화면이라면 이 줄바꿈을 그대로 적용할 수 없다. white-space 설명 (새 탭에서 열림)과 실제 적용값을 확인해 표시 요구를 먼저 정한다.
줄바꿈 규칙은 트랙 계산에도 영향을 준다. MDN의 overflow-wrap 설명 (새 탭에서 열림)에서 anywhere가 만드는 줄바꿈 기회는 콘텐츠의 최소 너비 계산에 반영된다. 같은 실험에서도 기본 1fr 1fr에 anywhere만 적용하면 두 열이 같아졌다. 반면 break-word만 적용한 비교는 기본 열 크기와 같았다. 따라서 줄바꿈만으로 요구를 충족한 화면까지 무조건 minmax(0, 1fr)으로 바꿀 필요는 없다. 열 크기를 콘텐츠와 독립적으로 정해야 하는지, 긴 문자열을 어떻게 읽게 할지가 적용 범위를 결정한다.
직접 확인한 범위는 Windows 빌드 26200, headless Chromium 147.0.7727.15, 장치 픽셀 비율 1에서 위 세 뷰포트와 합성 문자열을 비교한 결과다. 실제 사이트 CSS는 바꾸지 않았다. Firefox·Safari, 실제 휴대전화, 이미지·표·여러 열에 걸친 아이템과 중첩 Grid는 실행하지 않았다. 글꼴이 달라지면 문자열의 614.5px도 달라질 수 있으므로, 자기 화면에서는 같은 폭의 트랙이 필요한지와 그 안의 콘텐츠가 의도대로 보이는지를 함께 확인한다.