Devin.KR

박스 크기와 CSS 적용

100분 안팎

학습 목표

박스 모델과 선택자 우선순위로 크기 문제를 설명합니다.

개념

왜 100%인 카드가 밖으로 나가나요

지역 행사 소개와 검색 폼은 앞 모듈에서 완성했습니다. 이번에는 목록에 테두리와 여백을 더합니다. 디자인 시안의 카드 너비만 복사하면 작은 화면에서 가로 스크롤이 생길 수 있습니다. 너비가 무엇을 포함하는지 설명할 수 있어야 원인을 찾습니다. 눈으로 조금씩 줄이는 대신 부모 공간, 카드의 내용 영역, 안쪽 여백, 테두리, 바깥 여백을 나눠 확인합니다.

실습의 카드에는 긴 제목, 긴 주소, 큰 원본 이미지가 들어 있습니다. 첫 레슨은 상자 자체의 크기만 고칩니다. 긴 문자열과 이미지 때문에 생기는 넘침은 다음 레슨에서 따로 다룹니다. 여기서 한 속성을 바꾸고 모든 화면 문제가 해결됐다고 판단하면 원인이 다른 실패를 놓칩니다. 수정 후 어떤 상자의 계산이 달라졌는지 설명하는 것이 이번 목표입니다.

무엇이 카드의 너비에 포함되나요

content-box에서 지정한 width는 내용 영역의 너비입니다. width가 288px이고 좌우 padding이 각각 16px, 좌우 border가 각각 1px라면 테두리까지의 너비는 322px입니다. 부모가 288px여도 width:100%에 같은 여백을 더하면 부모보다 커집니다. margin은 이 계산 밖에서 주변 요소와의 거리를 만듭니다. 테두리까지의 크기와 주변 공간을 구분합니다.

border-box에서는 지정한 width에 padding과 border가 포함됩니다. 같은 조건에서 width가 288px이면 내용 영역은 254px입니다. margin까지 포함되는 것은 아닙니다. 아주 작은 상자에 여백과 테두리가 과도하면 내용 영역을 음수로 만들 수는 없으므로 이 속성만으로 어떤 크기도 보장한다고 생각하지 않습니다. 텍스트가 잘 들어가는지도 확인합니다.

width:100%는 뷰포트 전체가 아니라 해당 요소의 포함 블록을 기준으로 합니다. main에 좌우 여백이 있고 그 안에 카드가 있으면 카드의 기준 공간은 main의 내용 영역입니다. max-width는 큰 창에서 읽는 줄이 지나치게 길어지는 것을 제한합니다. 가변 width와 상한을 함께 사용하면 작은 창에 맞추면서 큰 창에서는 가운데 정렬할 수 있습니다.

어떻게 적용 규칙을 찾나요

CSS는 선택자로 요소를 고르고 선언으로 표현을 정합니다. .card는 card 클래스를 가진 요소를, main은 main 태그를 선택합니다. 쉼표로 구분한 선택자는 같은 선언을 여러 대상에 적용합니다. 실습에서는 모든 일반 요소와 가상 요소에 border-box를 적용합니다. box-sizing은 기본적으로 상속되지 않으므로 부모에만 선언한 뒤 자식도 같을 것이라고 기대하지 않습니다.

같은 출처와 중요도 등의 조건에서 선택자 우선순위가 높은 선언이 적용됩니다. id 선택자는 클래스보다, 클래스는 태그보다 우선합니다. 우선순위가 같다면 뒤에 쓴 선언이 이깁니다. 스타일이 안 바뀌면 개발자 도구에서 취소선이 있는 선언과 실제 적용 선언을 비교합니다. 모든 상황을 파일의 마지막 줄만으로 설명하지 말고 인라인 선언, 중요 표시, 적용된 규칙도 확인합니다.

실습 starter의 마지막 TODO 블록에 고정 너비가 있습니다. 앞부분에 정상 선언을 추가하고 TODO를 남겨 두면 같은 클래스의 뒤 선언이 다시 고정 너비를 적용할 수 있습니다. 해당 실패 블록을 교체합니다. !important를 추가해 덮는 방식은 다음 수정 때 규칙 관계를 복잡하게 만들 수 있습니다. 문제의 출처를 찾고 필요 없는 선언을 제거하는 연습을 합니다.

직접 계산과 화면을 연결합니다

작은 화면 320px에서 main의 좌우 padding을 16px씩으로 가정하면 내용 공간은 288px입니다. 이 숫자는 기본 글꼴 크기 16px에서 1rem을 사용한 계산 예입니다. 사용자의 글꼴 설정이 다르면 실제 값도 달라지므로 개발자 도구에서 확인합니다. rem은 루트 글꼴 크기를 기준으로 하는 단위이며 px 숫자와 항상 같은 것으로 바꿔 적지 않습니다.

개발자 도구 요소 탭에서 카드를 선택하고 박스 모델 그림을 확인합니다. computed에서 box-sizing, width, padding, border를 읽습니다. getBoundingClientRect의 width는 테두리까지의 사각형 폭을 볼 때 도움이 됩니다. 변형이 있는 경우 화면 좌표 측정 의미가 달라지므로 이번 실습처럼 transform 없는 상자에서 비교합니다. 계산한 내용 폭과 테두리 폭을 뒤섞지 않습니다.

요소 탭에서 임시로 너비를 바꾸는 것은 원인 확인용입니다. 편집기의 assets/site.css에 저장하고 새로고침해야 압축 파일을 받은 동료에게 같은 결과를 전달할 수 있습니다. 스타일 요청 경로가 틀리면 계산 자체가 적용되지 않습니다. 카드가 그대로라면 먼저 link의 상대 경로와 현재 연 파일을 확인하고, 그다음 적용된 선언을 살핍니다.

실패 메시지와 완료 기준

FAIL 카드 padding을 포함하는 가변 너비는 제공 계약의 .card 너비나 여백이 맞지 않는다는 뜻입니다. Python traceback이나 npm ENOENT는 레이아웃 실패와 다른 환경 문제입니다. package.json 폴더에서 실행했는지 먼저 확인합니다. 검사를 삭제하거나 기대값을 바꾸면 카드가 고쳐지지 않습니다. 첫 FAIL의 대상 선택자를 찾아 정상 파일과 비교한 뒤 다시 검사합니다.

자동 검사는 제공된 선택자의 CSS 선언과 HTML 계약을 읽습니다. 실제 CSS 엔진의 모든 우선순위와 렌더링을 계산하지 않습니다. PASS는 이 레슨에서 요구한 선언을 작성했다는 증거입니다. 320px에서 실제 카드 폭을 확인하고 남은 넘침 원인이 글인지 이미지인지 분리해 LAYOUT.md에 적습니다. 아직 하지 않은 화면 관찰은 미확인으로 남깁니다.

이 레슨에서는 예쁜 색보다 재현 가능한 크기를 먼저 만듭니다. 기존 검색어 이름 q, 지역 이름 region, GET 목적지 results.html을 유지합니다. 스타일을 바꾸는 과정에서 HTML을 새로 만들어 폼 계약을 잃으면 앞 단계의 기능이 후퇴합니다. document-check.py가 이전 모듈의 검사 18개를 함께 실행하는 이유입니다. 파일 전체를 다음 실습으로 넘길 수 있는 상태를 유지합니다.

더 읽기와 설명 연습

테두리 포함 여부는 MDN box-sizing 설명으로 확인할 수 있습니다. 아래 서재 연결은 긴 콘텐츠와 Grid 최소 크기를 확장해 다룹니다. 이 레슨을 마치면 부모 내용 공간이 288px인 상황에서 322px 카드가 만들어진 이유와 border-box 적용 후 내용 공간이 달라지는 이유를 동료에게 설명합니다.

따라하기

starter와 실패 조건 확인

이 레슨 starter 압축을 새 폴더에 풀고 package.json이 있는 폴더에서 실행합니다. Node/npm과 Python 3가 필요하며 외부 의존 패키지는 없습니다.

npm ci
npm test

starter는 일부 CSS 계약이 FAIL인 상태입니다. check.py와 document-check.py는 고치지 않습니다. ENOENT는 작업 폴더, python3 실행 실패는 Python 설치부터 확인합니다.

상자 포함 관계를 숫자로 확인

실습 폴더에서 Python 3 대화형 실행기를 열고 아래 코드를 입력합니다. 부모 내용 폭 288px, 좌우 padding 16px, border 1px이라는 조건의 계산입니다. 브라우저 측정값을 대신하지 않습니다.

parent = 288
padding = 16
border = 1
print("content-box 테두리 폭:", parent + 2 * padding + 2 * border)
print("border-box 내용 폭:", parent - 2 * padding - 2 * border)

실행 결과

content-box 테두리 폭: 322
border-box 내용 폭: 254

해당 단계의 CSS 규칙 교체

assets/site.css 마지막 TODO 실패 블록을 다음 코드로 교체합니다. 이전 단계와 기본 글꼴·포커스 선언은 유지합니다. 동일 선택자의 실패 선언을 뒤에 남기지 않습니다.

*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; }
header, main, footer { width: 100%; max-width: 64rem; margin: 0 auto; padding: 1rem; }
.card { width: 100%; padding: 1rem; border: 1px solid #64748b; border-radius: .5rem; }

index.html의 기존 폼 이름과 레이블, GET 목적지는 그대로 둡니다. 저장 후 브라우저를 새로고침합니다.

solution 기준 자동 검사

실습 폴더에서 실행합니다. 아래 출력은 모범 답안을 실제 실행한 결과입니다. 이 검사는 제공 CSS 선언 계약과 앞 모듈 HTML 계약을 확인하며 렌더링 검증 결과는 아닙니다.

npm test

실행 결과

> frontend-m02-box-model@1.0.0 test
> python3 check.py

PASS 앞 모듈 HTML·폼 계약 18개 유지
PASS 긴 제목·URL·이미지 없는 카드 fixture
PASS index.html 뷰포트 및 공통 CSS
PASS event.html 뷰포트 및 공통 CSS
PASS results.html 뷰포트 및 공통 CSS
PASS 공통 border-box 및 카드 테두리
PASS 컨테이너 가변 너비와 여백
PASS 카드 padding을 포함하는 가변 너비
CSS 계약 8개, 실패 0개
화면·200% 확대·실제 Tab 표시는 LAYOUT.md에 별도 기록합니다.

실제 화면과 사용자 흐름 기록

브라우저의 파일 열기로 index.html을 엽니다. 요소 탭에서 main과 card의 computed width·padding·border·box-sizing을 읽습니다. 320px에서 부모 내용 공간과 카드 테두리 폭을 비교하고 글과 이미지의 남은 넘침은 따로 기록합니다.

콘솔에서 아래 숫자를 읽고 LAYOUT.md에 환경·페이지·폭·관찰을 남깁니다. 수치가 같아도 잘린 내용이나 포커스가 없는지 눈으로 확인합니다. 실행 전의 결과는 미확인으로 둡니다.

({scrollWidth: document.documentElement.scrollWidth,
  clientWidth: document.documentElement.clientWidth})

확인 문제

실습

카드의 padding·border를 포함한 너비를 계산하고 수정합니다. starter의 TODO CSS를 수정해 npm ci 후 npm test를 통과시키세요. 자동 검사는 제공 선언 계약만 확인합니다. HTML의 name·label·설명 참조·GET 제출·DOM 순서를 유지하고, 세 페이지 네 폭·200% 확대·키보드 실제 관찰을 LAYOUT.md에 기록합니다. 검사 파일은 수정하지 않습니다.

시작 코드·테스트 내려받기

실행 명령

npm test

기대 결과

CSS 계약 8개, 실패 0개. 화면·확대·키보드 결과는 LAYOUT.md에 별도 기록합니다.

모범 답안모범 답안 내려받기

더 읽기

면접 질문

  • content-box와 border-box에서 width, padding, border, margin이 어떻게 계산되는지 설명해 주세요.
  • 같은 클래스의 CSS 선언이 적용되지 않을 때 어떤 순서로 원인을 확인하나요?