반응형 웹 디자인 기초
viewport와 미디어 쿼리, 유동 크기와 반응형 이미지를 적용해 모바일부터 데스크톱까지 레이아웃을 조정합니다.
지금까지 HTML로 구조를 만들고, CSS로 색과 글꼴을 정하고, Flexbox와 Grid로 레이아웃을 만들었습니다. 그런데 사용자들은 데스크톱과 노트북뿐 아니라 스마트폰과 태블릿처럼 화면 크기가 전혀 다른 기기로 웹에 접속합니다. 레이아웃 하나로 모든 기기에서 편하게 읽히게 하기는 어렵기 때문에, 화면에 맞춰 배치를 바꾸는 반응형 웹 디자인(Responsive Web Design, RWD)이 필요합니다.
반응형 웹 디자인은 같은 내용을 기기의 화면 크기와 조건에 맞춰 다시 배치해, 어떤 환경에서도 읽고 쓰기 편하게 보여 주는 설계 방식입니다. 이 글에서는 반응형 웹 디자인의 구성 요소와 뷰포트 메타 태그, 조건에 따라 CSS를 적용하는 미디어 쿼리(media query)를 익힌 뒤, 화면 너비에 따라 한 열에서 두 열, 세 열로 바뀌는 블로그 레이아웃을 만들어 봅니다.
RESPONSIVE REFLOW
같은 내용을 1열에서 2열·3열로 재배치합니다
모바일 기본 배치를 먼저 작성하고, 실습의 min-width 조건에서 Grid 영역과 내비게이션 방향을 바꿉니다.
기본 · 1열
header → nav → main → aside → footer. nav의 링크는 가로로 배치하고 공간이 부족하면 줄바꿈합니다.
768px 이상 · 2열
nav는 위에, main과 aside는 나란히 배치합니다.
1024px 이상 · 3열
nav가 왼쪽 열로 이동하고 링크를 세로로 배치합니다.
열 수를 바꿔도 읽기·focus 순서는 HTML에서 시작합니다
본문 폭, 이미지 max-width: 100%·height: auto, 자연스러운 줄바꿈을 먼저 설정합니다. 콘텐츠가 겹치거나 읽기 힘들어지는 지점에서 레이아웃을 조정합니다.
768px·1024px는 이 실습의 기준이며 모든 사이트의 정답이 아닙니다. 각 폭에서 터치 영역, 확대, 콘텐츠 손실, reduced motion과 색 대비를 확인합니다. 그림의 박스 폭은 구조를 위한 도식입니다.
위 그림은 이 글의 실습이 같은 HTML을 화면 너비에 따라 세 가지로 배치하는 모습을 보여 줍니다. 기본 상태에서는 헤더, 내비게이션, 본문, 사이드바, 푸터가 한 열로 쌓이고, 내비게이션의 링크는 가로로 놓이다가 공간이 모자라면 줄을 바꿉니다. 768px 이상에서는 내비게이션이 위에 남고 본문과 사이드바가 나란히 놓이는 두 열이 되며, 1024px 이상에서는 내비게이션이 왼쪽 열로 옮겨 가 링크를 세로로 놓는 세 열이 됩니다.
그림 아래의 설명처럼 열의 수가 바뀌어도 읽기 순서와 키보드 초점의 순서는 HTML에서 시작합니다. 먼저 본문 폭과 이미지 크기, 자연스러운 줄 바꿈을 정해 두고, 내용이 겹치거나 읽기 힘들어지는 지점에서 레이아웃을 바꿉니다. 768px과 1024px은 이 실습의 기준일 뿐 모든 사이트의 정답이 아니며, 각 너비에서 터치 영역, 확대, 내용이 빠지지 않는지, 색 대비가 충분한지를 확인합니다.
반응형 웹 디자인의 구성 요소
반응형 웹 디자인은 뷰포트(viewport)의 크기와 방향, 해상도 같은 조건에 맞춰 같은 HTML의 내용을 다시 배치하는 방식입니다. CSS 파일이 반드시 하나여야 하는 것은 아니며, 이 글의 실습은 한 파일에 기본 규칙과 화면 너비별 규칙을 모아 비교하기 쉽게 했습니다. 반응형 웹 디자인은 다음 세 가지로 이루어집니다.
- 유연한 그리드(flexible grid):
px같은 고정 단위 대신%,em,rem,vw,fr같은 상대 단위로 레이아웃 요소의 크기를 정해, 화면에 맞춰 늘고 줄게 합니다. Flexbox와 Grid가 이 일을 맡습니다. - 유연한 이미지와 미디어: 이미지나 비디오가 부모 요소보다 커지지 않고 함께 줄어들게 합니다.
max-width: 100%; height: auto;가 대표적입니다. - 미디어 쿼리: 화면 너비 같은 조건에 따라 다른 CSS를 적용합니다. 이 글에서 주로 다룹니다.
뷰포트 메타 태그
반응형 페이지를 만들려면 HTML 문서의 <head>에 뷰포트 메타 태그를 반드시 넣어야 합니다. 이 태그가 없으면 모바일 브라우저는 페이지를 데스크톱 너비(보통 980px)로 그린 뒤 화면에 맞게 축소해 보여 주므로, 글자가 아주 작아지고 미디어 쿼리도 의도대로 동작하지 않습니다.
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>반응형 웹 페이지</title>
<link rel="stylesheet" href="css/style.css">
</head>width=device-width는 레이아웃의 기준이 되는 뷰포트의 너비를 기기 화면의 CSS 픽셀 너비에 맞추고, initial-scale=1.0은 처음 열었을 때의 확대 배율을 1배로 정합니다. CSS 픽셀은 화면의 물리 픽셀과 일대일로 같지 않습니다. 고해상도 휴대폰은 CSS 픽셀 하나를 물리 픽셀 여러 개로 그리므로, 물리 해상도가 1080픽셀인 휴대폰도 미디어 쿼리에서는 360px 같은 너비로 보일 수 있습니다. 자세한 내용은 MDN의 viewport 설명을 참고하세요.
미디어 쿼리
미디어 쿼리는 미디어 유형(화면, 인쇄 등)이나 미디어 기능(뷰포트의 너비, 높이, 방향 등)에 따라 CSS를 조건부로 적용하는 기능입니다. 같은 HTML 문서를 휴대폰에서는 한 열로, 넓은 모니터에서는 여러 열로 보여 줄 수 있는 것도 이 기능 덕분입니다. 이 절에서는 @media 규칙의 문법과 자주 쓰는 미디어 기능을 먼저 보고, 기준 너비를 어떤 순서로 쌓을지 정하는 두 방식과 여러 조건을 묶는 방법을 차례로 봅니다.
기본 문법
미디어 쿼리는 @media 규칙으로 씁니다. 조건이 맞으면 중괄호 안의 규칙이 적용되고, 맞지 않으면 무시됩니다.
@media media-type and (media-feature) {
/* 여기에 특정 조건이 충족될 때 적용될 CSS 규칙 작성 */
}미디어 유형에는 기본값인 all(모든 장치), screen(화면), print(인쇄)가 있습니다. 예전에는 speech 같은 유형도 있었지만 지금은 쓰지 않으므로, 스크린 리더 사용자를 위한 분기로 미디어 유형을 쓰지 않습니다. 접근성은 기본 HTML의 구조와 읽기 순서로 보장합니다.
미디어 기능 가운데 가장 많이 쓰는 것은 뷰포트의 너비입니다. 자주 쓰는 미디어 기능은 다음과 같습니다.
width,height: 뷰포트의 너비와 높이min-width,max-width: 너비가 그 값 이상일 때, 이하일 때orientation: 뷰포트의 방향으로, 높이가 너비보다 크거나 같으면 세로인portrait, 너비가 더 크면 가로인landscape입니다. 기기를 돌린 방향과 늘 같지는 않아서, 가로로 놓인 모니터에서도 브라우저 창을 좁고 길게 줄이면portrait가 됩니다.resolution: 화면의 해상도prefers-color-scheme: 사용자가 고른 밝은 테마(light)나 어두운 테마(dark)prefers-reduced-motion: 사용자가 움직임을 줄이도록 설정했는지 여부
모바일 우선과 데스크톱 우선
화면 너비에 따라 레이아웃을 바꾸는 것이 가장 흔한 쓰임입니다. 다음 코드는 가장 작은 화면에 맞춘 기본 스타일을 먼저 쓰고, min-width 미디어 쿼리로 넓은 화면의 스타일을 점점 더하는 모바일 우선(mobile first) 방식입니다.
/* 기본 스타일 (모바일 우선, Mobile First) */
body {
background-color: lightblue;
color: #333;
font-size: 16px;
}
.container {
width: 95%;
margin: 0 auto;
padding: 10px;
background-color: white;
}
/* 스크린 너비가 600px 이상일 때 적용되는 스타일 (태블릿) */
@media screen and (min-width: 600px) {
body {
background-color: lightgreen;
font-size: 17px;
}
.container {
width: 80%;
padding: 20px;
}
}
/* 스크린 너비가 1024px 이상일 때 적용되는 스타일 (데스크톱) */
@media screen and (min-width: 1024px) {
body {
background-color: lightcoral;
font-size: 18px;
}
.container {
width: 70%;
max-width: 1000px;
padding: 30px;
}
}너비가 599px 이하이면 하늘색 배경에 16px 글자, 600px부터 1023px까지는 연두색 배경에 17px 글자, 1024px 이상이면 산호색 배경에 18px 글자가 됩니다. 1024px 이상에서는 두 미디어 쿼리가 모두 참이지만, 두 규칙의 명시도가 같으므로 나중에 나온 1024px 규칙이 이깁니다.
반대로 가장 큰 화면의 스타일을 기본으로 쓰고, max-width 미디어 쿼리로 작은 화면의 스타일을 더하는 데스크톱 우선(desktop first) 방식도 있습니다.
/* 기본 스타일 (데스크톱 우선, Desktop First) */
body {
background-color: lightcoral;
font-size: 18px;
}
.container {
width: 70%;
max-width: 1000px;
margin: 0 auto;
padding: 30px;
background-color: white;
}
/* 스크린 너비가 1023px 이하일 때 적용되는 스타일 (태블릿) */
@media screen and (max-width: 1023px) {
body {
background-color: lightgreen;
font-size: 17px;
}
.container {
width: 80%;
padding: 20px;
}
}
/* 스크린 너비가 599px 이하일 때 적용되는 스타일 (모바일) */
@media screen and (max-width: 599px) {
body {
background-color: lightblue;
font-size: 16px;
}
.container {
width: 95%;
padding: 10px;
}
}두 방식 모두 쓸 수 있습니다. 이 글의 실습은 좁은 화면에서도 내용이 빠지지 않는 기본 배치를 먼저 만들고, 공간이 넉넉할 때 열을 늘리는 모바일 우선 방식을 씁니다. 좁은 화면의 단순한 배치에서 시작하면 넓은 화면에서 더할 것만 고민하면 되므로 코드를 관리하기 쉽습니다. 어느 쪽을 고르든, 지금 만드는 내용과 화면 조건에 맞는 이유로 고르는 것이 중요합니다.
여러 조건 묶기
and는 모든 조건이 참일 때 적용합니다. 미디어 유형과 미디어 기능을 잇거나 미디어 기능 여러 개를 이을 때 쓰며, 다음 첫 쿼리처럼 min-width와 max-width를 함께 쓰면 특정 너비 구간에만 규칙을 적용할 수 있습니다.
@media screen and (min-width: 768px) and (max-width: 1024px) {
/* 화면 너비가 768px 이상 1024px 이하일 때 */
}
@media (min-height: 400px) and (orientation: landscape) {
/* 높이가 400px 이상이고 가로 모드일 때 */
}두 번째 쿼리는 미디어 유형을 생략했으므로 all로 간주되어, 화면이든 인쇄든 두 조건만 맞으면 적용됩니다.
쉼표(,)는 나열한 쿼리 가운데 하나라도 참이면 적용합니다. 쉼표로 나뉜 쿼리는 서로 독립적이어서, 다음 예의 print에는 앞쪽의 max-width: 600px 조건이 붙지 않습니다.
@media screen and (max-width: 600px), print {
/* 화면 너비가 600px 이하이거나 인쇄할 때 */
}not은 쿼리 전체를 뒤집습니다. 다음 쿼리는 “화면이면서 가로 방향”이라는 조건 전체가 거짓일 때 적용됩니다.
@media not screen and (orientation: landscape) {
/* 스크린 장치이면서 가로 모드인 조건 전체를 제외 */
}요즘 브라우저는 @media (768px <= width <= 1024px)처럼 범위를 비교 연산자로 적는 문법도 지원하므로, min-width와 max-width를 and로 잇는 대신 쓸 수 있습니다.
더 깊이: 599px과 600px 사이에 빈틈이 생기는 이유
max-width: 599px과 min-width: 600px로 구간을 나누면 빈틈이 없어 보이지만, 뷰포트의 너비가 언제나 정수인 것은 아닙니다. 화면의 픽셀 밀도가 정수가 아니거나 사용자가 페이지를 확대하면 CSS 픽셀로 잰 뷰포트 너비가 599.5px처럼 소수가 될 수 있고, 이때는 두 쿼리가 모두 거짓이 되어 어느 규칙도 적용되지 않습니다. Media Queries Level 4 명세도 320px과 321px로 비슷한 예를 들며, min-과 max- 접두사는 값을 포함하는 비교만 표현할 수 있어서 이런 빈틈을 만들 수 있다고 설명합니다.
범위 문법은 이 문제를 풉니다. 작은 화면은 (width < 600px), 큰 화면은 (width >= 600px)처럼 한쪽은 값을 빼고 다른 쪽은 값을 포함하게 적으면, 어떤 소수 너비도 정확히 한 구간에만 속합니다. 앞의 모바일 우선 예처럼 min-width만 쓰고 큰 쪽으로 덧붙여 가는 방식도, 구간 사이를 메우는 값이 언제나 기본 스타일이므로 빈틈이 생기지 않습니다.
미디어 쿼리에 em이나 rem을 쓸 때의 기준도 일반 규칙과 다릅니다. 명세는 미디어 쿼리 안의 상대 단위가 문서의 CSS가 아니라 초기값을 기준으로 한다고 정하므로, html { font-size: 20px; }을 적어도 미디어 쿼리의 1em은 사용자나 브라우저가 정한 기본 글자 크기를 따릅니다. 자세한 내용은 Media Queries Level 4 명세에 있습니다.
반응형 이미지
이미지는 페이지의 용량을 많이 차지하고, 부모보다 크면 레이아웃을 망가뜨립니다. max-width: 100%는 이미지가 부모 요소의 너비를 넘지 않게 하므로, 부모가 줄어들면 이미지도 함께 줄어듭니다. height: auto는 너비가 바뀔 때 높이를 원래 비율에 맞춰 바꿔, 이미지가 찌그러지지 않게 합니다.
img {
max-width: 100%;
height: auto; /* 이미지의 원본 비율 유지 */
display: block; /* 가끔 인라인 요소로 인한 하단 여백 제거 */
}display: block은 이미지를 인라인 요소로 두었을 때 글자의 기준선 아래 생기는 작은 틈을 없앱니다. 화면에 따라 아예 다른 크기의 이미지 파일을 보내는 방법은 13장의 asset 최적화에서 다룹니다.
실습: 반응형 블로그
앞의 Flexbox와 Grid를 바탕으로 미디어 쿼리를 적용해, 화면 너비에 따라 바뀌는 블로그 레이아웃을 만들어 봅니다. 다음 구조로 파일을 준비합니다.
responsive_blog.html에는 다음 코드를 씁니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>반응형 블로그 레이아웃</title>
<link rel="stylesheet" href="css/responsive.css">
</head>
<body>
<div class="page-wrapper">
<header class="header">
<h1>반응형 블로그</h1>
<p>모든 화면에서 읽기 쉬운 레이아웃</p>
</header>
<nav class="main-nav">
<ul>
<li><a href="#">홈</a></li>
<li><a href="#">카테고리</a></li>
<li><a href="#">연락처</a></li>
<li><a href="#">로그인</a></li>
</ul>
</nav>
<main class="main-content">
<article class="blog-post">
<h2>반응형 웹 디자인이란?</h2>
<p>반응형 웹 디자인은 하나의 웹사이트가 다양한 기기(데스크톱, 태블릿, 스마트폰)에서 최적의 형태로 보여지도록 설계하는 기법입니다. 이는 사용자 경험을 향상시키고, 개발 및 유지보수 비용을 절감하는 효과가 있습니다.</p>
<p>핵심 기술로는 유동적인 그리드 레이아웃, 유동적인 이미지 및 미디어, 그리고 미디어 쿼리가 있습니다. 특히 미디어 쿼리는 특정 화면 크기나 장치 특성에 따라 다른 CSS 스타일을 적용할 수 있게 해주는 핵심 도구입니다.</p>
<h3>미디어 쿼리 활용법</h3>
<p>미디어 쿼리는 <code>@media</code> 규칙을 사용하여 정의하며, <code>min-width</code>, <code>max-width</code>, <code>orientation</code> 등 다양한 미디어 기능을 조건으로 활용할 수 있습니다. 예를 들어, 모바일 화면에서는 내비게이션 메뉴를 숨기거나, 텍스트 크기를 조절하는 등의 변화를 줄 수 있습니다.</p>
</article>
<div class="related-posts">
<h3>최신 글</h3>
<ul>
<li><a href="#">Flexbox 완전 정복</a></li>
<li><a href="#">CSS Grid 심화 학습</a></li>
<li><a href="#">웹 접근성의 중요성</a></li>
</ul>
</div>
</main>
<aside class="sidebar">
<h3>공지사항</h3>
<p>이번 달 웹 개발 스터디는 매주 화요일 저녁 7시입니다.</p>
<h3>인기 태그</h3>
<div class="tags">
<span>#CSS</span>
<span>#HTML</span>
<span>#JavaScript</span>
<span>#WebDev</span>
<span>#Frontend</span>
</div>
</aside>
<footer class="footer">
<p>© 2026 반응형 블로그. 모든 권리 보유.</p>
</footer>
</div>
</body>
</html>css/responsive.css에는 다음 CSS를 씁니다. 미디어 쿼리 밖의 규칙이 가장 좁은 화면의 기본 스타일이고, 아래의 두 미디어 쿼리가 넓은 화면에서 덧붙는 스타일입니다.
/* 뷰포트 메타 태그가 HTML에 설정되어 있는지 확인! */
/* 전역 스타일 및 초기 설정 (Mobile First) */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f0f2f5;
color: #333;
line-height: 1.6;
}
.page-wrapper {
display: grid; /* Grid 컨테이너 */
gap: 15px; /* 기본 간격 */
grid-template-columns: 1fr; /* 모바일: 모든 영역 한 열로 */
grid-template-areas:
"header"
"main-nav"
"main-content"
"sidebar"
"footer";
max-width: 100%;
margin: 0 auto;
padding: 10px; /* 기본 패딩 */
}
/* 공통 스타일 */
.page-wrapper > * {
background-color: white;
padding: 15px; /* 기본 패딩 */
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
/* 헤더 */
.header {
grid-area: header;
background-color: #2c3e50;
color: white;
text-align: center;
padding: 20px 15px;
}
.header h1 { font-size: 2em; margin-bottom: 5px; }
.header p { font-size: 0.9em; }
/* 내비게이션 */
.main-nav {
grid-area: main-nav;
background-color: #34495e;
color: white;
}
.main-nav ul {
list-style: none;
display: flex; /* 메뉴 아이템들을 Flexbox로 가로 정렬 */
flex-wrap: wrap; /* 공간 부족 시 줄바꿈 */
justify-content: center; /* 가운데 정렬 */
gap: 10px;
}
.main-nav li { margin: 0; }
.main-nav a {
display: block;
color: white;
text-decoration: none;
padding: 8px 12px;
border-radius: 5px;
transition: background-color 0.3s ease;
}
.main-nav a:hover {
background-color: #4a6581;
}
/* 메인 콘텐츠 */
.main-content {
grid-area: main-content;
}
.main-content h2 {
color: #2980b9;
margin-bottom: 10px;
font-size: 1.6em;
}
.main-content h3 {
color: #4a6581;
margin-top: 20px;
margin-bottom: 8px;
font-size: 1.2em;
}
.main-content p {
margin-bottom: 1em;
}
.blog-post img {
max-width: 100%; /* 반응형 이미지 */
height: auto;
display: block;
margin: 0 auto 15px auto;
border-radius: 5px;
}
.related-posts ul {
list-style: none;
margin-top: 10px;
}
.related-posts li a {
display: block;
padding: 8px 0;
border-bottom: 1px dotted #ccc;
color: #333;
text-decoration: none;
}
.related-posts li:last-child a { border-bottom: none; }
.related-posts li a:hover { color: #2980b9; }
/* 사이드바 */
.sidebar {
grid-area: sidebar;
background-color: #ecf0f1;
}
.sidebar h3 {
color: #2c3e50;
margin-bottom: 10px;
font-size: 1.2em;
}
.tags span {
display: inline-block;
background-color: #bdc3c7;
color: #25303b; /* 회색 배경과 대비가 충분한 어두운 글자 */
padding: 5px 10px;
border-radius: 15px;
font-size: 0.8em;
margin: 5px 5px 0 0;
}
/* 푸터 */
.footer {
grid-area: footer;
background-color: #34495e;
color: white;
text-align: center;
padding: 15px;
font-size: 0.8em;
}
/* ----------------------------------------------------- */
/* 미디어 쿼리 (태블릿 크기, min-width: 768px) */
/* ----------------------------------------------------- */
@media screen and (min-width: 768px) {
.page-wrapper {
grid-template-columns: 1fr 250px; /* 메인 1fr, 사이드바 250px */
grid-template-areas:
"header header"
"main-nav main-nav"
"main-content sidebar" /* 메인 콘텐츠와 사이드바가 나란히 */
"footer footer";
gap: 20px; /* 간격 늘림 */
padding: 20px; /* 패딩 늘림 */
}
.header h1 { font-size: 2.5em; }
.header p { font-size: 1em; }
.main-nav ul {
justify-content: flex-start; /* 왼쪽 정렬 */
gap: 20px;
}
.main-nav a { padding: 10px 15px; }
.main-content h2 { font-size: 2em; }
.main-content h3 { font-size: 1.5em; }
.sidebar h3 { font-size: 1.5em; }
.tags span { font-size: 0.9em; }
.footer { font-size: 0.9em; }
}
/* ----------------------------------------------------- */
/* 미디어 쿼리 (데스크톱 크기, min-width: 1024px) */
/* ----------------------------------------------------- */
@media screen and (min-width: 1024px) {
.page-wrapper {
grid-template-columns: 200px 1fr 280px; /* 내비게이션 200px, 메인 가변, 사이드바 280px */
grid-template-areas:
"header header header"
"main-nav main-content sidebar" /* 내비게이션, 메인, 사이드바 나란히 */
"footer footer footer";
max-width: 1400px; /* 최대 너비 더 넓게 */
padding: 30px;
gap: 30px;
}
/* 내비게이션은 세로로 */
.main-nav {
padding: 20px;
}
.main-nav ul {
flex-direction: column; /* 세로 정렬 */
align-items: stretch; /* 항목을 내비게이션 폭만큼 늘려 링크가 너비 전체를 차지하게 함 */
gap: 15px;
}
.main-nav a {
width: 100%; /* 너비 전체 차지 */
text-align: left;
padding: 12px 15px;
font-size: 1.1em;
}
.header h1 { font-size: 3em; }
.header p { font-size: 1.1em; }
.main-content h2 { font-size: 2.5em; }
.main-content h3 { font-size: 1.8em; }
.main-content p { font-size: 1.1em; }
.sidebar h3 { font-size: 1.8em; }
.tags span { font-size: 1em; }
.footer { font-size: 1em; }
}Live Server로 responsive_blog.html을 열고 브라우저 창의 너비를 바꿔 봅니다. 가장 좁을 때는 다섯 영역이 한 열로 쌓이고, 내비게이션의 링크는 가로로 놓이다가 모자라면 다음 줄로 넘어갑니다. 768px 이상에서는 본문과 사이드바가 나란히 놓이는 두 열이 되고, 1024px 이상에서는 내비게이션이 왼쪽 열로 옮겨 가 링크를 세로로 놓는 세 열이 됩니다. 이 HTML에는 이미지가 없으므로 .blog-post img 규칙은 나중에 이미지를 넣었을 때 적용됩니다.
태그 배지의 글자색은 대비를 계산해 정했습니다. 흰 글자는 #bdc3c7 배경과의 대비가 약 1.78:1에 불과해 읽기 어렵지만, #25303b로 바꾸면 약 7.5:1이 됩니다. 반응형 디자인은 열의 배치만이 아니라, 모든 너비에서 글자가 읽히고 확대했을 때도 내용이 이어지는지까지 확인하는 일입니다.
정리
- 반응형 웹 디자인은 유연한 그리드, 유연한 이미지, 미디어 쿼리로 같은 내용을 화면에 맞춰 다시 배치합니다.
- 뷰포트 메타 태그
width=device-width, initial-scale=1.0이 없으면 모바일 브라우저가 페이지를 축소해 보여 주며, 미디어 쿼리의 너비는 물리 픽셀이 아니라 CSS 픽셀입니다. - 미디어 쿼리는
@media로 미디어 유형과 기능의 조건을 적고,and, 쉼표,not으로 묶습니다. - 모바일 우선은 기본 스타일 위에
min-width로, 데스크톱 우선은max-width로 스타일을 더하며, 여러 쿼리가 참이면 cascade 규칙에 따라 나중 규칙이 이깁니다. - 이미지는
max-width: 100%와height: auto로 부모에 맞춰 비율을 유지하며 줄어들게 합니다. - 열의 수가 바뀌어도 읽기와 초점 순서는 HTML을 따르므로, 각 너비에서 내용, 대비, 확대, 터치 영역을 확인합니다.
연습 문제
1. 기준 너비에서의 열 수
실습의 responsive.css에서 화면 너비가 767px, 768px, 1023px, 1024px일 때 적용되는 미디어 쿼리와 열의 수를 구해 보세요. 1024px에서 “768px 규칙이 꺼졌다”고 설명해도 될까요?
해설 보기
767px에서는 미디어 쿼리가 하나도 맞지 않아 기본 한 열이고, 768px과 1023px에서는 768px 쿼리만 맞아 두 열입니다. 1024px에서는 두 min-width 쿼리가 모두 참이며, 같은 선택자의 같은 속성이므로 나중에 나온 1024px 쿼리의 선언이 이겨 세 열이 됩니다.
768px 규칙이 꺼진 것은 아닙니다. 1024px 쿼리가 다시 정하지 않은 속성, 예를 들어 .main-nav ul의 justify-content: flex-start는 768px 쿼리의 값이 계속 적용됩니다. .main-nav a의 display: block처럼 처음부터 기본 규칙에 있던 값도 세 열에서 그대로 쓰입니다.
2. 사이드바 넓히기
두 열에서 사이드바가 좁다고 느껴 768px 쿼리의 grid-template-columns를 1fr 300px로 바꿨습니다. 화면 너비가 800px일 때 본문과 사이드바의 너비는 어떻게 바뀔까요? 375px 화면은 왜 그대로일까요?
해설 보기
800px에서 .page-wrapper의 내용 폭은 안쪽 여백 20px씩을 뺀 760px이고, 두 열 사이의 간격이 20px입니다. 바꾸기 전에는 본문이 760 − 20 − 250 = 490px, 사이드바가 250px이었고, 바꾼 뒤에는 본문이 760 − 20 − 300 = 440px, 사이드바가 300px입니다. 사이드바가 50px 넓어진 만큼 본문이 50px 좁아집니다.
375px 화면에서는 768px 쿼리가 적용되지 않으므로 기본 한 열 배치가 그대로입니다. 바꾼 뒤에는 800px 화면에서 본문의 글이 너무 좁아지거나 넘치지 않는지 확인합니다.
3. 뷰포트 메타 태그를 지우면
responsive_blog.html에서 뷰포트 메타 태그를 지우고 너비 390px인 휴대폰으로 열었습니다. 어떤 레이아웃이 보이고, 글자는 어떻게 보일까요?
해설 보기
모바일 브라우저는 뷰포트의 너비를 보통 980px로 잡고 페이지를 그린 뒤, 그 결과를 화면 너비에 맞게 축소해 보여 줍니다. 980px은 768px 이상 1024px 미만이므로 두 열 레이아웃이 그려지고, 전체가 약 40%로 줄어들어 글자를 읽으려면 확대해야 합니다.
한 열 배치를 의도한 모바일 우선 CSS를 써도 뷰포트 메타 태그가 없으면 그 배치가 보이지 않습니다. 반응형 페이지에는 항상 이 태그를 넣습니다.
이것으로 3장을 마칩니다. 3장에서는 CSS의 문법과 선택자, 색과 글꼴, 박스 모델, Flexbox와 Grid, 반응형 웹 디자인으로 페이지의 모양과 레이아웃을 만드는 방법을 살펴봤습니다. 다음 장의 CSS 애니메이션과 트랜지션부터는 움직임, 변수, 설계 방법론 같은 CSS의 고급 기법을 다룹니다.