이미지 예제는 이미지 다운로드에서 내려받는다.
[!INFO]
CSS 레이아웃과 반응형 웹 디자인 기법을 다룬다.
float로 텍스트 흐름을 조절하고,position으로 요소의 위치를 정한다. 이어서flex와grid로 현대적인 레이아웃을 구성한다.미디어 쿼리,
calc(), 반응형 이미지와 영상 예제로 화면 크기에 맞춰 배치를 바꾸는 방법도 확인한다.
1. float
[!TIP]
- 요소를 일반적인 문서 흐름에서 제외하여 부모 요소의 왼쪽이나 오른쪽 가장자리에 배치한다.
1.1. 주요 속성
| 값 | 설명 |
|---|---|
left |
요소를 왼쪽으로 부유시킴 |
right |
요소를 오른쪽으로 부유시킴 |
none |
기본값. 부유시키지 않음 |
부모 안의 자식이 모두
float상태이면 부모 높이가 사라진 것처럼 보일 수 있다. 초보 단계에서는 부모에display: flow-root를 지정하거나 아래의 Clearfix 방법으로 해결한다.자세한 내용은 🔗MDN 참고.
1.2. float 활용 예제
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>float 텍스트 흐름</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
</style>
</head>
<body>
<div class="container">
<img src="img/lotus.png" alt="연꽃">
<p>인도가 원산지인 연꽃은 연못 위에 떠 있는 수생식물로 알려져 있지만, 논이나 늪지의 진흙에서도 자라고 식용과 약용으로도 쓰인다.</p>
</div>
</body>
</html>
img {
float: right;
width: 150px;
margin-left: 20px;
}
[!INFO]
float: right— 이미지를 일반 문서 흐름에서 부분적으로 빼내 부모의 오른쪽 가장자리로 부유시킨다. 뒤따르는 텍스트는 문단이 이미지 왼쪽 공간으로 자연스럽게 흐르게 된다.
width: 150px— 떠 있는 이미지의 너비를 고정해 텍스트가 흐를 영역을 확보한다.
margin-left: 20px— 이미지 왼쪽에 여백을 주어 흐르는 텍스트와 너무 붙지 않도록 간격을 확보한다.
1.3. Clearfix (실무 해제 기법)
/* 부모 요소에 적용하여 자식의 float을 해제함 */
.clearfix::after {
content: "";
display: block;
clear: both;
}
[!INFO]
::after— 부모 요소 끝에 내용 없는 가상 요소를 만든다. 이 가상 요소가 float 해제 역할을 담당한다.
content: ""— 가상 요소가 화면에 생성되려면 반드시 필요한 속성이다. 빈 문자열을 넣어 내용 없는 요소를 만든다.
display: block— 가상 요소를 블록 요소로 만들어 clear 속성이 제대로 적용되도록 한다.
clear: both— 앞서 float된 요소들의 영향을 모두 해제한다. 이를 통해 부모가 자식의 높이를 다시 인식하게 되어 레이아웃이 무너지지 않는다.
2. position
설명
요소를 문서의 좌표 시스템 상에서 배치하는 방법을 정의한다. 레이어 겹침(layering)이나 화면 상단 고정 메뉴, 팝업 등을 구현할 때 필수적인 속성이다.
| 값 | 설명 |
|---|---|
static |
기본값. 문서 흐름에 따라 순차적 배치 |
relative |
원래 위치를 기준으로 상대적 이동 |
absolute |
자신을 감싸는 요소 중 위치 기준이 있는 요소를 기준으로 배치 |
fixed |
뷰포트(화면)를 기준으로 고정 배치 |
sticky |
지정한 임계값까지는 흐름을 따르다 그 후 고정됨 |
위치 오프셋 — top / right / bottom / left
position 으로 기준을 정했다면, 그 기준에서 얼마나 떨어뜨릴지는 아래 네 속성으로 지정한다. position: static 일 때는 무시되고, 나머지 값일 때만 동작한다.
| 속성 | 설명 |
|---|---|
top |
기준 위치의 위쪽 모서리에서 떨어뜨릴 거리 |
right |
기준 위치의 오른쪽 모서리에서 떨어뜨릴 거리 |
bottom |
기준 위치의 아래쪽 모서리에서 떨어뜨릴 거리 |
left |
기준 위치의 왼쪽 모서리에서 떨어뜨릴 거리 |
같은
top: 20px이라도 기준이 다르다.relative는 원래 자기 위치를 기준으로 한다.absolute는 자신을 감싸는 요소 중 가장 가까운position: relative,absolute,fixed,sticky요소를 기준으로 한다.fixed는 브라우저 화면을 기준으로 한다.값에 음수를 주면 반대 방향으로 밀 수 있다.
z-index는 주로position이 지정된 요소의 쌓임 순서를 조절한다. 또한 플렉스 아이템과 그리드 아이템은position: static상태에서도z-index를 사용할 수 있다.자세한 내용은 🔗MDN 참고.
2.1. absolute & relative
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>position absolute & relative</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.parent { width: 300px; height: 300px; background: #eee; }
.child { width: 100px; height: 100px; background: red; }
</style>
</head>
<body>
<div class="parent">
<div class="child"></div>
</div>
</body>
</html>
.parent {
position: relative;
}
.child {
position: absolute;
right: 0;
bottom: 0;
}
[!INFO]
position: relative— 부모 .parent에 지정한다. 원래 위치는 그대로 유지하면서 자식의 absolute 배치 기준이 되는 역할을 한다.
position: absolute— 자식 .child를 문서 흐름에서 빼내 가장 가까운 position 기준 요소(여기서는 .parent)를 기준으로 절대 배치한다.
bottom: 0; right: 0— 기준 요소의 오른쪽 아래 구석으로 자식을 붙인다. 상하·좌우 오프셋 값으로 정밀한 위치 제어가 가능하다.
2.2. sticky 예제
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>sticky position</title>
<style>
.container { background-color: orangered; width: 700px; height: 600px; overflow: auto; }
.box { width: 350px; height: 100px; font-size: 25px; color: #fff; }
.box-1 { background-color: green; }
.scroll-area { height: 1200px; }
</style>
</head>
<body>
<div class="container">
<div class="box box-1">Box 1 (Sticky)</div>
<div class="scroll-area">스크롤 영역 (Box 1 이 상단에 고정됨)</div>
</div>
</body>
</html>
.box-1 {
position: sticky;
top: 0;
}
[!INFO]
position: sticky— .box-1에 지정한다. 평소에는 일반 흐름대로 배치되다가 스크롤이 지정한 임계점에 도달하면 그 자리에 고정되는 속성이다.
top: 0— sticky 요소가 고정될 위치를 정한다. 스크롤 시 상단에서 0px 떨어진 지점에 달라붙어 고정된다.
overflow: auto— 부모 .container에 지정한다. 내부 내용이 넘칠 때 스크롤이 생기며, sticky 고정은 이 스크롤 영역을 기준으로 동작한다.
3. flex
설명
부모 요소 안의 자식들을 가로 한 줄 또는 세로 한 줄 방향으로 정렬하는 레이아웃 방법이다. 아이템 사이의 공간과 정렬을 CSS가 계산하므로 복잡한 너비 계산을 줄일 수 있다.
플렉스에서는 먼저 아이템이 나열되는 방향을 주축이라고 부른다. 주축이 가로이면 위아래 방향이 교차축이고, 주축이 세로이면 좌우 방향이 교차축이다.
| 주요 속성 | 설명 |
|---|---|
display: flex |
컨테이너를 Flex Box로 선언 |
flex-direction |
주축의 방향(행 또는 열) 결정 |
justify-content |
주축 방향 아이템 정렬 |
align-items |
교차축 방향 아이템 정렬 |
flex-grow / flex-shrink / flex-basis |
아이템이 남는 공간을 늘리는·줄이는 비율과 기본 크기 |
order |
HTML 작성 순서를 바꾸지 않고 화면 표시 순서 변경 |
gap |
아이템 사이 간격 |
Flex 아이템에 지정한
float은 무시되며,margin: auto를 통해 정렬과 여백 처리를 쉽게 구현할 수 있다.자세한 내용은 🔗MDN 참고.
3.1. flex 정렬
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>flex 정렬</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.item { width: 100px; height: 100px; background: tomato; margin: 10px; }
</style>
</head>
<body>
<div class="flex-container">
<div class="item">1</div><div class="item">2</div><div class="item">3</div>
</div>
</body>
</html>
.flex-container {
display: flex;
justify-content: space-around;
}
.item {
color: white;
text-align: center;
line-height: 100px;
}
[!INFO]
display: flex— .flex-container를 플렉스 박스로 선언해 아이템들을 가로 한 줄로 정렬한다.
justify-content: space-around— 주축(가로) 방향으로 아이템들을 균등한 간격으로 배치한다. 각 아이템 좌우에 동일한 여백이 생겨 고르게 분산된다.
line-height: 100px; text-align: center— 아이템 높이와 같은 값의 line-height와 text-align을 주어 박스 안 숫자를 상하·좌우 가운데로 맞춘다.
3.2. flex-direction
플렉스 컨테이너 안에서 아이템을 가로로 놓을지 세로로 쌓을지 정하는 속성이다. 이 방향이 주축이 된다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>플렉스 박스 레이아웃</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.container {
width:700px;
/* 플렉스 컨테이너 지정 */
background-color:#eee;
border:1px solid #222;
margin-bottom:30px;
}
.box {
padding:5px 45px;
margin:5px;
width:80px;
}
.box:nth-child(odd) {
background-color: rgb(255, 187, 0);
}
.box:nth-child(even) {
background-color: rgb(35, 220, 35);
}
p {
text-align: center;
font-weight: bold;
}
/* 주축의 방향을 지정하는 #opt1 ~ #opt4 스타일 만들기 */
</style>
</head>
<body>
<div class="container" id="opt1">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
</div>
<div class="container" id="opt2">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
</div>
<div class="container" id="opt3">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
</div>
<div class="container" id="opt4">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
</div>
</body>
</html>
.container {
display: flex;
}
#opt1 { flex-direction: row; }
#opt2 { flex-direction: row-reverse; }
#opt3 { flex-direction: column; }
#opt4 { flex-direction: column-reverse; }
[!INFO]
flex-direction: row— 기본값이다. 아이템을 왼쪽에서 오른쪽으로 가로로 나란히 놓는다. 이때 가로가 주축, 세로가 교차축이 된다.
flex-direction: row-reverse— 가로 방향은 같지만 순서를 뒤집어 오른쪽에서 왼쪽으로 놓는다. 1, 2, 3이 3, 2, 1 순으로 보인다.
flex-direction: column— 주축을 세로로 바꿔 아이템을 위에서 아래로 쌓는다. 이때 세로가 주축, 가로가 교차축이 된다.
flex-direction: column-reverse— 세로 방향에서 순서를 뒤집어 아래에서 위로 쌓는다.
3.3. flex-wrap (줄바꿈)
아이템이 한 줄에 다 들어가지 못할 때 다음 줄로 넘길지, 강제로 한 줄에 욱여넣을지 정하는 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>플렉스 박스 레이아웃</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.container {
display:flex; /* 플렉스 컨테이너 지정 */
flex-direction: row; /* 주축의 방향을 지정 */
width: 700px;
background-color:#eee;
border:1px solid #222;
margin-bottom:30px;
}
.box {
padding:5px 45px;
margin:5px;
width:80px;
background-color:#222;
}
p {
color:#fff;
text-align: center;
}
/* 줄바꿈을 지정하는 #opt1 ~ #opt3 스타일 만들기 */
</style>
</head>
<body>
<div class="container" id="opt1">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
<div class="box"><p>5</p></div>
<div class="box"><p>6</p></div>
</div>
<div class="container" id="opt2">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
<div class="box"><p>5</p></div>
<div class="box"><p>6</p></div>
</div>
<div class="container" id="opt3">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
<div class="box"><p>5</p></div>
<div class="box"><p>6</p></div>
</div>
</body>
</html>
.container {
display: flex;
flex-direction: row;
}
#opt1 { flex-wrap: nowrap; }
#opt2 { flex-wrap: wrap; }
#opt3 { flex-wrap: wrap-reverse; }
[!INFO]
flex-wrap: nowrap— 기본값이다. 줄바꿈을 하지 않고 모든 아이템을 한 줄에 욱여넣는다. 자리가 모자라면 아이템 너비가 줄어든다.
flex-wrap: wrap— 한 줄에 다 들어가지 못한 아이템을 다음 줄로 자연스럽게 넘긴다. 아이템의 원래 크기를 유지하면서 여러 줄로 쌓인다.
flex-wrap: wrap-reverse— wrap과 같이 여러 줄로 넘기되, 줄이 쌓이는 위아래 순서를 뒤집는다.
3.4. flex-flow (방향과 줄바꿈 한 번에)
flex-direction과 flex-wrap을 한 줄로 묶어 쓰는 단축 속성 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>플렉스 박스 레이아웃</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.container {
display:flex;
width: 700px;
background-color:#eee;
border:1px solid #222;
margin-bottom:10px;
}
.box {
padding:5px 45px;
margin:5px;
width:80px;
}
.box:nth-child(odd) {
background-color: rgb(255, 187, 0);
}
.box:nth-child(even) {
background-color: rgb(35, 220, 35);
}
p {
text-align: center;
font-weight: bold;
}
/* 주축의 방향과 줄바꿈을 한꺼번에 지정하는 #opt1, #opt2 스타일 만들기 */
</style>
</head>
<body>
<div class="container" id="opt1">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
<div class="box"><p>5</p></div>
<div class="box"><p>6</p></div>
</div>
<div class="container" id="opt2">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
<div class="box"><p>5</p></div>
<div class="box"><p>6</p></div>
</div>
</body>
</html>
.container {
display: flex;
}
#opt1 { flex-flow: row wrap; }
#opt2 { flex-flow: row nowrap; }
[!INFO]
flex-flow: row wrap— 앞 값(row)은 flex-direction, 뒤 값(wrap)은 flex-wrap을 한 번에 지정한다. 가로 방향으로 흐르되 자리가 모자라면 다음 줄로 넘긴다.
flex-flow: row nowrap— 가로 방향으로 흐르되 줄바꿈은 하지 않는다. 두 줄짜리 설정을 한 줄로 줄여 적을 수 있어 편리하다.
3.5. justify-content (주축 정렬)
아이템들을 주축(가로) 방향에서 어디에 모을지·어떻게 흩뜨릴지 정하는 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>플렉스 박스 레이아웃</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.container {
display:flex; /* 플렉스 컨테이너 지정 */
background-color:#eee;
border:1px solid #222;
margin-bottom:30px;
}
.box {
padding:5px 45px;
margin:5px;
width:80px;
}
.box:nth-child(odd) {
background-color: rgb(255, 187, 0);
}
.box:nth-child(even) {
background-color: rgb(35, 220, 35);
}
p {
text-align: center;
font-weight: bold;
}
/* 주축의 정렬 방법을 지정하는 #opt1 ~ #opt5 스타일 만들기 */
</style>
</head>
<body>
<div class="container" id="opt1">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
<div class="container" id="opt2">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
<div class="container" id="opt3">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
<div class="container" id="opt4">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
<div class="container" id="opt5">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
<div class="container" id="opt6">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
</body>
</html>
.container {
display: flex;
}
#opt1 { justify-content: flex-start; }
#opt2 { justify-content: flex-end; }
#opt3 { justify-content: center; }
#opt4 { justify-content: space-between; }
#opt5 { justify-content: space-around; }
#opt6 { justify-content: space-evenly; }
[!INFO]
flex-start / flex-end / center— 아이템 묶음을 각각 시작점·끝점·가운데에 몰아 붙인다.space-between — 첫 아이템은 맨 왼쪽, 마지막 아이템은 맨 오른쪽에 붙이고, 남는 공간을 가운데 아이템 사이에 똑같이 나눈다. 양 끝에는 여백이 없다.
space-around — 아이템마다 좌우에 같은 크기의 여백을 준다. 그래서 아이템과 아이템 사이 간격은 양 끝 여백의 두 배가 된다.
space-evenly — 양 끝 여백과 아이템 사이 간격을 모두 똑같이 맞춘다. space-around보다 더 고르게 보인다.
3.6. align-items (교차축 정렬)
아이템들을 교차축(컨테이너 높이) 방향에서 위·아래·가운데 중 어디에 맞출지 정하는 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>플렉스 박스 레이아웃</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.container {
width:100%;
height:150px;
display:flex;
justify-content: flex-start; /* 주축 정렬 방법 - 시작점 기준 */
background-color:#eee;
border:1px solid #222;
margin-bottom:20px;
}
.box {
padding:5px 45px;
margin:5px;
width:80px;
}
.box:nth-child(odd) {
background-color: rgb(255, 187, 0);
}
.box:nth-child(even) {
background-color: rgb(35, 220, 35);
}
p {
text-align: center;
font-weight: bold;
}
/* #opt4 베이스라인 정렬을 보이기 위한 글자 크기 차이 */
#opt4 .box:nth-child(2) p { font-size: 14px; }
#opt4 .box:nth-child(3) p { font-size: 25px; }
#opt4 .box:nth-child(4) p { font-size: 10px; }
/* 교차축 정렬 방법을 지정하는 #opt1 ~ #opt5 스타일 만들기 */
</style>
</head>
<body>
<div class="container" id="opt1">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
<div class="container" id="opt2">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
<div class="container" id="opt3">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
<div class="container" id="opt4">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
<div class="container" id="opt5">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
</body>
</html>
.container {
display: flex;
justify-content: flex-start;
}
#opt1 { align-items: flex-start; }
#opt2 { align-items: flex-end; }
#opt3 { align-items: center; }
#opt4 { align-items: baseline; }
#opt5 { align-items: stretch; }
#opt4 .box:nth-child(2) p { font-size: 14px; }
#opt4 .box:nth-child(3) p { font-size: 25px; }
#opt4 .box:nth-child(4) p { font-size: 10px; }
[!INFO]
flex-start / flex-end / center— 컨테이너 높이를 기준으로 아이템을 각각 위·아래·가운데에 맞춘다. 주축 정렬이 가로라면 교차축 정렬은 세로 방향을 다룬다.baseline — 아이템 안에 든 글자의 밑줄(기준선)을 서로 맞춘다. 글자 크기가 제각각이어도 글줄이 가지런해 보인다.
stretch — 기본값이다. 아이템에 높이를 지정하지 않으면 컨테이너 높이만큼 위아래로 늘려 가득 채운다.
3.7. align-self (개별 아이템만 따로 정렬)
컨테이너 전체 정렬과 다르게, 특정 아이템 하나만 교차축에서 다르게 배치하는 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>플렉스 박스 레이아웃</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.container {
width:450px;
height:150px;
background-color:#eee;
border:1px solid #222;
margin-bottom:20px;
display:flex; /* 플렉스 컨테이너 지정 */
align-items: center; /* 교차축의 중앙에 배치 */
}
.box {
padding:5px 45px;
margin:5px;
width:80px;
}
.box:nth-child(odd) {
background-color: rgb(255, 187, 0);
}
.box:nth-child(even) {
background-color: rgb(35, 220, 35);
}
p {
text-align: center;
font-weight: bold;
}
/* 1번 상자는 교차축 시작점에, 3번 상자는 교차축에 가득 차게 배치하는 #box1, #box3 스타일 만들기 */
</style>
</head>
<body>
<div class="container">
<div class="box" id="box1"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box" id="box3"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
</body>
</html>
.container {
display: flex;
align-items: center;
}
#box1 { align-self: flex-start; }
#box3 { align-self: stretch; }
[!INFO]
align-items: center— 컨테이너에 지정하면 모든 아이템이 교차축 가운데에 모인다. 이것이 전체 기본 정렬이 된다.
align-self: flex-start— box1 하나에만 지정한다. 컨테이너 정렬과 상관없이 이 아이템만 교차축 시작점(위)으로 올린다.
align-self: stretch— box3에만 지정해 이 아이템만 컨테이너 높이만큼 늘린다. align-self는 한 아이템만 예외로 두고 싶을 때 쓴다.
3.8. align-content (여러 줄일 때 줄 묶음 정렬)
flex-wrap: wrap으로 줄이 여러 개 생겼을 때, 그 줄 묶음 전체를 교차축에서 어떻게 배치할지 정하는 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>플렉스 박스 레이아웃</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.container {
float:left;
width:300px;
height:300px;
display:flex; /* 플렉스 컨테이너 지정 */
flex-flow: row wrap; /* 왼쪽에서 오른쪽, 여러 줄 표시 */
border:1px solid #222;
background-color:#eee;
margin:30px;
}
.box {
padding:10px;
margin:5px;
width: 80px;
}
.box:nth-child(odd) {
background-color: rgb(255, 187, 0);
}
.box:nth-child(even) {
background-color: rgb(35, 220, 35);
}
p {
text-align: center;
font-weight: bold;
}
/* align-content 값을 다양하게 적용하는 #opt1 ~ #opt6 스타일 만들기 */
</style>
</head>
<body>
<div class="container" id="opt1">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
<div class="container" id="opt2">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
<div class="container" id="opt3">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
<div class="container" id="opt4">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
<div class="container" id="opt5">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
<div class="container" id="opt6">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
</div>
</body>
</html>
.container {
display: flex;
flex-flow: row wrap;
}
#opt1 { align-content: flex-start; }
#opt2 { align-content: flex-end; }
#opt3 { align-content: center; }
#opt4 { align-content: space-between; }
#opt5 { align-content: space-around; }
#opt6 { align-content: stretch; }
[!INFO]
align-content — 줄이 두 줄 이상일 때만 동작한다. align-items가 한 줄 안에서 아이템을 정렬한다면, align-content는 여러 줄 묶음 전체를 교차축에서 정렬한다.
flex-start / flex-end / center— 줄 묶음을 각각 위·아래·가운데에 몬다.
space-between / space-around— 줄과 줄 사이 간격을 고르게 나눈다. space-between은 양 끝 줄을 위아래 끝에 붙이고, space-around는 각 줄 둘레에 같은 여백을 준다.
3.9. gap (아이템 사이 간격)
아이템 사이에만 깔끔하게 간격을 주는 예제다. margin과 달리 바깥 가장자리에는 여백이 생기지 않는다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>플렉스 박스 레이아웃</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
/* 플렉스 항목 간의 간격을 10px로 적용하기 */
.container {
width:650px;
height:150px;
display:flex;
align-items:center;
background-color:#eee;
border:1px solid #222;
}
.box {
padding: 10px;
width: 80px;
}
.box:nth-child(odd) {
background-color: rgb(255, 187, 0);
}
.box:nth-child(even) {
background-color: rgb(35, 220, 35);
}
p {
text-align: center;
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<div class="box"><p>1</p></div>
<div class="box"><p>2</p></div>
<div class="box"><p>3</p></div>
<div class="box"><p>4</p></div>
<div class="box"><p>5</p></div>
</div>
</body>
</html>
.container {
gap: 10px;
}
[!INFO]
gap: 10px— 아이템과 아이템 사이에만 10px 간격을 준다. 컨테이너 안쪽 가장자리(맨 앞·맨 뒤)에는 여백이 생기지 않아 정렬이 깔끔하게 유지된다.margin 대신 gap — 예전에는 .box에 margin을 줘서 간격을 만들었지만, 그러면 양 끝에도 여백이 생긴다. gap은 컨테이너 한 곳에만 적어도 사이 간격만 정확히 만들어 준다.
3.10. flex-basis (아이템 기본 크기)
아이템 너비를 콘텐츠 길이가 아니라 지정한 기본 크기로 똑같이 맞추는 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>플렉스 박스 레이아웃</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.container {
width: 800px;
display:flex;
border:1px solid #222;
background-color:#eee;
gap: 30px;
}
/* flex-basis 속성을 사용해 플렉스 항목의 기본 크기 지정하기 */
.box {
background-color:#222;
}
p {
color:#fff;
text-align: center;
font-size: 2rem;
}
</style>
</head>
<body>
<div class="container">
<div class="box"><p>가나다라마</p></div>
<div class="box"><p>가나다라마바사</p></div>
<div class="box"><p>ABCDE</p></div>
</div>
</body>
</html>
.box {
flex-basis: 150px;
}
[!INFO]
flex-basis: 150px— 아이템의 기본 너비를 150px로 잡는다. 글자 수가 적든 많든 모든 아이템이 같은 기본 폭에서 출발한다.width와의 차이 — flex-basis는 주축 방향 기본 크기를 정하는 플렉스 전용 속성이다. flex-grow·flex-shrink와 함께 동작해 남거나 모자란 공간을 어떻게 나눌지의 출발점이 된다.
3.11. flex-grow (남는 공간 나눠 갖기)
컨테이너에 빈 공간이 남을 때, 어떤 아이템이 그 공간을 얼마나 더 차지할지 비율로 정하는 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>플렉스 박스 레이아웃</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.container {
width:800px;
height:100px;
display:flex;
border:1px solid #222;
gap: 10px;
}
.box {
flex-basis: 150px;
}
.box:nth-child(odd) {
background-color: #ffc400;
}
.box:nth-child(even) {
background-color: #a6ff00;
}
p {
font-weight: bold;
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<div class="box" id="box1"><p>1</p></div>
<div class="box" id="box2"><p>2</p></div>
<div class="box" id="box3"><p>3</p></div>
<div class="box" id="box4"><p>4</p></div>
</div>
</body>
</html>
#box1, #box2 { flex-grow: 1; }
#box4 { flex-grow: 2; }
[!INFO]
flex-grow: 1— box1과 box2에 지정한다. 컨테이너에 남는 빈 공간이 있을 때 그 공간을 나눠 갖겠다는 뜻이다. 값이 0(기본값)이면 늘어나지 않는다.
flex-grow: 2— box4에 지정한다. box1·box2(각 1)보다 두 배 더 많은 빈 공간을 가져간다. box3은 값이 없어(0) 기본 크기 그대로 머문다.비율로 동작 — flex-grow는 절대 크기가 아니라 "남는 공간을 1:1:2로 나눈다"는 비율 개념이다.
3.12. flex-shrink (모자랄 때 줄이는 비율)
아이템들의 너비 합이 컨테이너보다 클 때, 어떤 아이템을 얼마나 더 줄일지 비율로 정하는 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>플렉스 박스 레이아웃</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.container {
width:800px;
height:100px;
display:flex;
border:1px solid #222;
gap: 10px;
}
.box {
flex-basis: 300px;
}
.box:nth-child(odd) {
background-color: #ffc400;
}
.box:nth-child(even) {
background-color: #a6ff00;
}
p {
font-weight: bold;
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<div class="box" id="box1"><p>1</p></div>
<div class="box" id="box2"><p>2</p></div>
<div class="box" id="box3"><p>3</p></div>
<div class="box" id="box4"><p>4</p></div>
</div>
</body>
</html>
#box1 { flex-shrink: 1; }
#box2 { flex-shrink: 0; }
#box3 { flex-shrink: 1; }
#box4 { flex-shrink: 2; }
[!INFO]
flex-shrink: 1— box1·box3에 지정한다. 기본값이며, 공간이 모자라면 함께 줄어든다.
flex-shrink: 0— box2에 지정한다. 플렉스 축소 공간을 배분할 때 이 아이템은 줄어들지 않고 기본 크기 300px을 유지한다.
flex-shrink: 2— box4에 지정한다. 다른 아이템보다 두 배 더 많이 줄어든다. flex-shrink는 flex-grow의 반대로, 공간이 부족할 때 줄어드는 비율을 정한다.
3.13. margin auto (한 아이템만 밀어내기)
margin: auto가 남는 공간을 모두 먹어 한 아이템만 반대쪽으로 밀어내는 정렬 기법 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>플렉스 박스 레이아웃</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.container {
width:700px;
display:flex; /* 플렉스 컨테이너 지정 */
justify-content: flex-end; /* 주축 정렬 - 끝점에 맞춰서 */
background-color:#eee;
border:1px solid #222;
margin-bottom:30px;
}
.box {
padding:5px 45px;
margin:5px;
width:80px;
}
.box:nth-child(odd) {
background-color: rgb(255, 187, 0);
}
.box:nth-child(even) {
background-color: rgb(35, 220, 35);
}
p {
text-align: center;
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<div class="box" id="box1"><p>1</p></div>
<div class="box" id="box2"><p>2</p></div>
<div class="box" id="box3"><p>3</p></div>
</div>
</body>
</html>
#box1 {
margin-right: auto;
}
[!INFO]
justify-content: flex-end— 평소에는 세 박스가 모두 오른쪽 끝에 붙어 있다.
margin-right: auto— box1 오른쪽에 "남는 공간을 전부" 채워 넣는다. 그 결과 box1만 왼쪽 시작점으로 밀려나고, 나머지 두 박스는 오른쪽에 그대로 남는다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>플렉스 박스 레이아웃</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.container {
width:700px;
display:flex; /* 플렉스 컨테이너 지정 */
justify-content: flex-end; /* 주축 정렬 - 끝점에 맞춰서 */
background-color:#eee;
border:1px solid #222;
margin-bottom:30px;
}
.box {
padding:5px 45px;
margin:5px;
width:80px;
}
.box:nth-child(odd) {
background-color: rgb(255, 187, 0);
}
.box:nth-child(even) {
background-color: rgb(35, 220, 35);
}
p {
text-align: center;
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<div class="box" id="box1"><p>1</p></div>
<div class="box" id="box2"><p>2</p></div>
<div class="box" id="box3"><p>3</p></div>
</div>
</body>
</html>
#box3 {
margin-left: auto;
}
[!INFO]
margin-left: auto— box3 왼쪽에 남는 공간을 전부 채워, 앞의 두 박스는 왼쪽에 모이고 box3만 오른쪽 끝으로 밀려난다. 메뉴바에서 로고는 왼쪽, 로그인 버튼은 오른쪽에 붙이는 흔한 패턴이 이렇게 만들어진다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>플렉스 박스 레이아웃</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.container {
width: 200px;
height:500px;
display:flex; /* 플렉스 컨테이너 지정 */
flex-direction: column; /* 주축의 방향 - 세로 */
justify-content: flex-start; /* 주축 정렬 방법 - 시작점 기준 */
background-color:#eee;
border:1px solid #222;
margin-bottom:30px;
}
.box {
padding:5px 45px;
margin:5px;
}
.box:nth-child(odd) {
background-color: rgb(255, 187, 0);
}
.box:nth-child(even) {
background-color: rgb(35, 220, 35);
}
p {
text-align: center;
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<div class="box" id="box1"><p>1</p></div>
<div class="box" id="box2"><p>2</p></div>
<div class="box" id="box3"><p>3</p></div>
</div>
</body>
</html>
#box1 {
margin-bottom: auto;
}
[!INFO]
flex-direction: column— 주축을 세로로 바꿔 박스를 위에서 아래로 쌓는다. 이때 margin auto도 세로 방향으로 동작한다.
margin-bottom: auto— box1 아래에 남는 세로 공간을 전부 채운다. 그 결과 box1만 맨 위에 남고, 나머지 박스는 아래로 내려간다. 세로 메뉴에서 첫 항목만 위에 띄울 때 쓴다.
3.14. flex 화면 중앙 배치
요소 하나를 화면 한가운데에 정확히 놓는, flex의 가장 많이 쓰이는 활용 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>버튼을 화면 중앙에 배치하기</title>
<style>
/* 버튼을 화면 중앙에 배치하기 */
* {
margin:0;
box-sizing: border-box;
}
body {
background:url('images/bg5.jpg') no-repeat left top fixed;
background-size:cover;
}
button {
background-color:#ccc;
font-size: 1.2em;
padding:1em 2em;
border:none;
border-radius:5px;
box-shadow:1px 1px 2px #fff;
}
</style>
</head>
<body>
<button>클릭!</button>
</body>
</html>
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
[!INFO]
display: flex— body 자체를 플렉스 컨테이너로 만들어, 안에 든 버튼 하나를 정렬 대상으로 삼는다.
justify-content: center; align-items: center— 가로·세로 두 축 모두 가운데로 맞춘다. 두 줄만으로 요소가 화면 정중앙에 놓인다.
min-height: 100vh— vh는 화면 높이의 1%를 뜻하는 단위다. 100vh는 화면 전체 높이다. body 높이를 화면 높이만큼 확보해야 세로 중앙 정렬이 화면 한가운데로 보인다.
4. grid
설명
가로 열과 세로 행을 함께 만들어 표처럼 칸을 구성하는 레이아웃 방법이다. 페이지의 큰 구조나 사진 갤러리처럼 가로와 세로 배치를 동시에 조절할 때 사용한다.
| 용어 / 속성 | 설명 |
|---|---|
display: grid |
컨테이너를 그리드로 선언 |
grid-template-columns / rows |
열·행의 개수와 크기 정의 |
gap |
행·열(그리드 아이템) 사이의 간격 |
fr / repeat() / minmax() |
비율 단위·반복 지정·최소~최대 크기 함수 |
grid-column / grid-row |
아이템이 차지할 칸을 라인 번호로 지정 |
grid-template-areas |
영역에 이름을 붙여 설계도처럼 배치 |
먼저 열과 행으로 기본 격자를 만든다. 그다음 라인 번호로 칸을 합치고, 영역 이름으로 페이지 구조를 배치한다.
자세한 내용은 🔗MDN 참고.
4.1. 열·행 직접 지정 (grid-template-columns / rows)
격자의 열 개수·너비와 행 개수·높이를 직접 적어 칸을 만드는 가장 기본 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>grid 열과 행</title>
<style>
.items {
border: 1px solid #222;
padding: 10px;
font-size: 20px;
text-align: center;
}
.items:nth-child(even) {
background-color: #ffc400;
}
</style>
</head>
<body>
<div class="container">
<div class="items">1</div>
<div class="items">2</div>
<div class="items">3</div>
<div class="items">4</div>
<div class="items">5</div>
<div class="items">6</div>
</div>
</body>
</html>
.container {
display: grid;
grid-template-columns: 100px 200px 300px;
grid-template-rows: 50px 100px;
}
[!INFO]
display: grid— .container를 그리드 컨테이너로 선언한다. 안의 .items 6개가 자동으로 격자 칸에 들어간다.
grid-template-columns: 100px 200px 300px— 값을 띄어 쓴 개수만큼 열이 만들어진다. 여기서는 100px, 200px, 300px 너비의 열 3개가 생긴다.
grid-template-rows: 50px 100px— 같은 방식으로 50px, 100px 높이의 행 2개를 만든다. 아이템이 6개이므로 3열 × 2행에 딱 맞게 채워진다.
4.2. grid-auto-rows (자동 행 높이)
행 높이를 일일이 적지 않고, 새로 생기는 행마다 같은 높이를 자동으로 적용하는 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CSS Grid Layout</title>
<style>
/* 그리드 컨테이너에서 열과 행 지정하기 */
.container {
display:grid;
grid-template-columns: 100px 200px 300px;
}
.items{
border: 1px solid #222;
padding:10px;
font-size: 20px;
text-align: center;
}
.items:nth-child(even) {
background-color:#ffc400;
}
</style>
</head>
<body>
<div class="container">
<div class="items">1</div>
<div class="items">2</div>
<div class="items">3</div>
<div class="items">4</div>
<div class="items">5</div>
<div class="items">6</div>
</div>
</body>
</html>
.container {
grid-auto-rows: 100px;
}
[!INFO]
grid-auto-rows: 100px— grid-template-rows로 행 높이를 직접 정하지 않아도, 아이템이 넘쳐 새 행이 생길 때마다 그 행 높이를 100px로 자동 적용한다.언제 쓰나 — 아이템 개수가 몇 개일지 미리 모를 때 유용하다. 행이 몇 줄로 늘어나든 높이가 일정하게 유지된다.
4.3. minmax로 행과 열의 범위 정하기
minmax(최솟값, 최댓값)은 그리드의 행이나 열이 최소 얼마까지 줄어들고, 최대 얼마까지 늘어날지 함께 정한다.
grid-template-columns, grid-template-rows, grid-auto-columns, grid-auto-rows처럼 트랙 크기를 정하는 속성에 넣는다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CSS Grid Layout</title>
<style>
.container {
width: 600px;
border: 1px solid #ccc;
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.items {
padding: 10px;
font-size: 16px;
}
.items:nth-child(odd) {
background-color: #ffc400;
}
</style>
</head>
<body>
<div class="container">
<div class="items">짧은 내용</div>
<div class="items">두 줄 이상으로 늘어날 수 있는 조금 긴 내용이다.</div>
<div class="items">짧은 내용</div>
<div class="items">내용이 길어지면 100px을 넘어 필요한 높이만큼 행이 늘어난다. 여러 문장을 넣어 실제 높이 변화를 확인한다.</div>
<div class="items">짧은 내용</div>
<div class="items">중간 길이의 내용이다.</div>
</div>
</body>
</html>
.container {
grid-auto-rows: minmax(100px, auto);
}
[!INFO]
grid-auto-rows: minmax(100px, auto)는 새로 생기는 행의 최소 높이를 100px로 잡는다.내용이 짧으면 행 높이를 100px로 유지한다. 내용이 길면
auto가 내용 높이에 맞춰 행을 늘린다.
minmax()는 행 높이 전용 함수가 아니다. 열 너비와 자동 생성 열에도 같은 문법을 사용한다.
행 높이를 일정 범위로 묶는 예제다. 카드 목록처럼 내용 길이가 들쭉날쭉한 화면에서 많이 쓴다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>행 높이 범위</title>
<style>
.cards {
display: grid;
grid-auto-rows: minmax(120px, auto);
gap: 16px;
}
.card {
padding: 16px;
border: 1px solid #222;
}
</style>
</head>
<body>
<div class="cards">
<div class="card">짧은 내용</div>
<div class="card">내용이 길어지면 120px을 넘어 필요한 높이만큼 행이 늘어난다. 여러 문장을 넣어 높이 변화를 확인한다.</div>
</div>
</body>
</html>
[!INFO]
각 행은 최소 120px을 확보한다.
짧은 내용은 120px 안에서 끝난다.
긴 내용은 내용 높이만큼 행을 더 늘린다.
사이드바와 본문처럼 열의 폭을 제한해야 할 때도 쓴다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>열 너비 범위</title>
<style>
.layout {
display: grid;
grid-template-columns: minmax(180px, 260px) 1fr;
gap: 20px;
}
.side,
.main {
padding: 16px;
border: 1px solid #222;
}
</style>
</head>
<body>
<div class="layout">
<aside class="side">사이드바</aside>
<main class="main">본문</main>
</div>
</body>
</html>
[!INFO]
첫 번째 열은 180px보다 좁아지지 않고 260px보다 넓어지지 않는다.
두 번째 열의
1fr은 첫 번째 열과 간격을 제외한 나머지 너비를 채운다.왼쪽 메뉴는 일정 폭으로 묶고 오른쪽 본문은 남는 공간을 모두 쓰게 할 때 알맞다.
화면이 좁아져도 왼쪽 폭이 너무 작아지지 않게 막을 수 있다.
반응형 카드에서는 repeat()와 함께 사용한다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>반응형 카드</title>
<style>
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 16px;
}
.card {
padding: 16px;
border: 1px solid #222;
}
</style>
</head>
<body>
<div class="cards">
<div class="card">카드 1</div>
<div class="card">카드 2</div>
<div class="card">카드 3</div>
<div class="card">카드 4</div>
</div>
</body>
</html>
[!INFO]
각 열은 최소 180px을 유지한다.
들어갈 수 있는 칸이 더 생기면 열 개수가 늘어난다.
남는 공간은
1fr이 각 열에 나눠 가져서 카드가 같은 너비로 늘어난다.화면이 좁으면 열 개수가 줄고, 넓으면 열 개수가 늘어난다.
자동으로 생기는 열에도 같은 문법을 쓴다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>자동 생성 열</title>
<style>
.timeline {
display: grid;
grid-auto-flow: column;
grid-auto-columns: minmax(140px, 1fr);
gap: 12px;
overflow-x: auto;
}
.step {
padding: 16px;
border: 1px solid #222;
}
</style>
</head>
<body>
<div class="timeline">
<div class="step">기획</div>
<div class="step">설계</div>
<div class="step">개발</div>
<div class="step">검토</div>
</div>
</body>
</html>
[!INFO]
새로 생기는 열은 최소 140px을 확보한다.
내용이 짧으면 그 폭 안에서 끝난다.
내용이 길면 열이 1fr 범위 안에서 더 넓어진다.
가로로 계속 늘어나는 일정표, 단계표, 타임라인에 잘 맞는다.
[!NOTE]
minmax()는 일반 요소의width나height에 직접 쓰지 않는다.일반 요소의 크기를 최소·권장·최대 범위로 제한할 때는
clamp()나min-width,max-width를 사용한다.
4.4. auto-fit / auto-fill (칸 개수 자동 채움)
repeat()는 같은 트랙 크기를 반복한다. 반복 횟수에 숫자를 넣으면 정해진 개수만 만들고, auto-fit이나 auto-fill을 넣으면 컨테이너 너비에 맞춰 개수를 계산한다.
/* 1fr 열을 정확히 3개 만든다. */
grid-template-columns: repeat(3, 1fr);
/* 들어갈 수 있는 만큼 열을 만든다. */
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
첫 번째 코드는 항상 3열을 유지한다. 두 번째 코드는 각 열의 최소 너비 100px을 지키면서 화면 너비에 맞춰 열 개수를 바꾼다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CSS Grid Layout</title>
<style>
.container {
width: min(100%, 800px);
box-sizing: border-box;
border: 1px solid #ccc;
display:grid;
margin-bottom: 20px;
}
h1 {
font-size:24px;
}
.items{
border: 1px solid #222;
padding:10px;
font-size: 20px;
text-align: center;
}
.items:nth-child(odd){
background-color:#d6faff;
}
.items:nth-child(even){
background-color:#00b7ff;
}
</style>
</head>
<body>
<h1>auto-fit일 때</h1>
<div class="container container-1">
<div class="items">1</div>
<div class="items">2</div>
<div class="items">3</div>
<div class="items">4</div>
<div class="items">5</div>
</div>
<h1>auto-fill일 때</h1>
<div class="container container-2">
<div class="items">1</div>
<div class="items">2</div>
<div class="items">3</div>
<div class="items">4</div>
<div class="items">5</div>
</div>
</body>
</html>
.container-1 {
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
}
.container-2 {
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
[!INFO]
repeat(auto-fit, minmax(100px, 1fr))— 열 개수를 숫자로 적지 않고 "100px 이상 들어갈 수 있는 만큼" 자동으로 만든다. 화면이 넓으면 열이 늘고 좁으면 준다.
auto-fit은 비어 있는 열을 접고 남는 너비를 실제 아이템이 나눠 갖게 한다.
auto-fill은 아이템이 없어도 들어갈 수 있는 열 자리를 유지한다.카드가 컨테이너 너비를 채우게 하려면
auto-fit, 빈 열 자리까지 유지하려면auto-fill을 사용한다.
4.5. gap (행·열 간격 따로 주기)
격자 칸 사이 간격을 행 방향·열 방향 따로 지정하는 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CSS Grid Layout</title>
<style>
/* 그리드 항목 사이에 간격 지정하기 */
.container {
width: 600px;
border: 2px solid #222;
display:grid;
grid-template-columns:repeat(3, 1fr); /* 열 3개 */
}
.items{
padding:10px;
font-size: 20px;
text-align: center;
}
.items:nth-child(odd){
background-color:#d6faff;
}
.items:nth-child(even){
background-color:#00b7ff;
}
</style>
</head>
<body>
<div class="container">
<div class="items">1</div>
<div class="items">2</div>
<div class="items">3</div>
<div class="items">4</div>
<div class="items">5</div>
<div class="items">6</div>
</div>
</body>
</html>
.container {
gap: 20px 30px;
}
[!INFO]
gap: 20px 30px— 값을 두 개 적으면 앞은 행 사이(위아래) 간격 20px, 뒤는 열 사이(좌우) 간격 30px이 된다.값을 하나만 적으면 —
gap: 20px처럼 한 값만 쓰면 행·열 간격이 모두 20px로 똑같이 적용된다.
4.6. grid-column / grid-row (라인 번호로 칸 합치기)
특정 아이템이 여러 칸을 가로지르도록 시작·끝 라인 번호를 지정해 자유로운 골격을 만드는 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CSS Grid Layout</title>
<style>
/* 그리드 레이아웃 지정하기 */
.container{
width:700px;
display:grid;
grid-template-columns:repeat(3, 1fr);
grid-template-rows:repeat(3, 100px);
gap: 1rem;
}
.box{
padding:15px;
color:#fff;
text-align:center;
}
/* 그리드 라인을 사용해 배치하기 */
.box1 {
background-color:#3689ff;
}
.box2 {
background-color:#00cf12;
}
.box3 {
background-color:#ff9019;
}
.box4 {
background-color:#ffd000;
}
.box5 {
background-color:#ff3f3f;
}
</style>
</head>
<body>
<div class="container">
<div class="box box1">box1</div>
<div class="box box2">box2</div>
<div class="box box3">box3</div>
<div class="box box4">box4</div>
<div class="box box5">box5</div>
</div>
</body>
</html>
.box1 { grid-column: 1 / -1; grid-row-start: 1; }
.box2 { grid-column-start: 1; grid-row: 2 / -1; }
.box3 { grid-column: 2 / -1; grid-row-start: 2; }
.box4 { grid-column-start: 2; grid-row-start: 3; }
.box5 { grid-column: 3 / -1; grid-row: 3 / -1; }
[!INFO]
라인 번호란 — 그리드는 칸을 나누는 선마다 1, 2, 3… 번호가 매겨진다. 열이 3개면 세로선은 1번부터 4번까지 있다.
grid-column: 1 / -1— "1번 선에서 시작해 마지막(-1) 선까지" 차지하라는 뜻이다.-1은 항상 맨 끝 선을 가리켜, 열이 몇 개든 한 행 전체를 가로지르게 한다.
grid-row: 2 / -1— 행도 같은 방식이다. 2번 가로선부터 맨 끝 선까지 세로로 차지한다.
grid-column-start: 2— 끝을 적지 않으면 한 칸만 차지한다. 시작 라인만 지정해 그 자리에 한 칸을 놓는다.
4.7. 영역 이름으로 페이지 구조 배치
열과 행, 라인 번호를 익힌 뒤 각 영역에 이름을 붙여 페이지 구조를 배치한다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>grid 영역 배치</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
header, nav, main, footer { padding: 20px; }
header { background: #f99; }
nav { background: #9f9; }
main { background: #99f; }
footer { background: #ff9; }
</style>
</head>
<body>
<div class="grid-layout">
<header>Header</header>
<nav>Nav</nav>
<main>Main</main>
<footer>Footer</footer>
</div>
</body>
</html>
.grid-layout {
display: grid;
min-height: 300px;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header"
"nav main main"
"footer footer footer";
gap: 10px;
}
header { grid-area: header; }
nav { grid-area: nav; }
main { grid-area: main; }
footer { grid-area: footer; }
[!INFO]
display: grid— .grid-layout을 그리드 컨테이너로 선언한다.
min-height: 300px— 페이지 구조를 확인할 수 있도록 컨테이너의 최소 높이를 확보한다.
grid-template-columns: 1fr 1fr 1fr— 사용할 너비를 같은 비율의 열 3개로 나눈다.
grid-template-rows: auto 1fr auto— 머리말과 바닥글은 내용 높이를 사용하고, 가운데 행은 남는 높이를 채운다.
grid-template-areas— 영역 이름을 문자열로 배치해 페이지 구조를 설계도처럼 나타낸다. header, nav, main, footer의 위치를 한눈에 확인할 수 있다.
grid-area— 각 자식 요소를grid-template-areas에 적은 영역 이름과 연결한다.
gap: 10px— 그리드 아이템 사이에 10px 간격을 만든다.
4.8. grid 이미지 갤러리
grid-template-areas로 크기가 다른 사진 칸을 설계도처럼 배치해 갤러리를 만드는 활용 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CSS Grid Layout</title>
<style>
.gallery{
width:700px;
margin: 20px auto;
}
.gallery img{
width:100%;
height:100%;
border-radius: 10px;
}
</style>
</head>
<body>
<div class="gallery">
<img class="photo1" src="images/photo-1.jpg" alt="따뜻한 차가 있는 겨울 풍경">
<img class="photo2" src="images/photo-2.jpg" alt="남극 펭귄">
<img class="photo3" src="images/photo-3.jpg" alt="눈이 가득 쌓인 시골 풍경">
<img class="photo4" src="images/photo-4.jpg" alt="오로라가 보이는 밤 풍경">
<img class="photo5" src="images/photo-5.jpg" alt="눈 쌓인 배경">
<img class="photo6" src="images/photo-6.jpg" alt="나뭇가지에 앉아있는 빨간 새">
</div>
</body>
</html>
.gallery {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 180px);
grid-template-areas:
"photo1 photo1 photo2"
"photo1 photo1 photo3"
"photo4 photo5 photo6";
gap: 5px;
}
.gallery img { object-fit: cover; }
.photo1 { grid-area: photo1; }
.photo2 { grid-area: photo2; }
.photo3 { grid-area: photo3; }
.photo4 { grid-area: photo4; }
.photo5 { grid-area: photo5; }
.photo6 { grid-area: photo6; }
[!INFO]
grid-template-columns와grid-template-rows— 너비가 같은 열 3개와 높이 180px인 행 3개를 만든다.
grid-template-areas— 같은 이름을 여러 칸에 적으면 그 칸들이 하나로 합쳐진다. photo1이 가로 2칸·세로 2칸에 적혀 있어 큰 사진 한 장 자리가 만들어진다.
각 .photoN { grid-area }— 이미지마다 영역 이름을 연결해 설계도에 적은 자리에 들어가게 한다. 문자열만 고치면 레이아웃을 바로 바꿀 수 있어 직관적이다.
object-fit: cover— 사진 비율이 칸과 달라도 칸을 빈틈없이 채우도록 잘라 맞춘다. 갤러리 칸 크기가 제각각이어도 사진이 찌그러지지 않는다.
4.9. 반응형 grid (미디어 쿼리로 한 줄 쌓기)
큰 화면에서는 격자로 보이다가, 화면이 좁아지면 칸을 한 줄로 풀어 세로로 쌓는 반응형 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CSS Grid Layout</title>
<style>
body {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: min(100%, 1000px);
margin:10px auto;
padding: 10px;
box-sizing: border-box;
}
.item {
border-radius: 5px;
font-size: 2em;
}
.item-1 {
background-color: #e91e63;
}
.item-2{
background-color: #ff9800;
}
.item-3{
background-color: #ab47bc;
}
.item-4{
background-color: #38cf44;
}
.item-5{
background-color: #546e7a;
}
</style>
</head>
<body>
<div class="container">
<div class="item item-1">1</div>
<div class="item item-2">2</div>
<div class="item item-3">3</div>
<div class="item item-4">4</div>
<div class="item item-5">5</div>
</div>
</body>
</html>
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 250px);
gap: 10px;
}
.item-1 { grid-column: 1 / 2; grid-row: 1 / 3; }
.item-2 { grid-column: 2 / 4; grid-row: 1 / 2; }
.item-3 { grid-column: 2 / 3; grid-row: 2 / 3; }
.item-4 { grid-column: 3 / 4; grid-row: 2 / 3; }
.item-5 { grid-column: 1 / 4; grid-row: 3 / 4; }
@media (max-width: 768px) {
.container { display: block; }
.item {
height: 200px;
margin-bottom: 10px;
}
}
[!INFO]
넓은 화면 — grid-column·grid-row로 아이템마다 차지할 칸을 정해 크기가 다른 5개 블록이 격자로 짜인다.
@media (max-width: 768px) { display: block }— 화면이 768px 이하로 좁아지면 그리드를 풀고(block) 아이템을 위에서 아래로 한 줄씩 쌓는다. 좁은 화면에서 가로 격자는 보기 불편하기 때문이다.
width: min(100%, 1000px)— 화면이 1000px보다 넓으면 최대 1000px을 사용하고, 화면이 더 좁으면 화면 너비를 넘지 않는다. 고정 너비 때문에 생기는 가로 스크롤을 방지한다.
5. calc
설명
calc()는 CSS 값 안에서 서로 다른 단위의 값을 계산한다. 너비뿐 아니라 높이, 글자 크기, 여백처럼 숫자와 길이를 받는 여러 속성에 사용할 수 있다.
| 연산자 | 사용 규칙 |
|---|---|
+, - |
연산자 앞뒤로 공백이 있어야 한다. |
* |
최신 Typed Arithmetic 문법이다. 한쪽은 단위가 없는 숫자를 사용한다. |
/ |
최신 Typed Arithmetic 문법이다. 오른쪽에는 단위가 없는 숫자를 사용한다. |
자세한 내용은 🔗MDN 참고.
width: calc(100% - 32px);
padding: calc(1rem + 1vw);
[!WARNING]
+와-는 널리 지원되므로 기본 계산에 우선 사용한다.
*와/는 CSS Values and Units Level 4의 Typed Arithmetic 문법이다. 대상 브라우저의 지원 범위를 확인한 뒤 사용한다.곱셈이 필요하지만 지원 범위가 불분명하면
calc(50px + 50px)처럼 덧셈으로 바꾸거나 계산한 값100px을 직접 적는다.
속성에 따라 다음처럼 계산 목적이 달라진다.
/* 화면 너비에서 사이드바 너비 빼기 */
main {
width: calc(100% - 250px);
}
/* 화면 높이에서 머리말 높이 빼기 */
.content {
min-height: calc(100vh - 64px);
}
/* 기본 글자 크기에 화면 너비 비율 더하기 */
h1 {
font-size: calc(1.5rem + 1vw);
}
/* 고정 여백과 화면 너비 비율 더하기 */
.section {
padding-inline: calc(16px + 2vw);
}
[!INFO]
%와px처럼 단위가 다른 값도 더하거나 뺄 수 있다.
100vh - 64px은 화면 높이에서 고정된 머리말 높이를 뺀다.
1.5rem + 1vw는 기본 글자 크기에 화면 너비에 따른 크기를 더한다.계산 결과를 사용할 수 있는 속성에만 넣는다. 색상 이름이나
display처럼 수치가 아닌 값에는 사용하지 않는다.
5.1. calc로 남는 너비 채우기
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>calc 레이아웃</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
</style>
</head>
<body>
<div class="layout">
<aside>사이드바</aside>
<main>본문 영역</main>
</div>
</body>
</html>
.layout {
display: flex;
}
aside {
width: 250px;
flex-shrink: 0;
}
main {
width: calc(100% - 250px);
}
.layout > * {
box-sizing: border-box;
}
[!INFO]
.layout { display: flex }— aside와 main을 가로로 나란히 배치해 사이드바 + 본문 2단 구조를 만든다.
aside { width: 250px }— 사이드바의 너비를 250px로 고정해 화면 크기와 상관없이 일정한 폭을 유지한다.
main { width: calc(100% - 250px) }— 전체 너비 100%에서 고정 사이드바 250px를 제외한 나머지를 본문이 차지한다.
box-sizing: border-box는 패딩과 테두리를 지정 너비 안에 포함해 전체 너비가 넘치는 문제를 막는다.
6. 미디어 쿼리 및 반응형 패턴
설명
미디어 쿼리는 화면 너비 같은 조건에 따라 CSS를 다르게 적용하는 문법이다. 먼저 조건 작성법과 중단점을 익힌 뒤 반응형 레이아웃 패턴에 적용한다.
| 문법 | 역할 |
|---|---|
@media |
조건에 맞을 때 실행할 CSS 영역을 만든다. |
min-width |
지정한 너비 이상에서 스타일을 적용한다. |
max-width |
지정한 너비 이하에서 스타일을 적용한다. |
and |
두 조건을 모두 만족하는 범위를 만든다. |
screen |
화면 장치를 대상으로 한다. |
width, orientation |
화면 너비나 가로·세로 방향을 조건으로 삼는다. |
print |
인쇄 미리보기와 출력용 스타일을 만든다. |
자세한 내용은 🔗MDN 참고.
6.1. 미디어 쿼리 기본 문법
기본 스타일을 먼저 작성하고 @media 안에 조건을 만족할 때 바꿀 스타일을 적는다.
미디어 쿼리는 “기본값 + 예외 규칙” 구조로 읽으면 쉽다. 화면이 넓을 때의 기본 화면을 먼저 잡고, 좁아질 때 바꿀 부분만 덧씌운다.
HTML의 <head>에는 다음 태그를 넣는다. 모바일 브라우저가 기기 화면 너비를 CSS 화면 너비로 사용하게 한다.
<meta name="viewport" content="width=device-width, initial-scale=1">
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>미디어 쿼리 기본 문법</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.box {
width: 600px;
height: 150px;
background-color: #3689ff;
}
</style>
</head>
<body>
<div class="box"></div>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>미디어 쿼리 기본 문법</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.box {
width: 600px;
height: 150px;
background-color: #3689ff;
}
@media screen and (max-width: 768px) {
.box {
width: 100%;
background-color: #ff9019;
}
}
@media screen and (max-width: 768px) and (orientation: landscape) {
.box {
width: 80%;
background-color: #ffcc66;
}
}
</style>
</head>
<body>
<div class="box"></div>
</body>
</html>
[!INFO]
화면 너비가
769px이상이면 기본 규칙인width: 600px과 파란색 배경을 사용한다.화면 너비가
768px이하이면 미디어 쿼리 안의 규칙이 같은 선택자를 덮어쓴다.
screen and는 화면 장치이면서 너비 조건도 만족할 때 규칙을 적용한다. 화면 너비만 조건으로 삼을 때는@media (max-width: 768px)처럼 짧게 적을 수 있다.
width=device-width는 CSS 화면 너비를 기기 화면 너비에 맞춘다. 이 태그를 넣으면 모바일에서도 지정한 중단점이 의도한 너비에서 동작한다.
orientation: landscape는 화면이 가로로 눕는 순간에만 적용한다. 작은 화면을 가로로 돌렸을 때 글자와 버튼이 너무 넓게 퍼지는 문제를 줄일 수 있다.
[!NOTE]
같은 선택자를 여러 곳에서 적으면 아래쪽 규칙이 위쪽 규칙을 덮는다.
그래서 기본 스타일은 위에, 작은 화면용 수정은 아래에 두는 편이 읽기 쉽다.
orientation같은 조건은 예외 상황에만 추가하고, 핵심 중단점은 너비 기준으로 잡는 편이 관리하기 쉽다.
6.2. min-width와 max-width
min-width는 조건의 시작점을, max-width는 조건의 끝점을 정한다. 두 조건을 and로 연결하면 특정 범위만 선택한다.
max-width는 줄이면서 바꾸는 방식이고, min-width는 넓히면서 덧붙이는 방식이다. 둘 다 쓸 수 있지만 한 문서 안에서는 한쪽 방향으로 통일하는 편이 덜 헷갈린다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>min-width와 max-width</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-size: 2rem; }
</style>
</head>
<body>
<p>브라우저 너비를 바꿔 배경색을 확인하세요.</p>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>min-width와 max-width</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-size: 2rem; }
/* 767px 이하 */
@media screen and (max-width: 767px) {
body { background-color: #fff4d6; }
}
/* 768px 이상 1023px 이하 */
@media screen and (min-width: 768px) and (max-width: 1023px) {
body { background-color: #d6faff; }
}
/* 1024px 이상 */
@media screen and (min-width: 1024px) {
body { background-color: #e7d6ff; }
}
/* 인쇄 */
@media print {
body { background-color: #fff; color: #000; }
}
</style>
</head>
<body>
<p>브라우저 너비를 바꿔 배경색을 확인하세요.</p>
</body>
</html>
[!INFO]
max-width: 767px은 화면 너비가 767px과 같거나 작을 때 적용한다.
min-width: 1024px은 화면 너비가 1024px과 같거나 클 때 적용한다.
768px~1023px처럼 경계를 나누면 같은 너비에서 여러 구간이 겹치는 문제를 막을 수 있다.
@media print는 화면이 아니라 인쇄 출력용 스타일을 따로 적용한다.
[!NOTE]
max-width만 쓰면 큰 화면에서 작은 화면으로 내려가며 규칙을 끼워 넣는 식으로 읽는다.
min-width만 쓰면 작은 화면 기본값 위에 큰 화면용 규칙을 덧붙이는 식으로 읽는다.한 파일 안에서 두 방식을 섞을 수는 있지만, 초보 단계에서는 한쪽만 쓰는 편이 실수가 적다.
6.3. 중단점과 모바일 우선 작성
중단점은 레이아웃을 바꿀 화면 너비이다. 기기 이름이나 특정 제품 크기보다 콘텐츠가 좁아져 읽기 어려워지는 지점을 기준으로 정한다.
모바일 우선 방식은 좁은 화면의 기본 스타일을 먼저 작성하고 min-width로 넓은 화면의 규칙을 추가한다.
중단점은 “기기가 뭐냐”가 아니라 “지금 배치가 버티느냐”를 기준으로 잡는다. 글이 두 줄로 깨지거나 버튼이 눌기 어려워지는 순간이 바꿀 지점이다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>중단점과 모바일 우선</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.card {
padding: 20px;
background-color: #3689ff;
color: #fff;
text-align: center;
}
.cards {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
</style>
</head>
<body>
<div class="cards">
<div class="card">1</div>
<div class="card">2</div>
<div class="card">3</div>
<div class="card">4</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>중단점과 모바일 우선</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.card {
padding: 20px;
background-color: #3689ff;
color: #fff;
text-align: center;
}
.cards {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
@media (min-width: 768px) {
.cards {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1200px) {
.cards {
grid-template-columns: repeat(4, 1fr);
}
}
</style>
</head>
<body>
<div class="cards">
<div class="card">1</div>
<div class="card">2</div>
<div class="card">3</div>
<div class="card">4</div>
</div>
</body>
</html>
[!INFO]
기본 화면에서는 카드가 한 열로 쌓인다.
화면 너비가
768px이상이면 두 열,1200px이상이면 네 열로 바뀐다.
1200px이상에서는768px조건과1200px조건을 모두 만족한다. 같은 속성은 뒤에 적은1200px규칙이 덮어쓰므로 좁은 화면부터 넓은 화면 순서로 작성한다.
[!NOTE]
중단점은
768px,1024px같은 관습적인 값만 따라 정하지 않는다.브라우저 너비를 줄이면서 글자, 버튼, 카드가 겹치거나 지나치게 좁아지는 지점을 확인하고 그 위치를 중단점으로 정한다.
중단점을 너무 많이 만들면 관리가 복잡해진다. 꼭 필요한 지점만 잡는다.
6.4. 패턴 01. Mostly Fluid
넓은 화면에서는 콘텐츠의 최대 너비를 제한해 가운데에 놓고, 좁은 화면에서는 화면 너비를 채우는 패턴이다. 화면이 더 좁아지면 여러 열을 한 열로 바꾼다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Mostly Fluid</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
</style>
</head>
<body>
<div id="wrap">
<div id="content">Content</div>
<div id="aside">Aside</div>
</div>
</body>
</html>
#wrap {
display: flex;
gap: 20px;
width: 100%;
max-width: 960px;
margin: 0 auto;
}
#content { flex: 1 1 62.5%; }
#aside { flex: 1 1 35%; }
@media screen and (max-width: 480px) {
#wrap { flex-direction: column; }
}
[!INFO]
display: flex— 부모#wrap을 플렉스 컨테이너로 지정해 자식 요소를 가로 한 줄로 배치한다.float를 사용하지 않으므로 별도의 해제 처리가 필요 없다.
gap: 20px— 플렉스 아이템 사이에만 20px 간격을 만든다.
flex: 1 1 62.5%—flex-grow,flex-shrink,flex-basis를 한 줄로 쓴 것이다. 처음 너비를 62.5%로 잡고, 공간이 남으면 늘어나며 부족하면 줄어든다.
max-width: 960px; margin: 0 auto— 콘텐츠의 최대 너비를 960px로 제한하고 좌우 여백을 나눠 가운데에 놓는다.화면 너비가
480px이하이면flex-direction: column이 두 영역을 위아래로 쌓는다.
6.5. 패턴 02. Column Drop
여러 열이 한 줄에 들어갈 때는 가로로 놓고, 화면이 좁아지면 열을 아래로 내려 한 줄씩 쌓는 패턴이다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Column Drop</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
</style>
</head>
<body>
<div class="cols">
<div class="box">A</div>
<div class="box">B</div>
<div class="box">C</div>
</div>
</body>
</html>
.cols {
display: flex;
gap: 10px;
}
.box { flex: 1; }
@media screen and (max-width: 600px) {
.cols { flex-direction: column; }
}
[!INFO]
display: flex; gap: 10px— .cols를 플렉스 컨테이너로 지정해 세 상자(A, B, C)를 가로로 나란히 배치하고, 상자 사이 간격을 10px로 준다.
flex: 1— 세 상자가 남는 공간을 같은 비율로 나눠 같은 너비를 갖는다.화면 너비가
600px이하이면flex-direction: column이 가로 배치를 세로로 바꾼다.
6.6. 패턴 03. Layout Shifter
화면 너비에 따라 요소의 위치와 표시 순서를 함께 바꾸는 패턴이다. 좁은 화면에서 중요한 본문을 내비게이션보다 먼저 보여 줄 때 사용할 수 있다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Layout Shifter</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
</style>
</head>
<body>
<div class="shifter">
<nav class="nav">Nav</nav>
<main class="main">Main</main>
</div>
</body>
</html>
.shifter {
display: flex;
gap: 10px;
}
.nav { flex: 1; order: 1; }
.main { flex: 3; order: 2; }
@media screen and (max-width: 600px) {
.shifter { flex-direction: column; }
.main { order: 1; }
.nav { order: 2; }
}
[!INFO]
display: flex; gap: 10px— .shifter를 플렉스 컨테이너로 만들어 nav와 main을 가로로 배치하고, 아이템 사이 간격을 10px로 준다.
flex: 1 / flex: 3— nav와 main의 너비 비율을 1:3으로 나눈다. main이 nav보다 3배 넓은 공간을 차지한다.
order— HTML 작성 순서는 유지하면서 화면에 보이는 순서를 바꾼다.화면 너비가
600px이하이면 세로로 배치하고order값으로 본문(main)을 내비게이션(nav)보다 위에 놓는다.
7. 화면에 맞춰 크기가 변하는 미디어
설명
비디오나 지도 같은 고정 비율의 콘텐츠가 부모 요소의 너비에 맞춰 비율을 유지하며 크기가 변하도록 처리하는 기법이다.
| 핵심 기술 | 역할 |
|---|---|
aspect-ratio |
가로:세로 비율을 직접 지정 (최신 브라우저, 가장 간단) |
padding-bottom |
가로 너비 기준 높이 비율(예: 16:9 = 56.25%) 확보 |
최신 브라우저에서는
aspect-ratio속성으로 비율을 훨씬 간단히 지정할 수 있다.예전 방식인
padding-bottom기법도 함께 알아두면 좋다.자세한 내용은 🔗MDN 참고.
7.1. 16:9 반응형 영상
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>16:9 반응형 영상</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
</style>
</head>
<body>
<div class="video">
<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" title="16대 9 반응형 영상"></iframe>
</div>
</body>
</html>
.video { aspect-ratio: 16 / 9; }
.video iframe {
width: 100%;
height: 100%;
border: 0;
}
[!INFO]
aspect-ratio: 16 / 9— 요소의 가로:세로 비율을 직접 지정한다. 너비가 정해지면 높이가 자동으로 계산되어 별도의 높이 계산 없이 16:9 비율이 유지된다. 최신 브라우저에서 가장 간단한 방식이다.
iframe { width: 100%; height: 100%; border: 0 }— iframe이 aspect-ratio로 확보된 부모 영역을 가득 채우도록 하고, 기본 테두리(border)를 제거해 깔끔한 영상 영역을 만든다.
.video {
position: relative;
width: 100%;
height: 0;
padding-bottom: 56.25%;
}
.video iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
}
[!INFO]
.video { position: relative; padding-bottom: 56.25%; height: 0 }— 예전 브라우저에서 16:9 비율을 만드는 방법이다. 가로 길이를 16으로 볼 때 세로 길이는 9이므로9 ÷ 16 × 100 = 56.25%만큼 아래쪽 공간을 확보한다.
position: relative— 부모 .video를 기준 좌표로 삼아 내부 iframe의 절대 위치(absolute) 기준점이 되도록 한다.
iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100% }— 확보된 비율 공간을 iframe이 빈틈없이 채우므로 부모 크기에 맞춰 영상이 늘어나고 줄어든다.
8. 10장 반응형 보충 예제
8.1. em과 rem
시작 예제와 완료 예제로 부모 글자 크기를 기준으로 계산하는 과정을 확인한다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>상품 소개 페이지</title>
<style>
/* em 단위로 글자 크기 지정하기 */
p {
font-size: 1em;
}
</style>
</head>
<body>
<h1>레드향</h1>
<p>껍질에 붉은 빛이 돌아 레드향이라 불린다.</p>
<div class="content">
<p>레드향은 한라봉과 귤을 교배한 것으로</p>
<p>일반 귤보다 2~3배 크고, 과육이 붉고 통통하다.</p>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>상품 소개 페이지</title>
<style>
/* em 단위로 글자 크기 지정하기 */
.content {
font-size: 1.5em;
}
p {
font-size: 1em;
}
</style>
</head>
<body>
<h1>레드향</h1>
<p>껍질에 붉은 빛이 돌아 레드향이라 불린다.</p>
<div class="content">
<p>레드향은 한라봉과 귤을 교배한 것으로</p>
<p>일반 귤보다 2~3배 크고, 과육이 붉고 통통하다.</p>
</div>
</body>
</html>
브라우저 기본 글자 크기가 16px이라고 가정하면 .content의 1.5em은 24px이다. 그 안의 문단은 1em이므로 .content의 계산된 크기인 24px을 사용한다. 이처럼 em은 부모 크기의 영향을 받으므로 요소가 여러 단계로 겹치면 계산 결과도 계속 달라질 수 있다.
시작 예제와 완료 예제로 문서 루트인 html의 글자 크기를 기준으로 계산하는 과정을 확인한다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>상품 소개 페이지</title>
<style>
/* rem 단위로 글자 크기 지정하기 */
</style>
</head>
<body>
<h1>레드향</h1>
<p>껍질에 붉은 빛이 돌아 레드향이라 불린다.</p>
<div class="content">
<p>레드향은 한라봉과 귤을 교배한 것으로</p>
<p>일반 귤보다 2~3배 크고, 과육이 붉고 통통하다.</p>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>상품 소개 페이지</title>
<style>
/* rem 단위로 글자 크기 지정하기 */
html {
font-size: 16px;
}
.content {
font-size: 1.5rem;
}
p {
font-size: 1rem;
}
</style>
</head>
<body>
<h1>레드향</h1>
<p>껍질에 붉은 빛이 돌아 레드향이라 불린다.</p>
<div class="content">
<p>레드향은 한라봉과 귤을 교배한 것으로</p>
<p>일반 귤보다 2~3배 크고, 과육이 붉고 통통하다.</p>
</div>
</body>
</html>
html의 글자 크기가 16px이면 1.5rem은 항상 24px이다. 부모의 글자 크기가 바뀌어도 값이 달라지지 않아 문서 전체의 크기를 일관되게 관리하기 쉽다.
8.2. 반응형 이미지
시작 예제에서는 이미지가 원본 너비를 유지하므로 작은 화면에서 부모 영역을 벗어날 수 있다. 완료 예제는 .top 규칙으로 문제를 해결한다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>반응형 이미지 만들기</title>
<style>
p {
font-size: 1.5rem;
}
/* 이미지를 반응형으로 만들기 */
</style>
</head>
<body>
<div>
<p>브라우저 창의 크기를 조절해보세요.</p>
<img src="images/kitten.jpg" alt="베개 뒤에 숨어 있는 고양이" class="top">
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>반응형 이미지 만들기</title>
<style>
p {
font-size: 1.5rem;
}
/* 이미지를 반응형으로 만들기 */
.top {
max-width: 100%;
height: auto;
}
</style>
</head>
<body>
<div>
<p>브라우저 창의 크기를 조절해보세요.</p>
<img src="images/kitten.jpg" alt="베개 뒤에 숨어 있는 고양이" class="top">
</div>
</body>
</html>
-
max-width: 100%는 이미지가 부모보다 커지는 것을 막는다. -
height: auto는 너비가 줄어들 때 원본 가로·세로 비율을 유지한다. -
작은 이미지를 강제로 확대하지 않으므로
width: 100%보다 원본 품질을 보존하기 쉽다.
8.3. object-fit
시작 예제와 완료 예제는 같은 이미지를 고정된 상자에 넣고 값에 따른 차이를 비교한다.
| 값 | 동작 |
|---|---|
fill |
상자를 가득 채우며 이미지 비율이 달라질 수 있다. |
contain |
이미지 전체를 보이게 하며 상자 안에 빈 공간이 생길 수 있다. |
cover |
비율을 유지하며 상자를 가득 채우고 넘치는 부분을 자른다. |
none |
이미지 원본 크기를 유지한다. |
scale-down |
none과 contain 중 더 작은 결과를 사용한다. |
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>object-fit 속성</title>
<style>
body {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
gap: 5px;
}
.container {
width: 200px;
height: 400px;
border: 1px solid #000;
}
img {
width: 100%;
height: 100%;
}
/* object-fit 속성을 사용하여 이미지 크기 조절하기 */
</style>
</head>
<body>
<div class="container">
<img src="images/cat.png" alt="고양이" class="fill">
<h3>object-fit: fill</h3>
</div>
<div class="container">
<img src="images/cat.png" alt="고양이" class="contain">
<h3>object-fit: contain</h3>
</div>
<div class="container">
<img src="images/cat.png" alt="고양이" class="cover">
<h3>object-fit: cover</h3>
</div>
<div class="container">
<img src="images/cat.png" alt="고양이" class="none">
<h3>object-fit: none</h3>
</div>
<div class="container">
<img src="images/cat.png" alt="고양이" class="scale-down">
<h3>object-fit: scale-down</h3>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>object-fit 속성</title>
<style>
body {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
gap: 5px;
}
.container {
width: 200px;
height: 400px;
border: 1px solid #000;
}
img {
width: 100%;
height: 100%;
}
/* object-fit 속성을 사용하여 이미지 크기 조절하기 */
.fill {
object-fit: fill; /* 이미지의 비율을 유지하지 않고 요소에 꽉 차게 채움 */
}
.contain {
object-fit: contain; /* 이미지 비율을 유지하면서 요소 크기에 맞춤 */
}
.cover {
object-fit: cover; /* 이미지의 비율을 유지하면서 요소에 꽉 차게 채움 */
}
.none {
object-fit: none; /* 이미지의 원래 크기를 유지함 */
}
.scale-down {
object-fit: scale-down; /* none과 contain 중 작은 쪽으로 이미지 크기를 조절함 */
}
</style>
</head>
<body>
<div class="container">
<img src="images/cat.png" alt="고양이" class="fill">
<h3>object-fit: fill</h3>
</div>
<div class="container">
<img src="images/cat.png" alt="고양이" class="contain">
<h3>object-fit: contain</h3>
</div>
<div class="container">
<img src="images/cat.png" alt="고양이" class="cover">
<h3>object-fit: cover</h3>
</div>
<div class="container">
<img src="images/cat.png" alt="고양이" class="none">
<h3>object-fit: none</h3>
</div>
<div class="container">
<img src="images/cat.png" alt="고양이" class="scale-down">
<h3>object-fit: scale-down</h3>
</div>
</body>
</html>
사진 카드와 갤러리처럼 상자의 크기는 일정하게 유지하고 이미지 왜곡은 막아야 할 때 cover를 주로 사용한다.
8.4. 미디어 쿼리로 배경 변경
시작 예제와 완료 예제는 기본 배경을 지정한 뒤 태블릿과 모바일 구간에서 배경과 글자색을 변경한다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>미디어 쿼리 사용하기</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
h1 {
font-size: 4rem;
}
/* 미디어 쿼리 사용 */
body {
background: url(images/bg-light.jpg) no-repeat fixed;
background-size: cover;
}
</style>
</head>
<body>
<h1>미디어 쿼리</h1>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>미디어 쿼리 사용하기</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
h1 {
font-size: 4rem;
}
/* 미디어 쿼리 사용 */
body {
background: url(images/bg-light.jpg) no-repeat fixed;
background-size: cover;
}
/* 768px ~ 1023px 이면 images/bg-dark.jpg 사용 */
@media screen and (min-width: 768px) and (max-width: 1023px) {
body {
background: url(images/bg-dark.jpg) no-repeat fixed;
background-size: cover;
color: #fff;
}
}
/* 768px 미만이면 images/bg-small.jpg 사용 */
@media screen and (max-width: 767px) {
body {
background: url(images/bg-small.jpg) no-repeat fixed;
background-size: cover;
color: #fff;
}
}
</style>
</head>
<body>
<h1>미디어 쿼리</h1>
</body>
</html>
기본 배경은 bg-light.jpg이다. 화면 너비가 768px~1023px이면 bg-dark.jpg, 767px 이하이면 bg-small.jpg를 사용한다. 구간의 경계를 겹치지 않게 작성해야 같은 화면에서 여러 규칙이 동시에 적용되는 오류를 막을 수 있다.
아래는 직접 풀어 보는 문제다. 시작 예제는 중단점 규칙을 직접 작성하는 문제이고, 완료 예제는 768px~1023px 구간을 지정한 해답이다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>미디어 쿼리</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
h1 {
font-size: 4rem;
}
</style>
</head>
<body>
<h1>안녕하세요?</h1>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>미디어 쿼리</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
h1 {
font-size: 4rem;
}
@media screen and (min-width: 768px) and (max-width: 1023px) {
body {
background-color: gray;
color: white;
}
}
</style>
</head>
<body>
<h1>안녕하세요?</h1>
</body>
</html>
8.5. 반응형 카드
아래 예제는 같은 카드가 화면 너비에 따라 세 가지 형태로 바뀐다. HTML과 CSS를 함께 싣는다.
| 화면 구간 | 카드 배치 |
|---|---|
767px 이하 |
너비 300px의 세로 카드 |
768px~1439px |
너비 550px의 가로 카드 |
1440px 이상 |
카드 두 개를 가로로 배치 |
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>INSPIRATIONAL QUOTES</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="container">
<div class="card">
<img src="images/1.jpg">
<div class="words">
<h2>일 분 전만큼 먼 시간은 없다.</h2>
<h3>- Jim Bishop</h3>
</div>
</div>
<div class="card">
<img src="images/2.jpg">
<div class="words">
<h2>낡은 옷은 그냥 입고 새 책을 사라.</h2>
<h3>- Austin Phelps</h3>
</div>
</div>
</div>
</body>
</html>
* {
margin:0;
padding:0;
box-sizing: border-box;
}
body {
background:rgb(9, 100, 160);
}
#container {
width:320px;
margin:50px auto;
}
.card {
position:relative;
width:300px;
height:500px;
margin:20px 10px;
border:1px solid #0f0f0f33;
background-color:#ffffff;
}
.words {
position:absolute;
left:10px;
top:300px;
padding:10px;
text-align:center;
}
@media screen and (min-width:768px) and (max-width:1439px) {
#container {
width:570px;
margin:50px auto;
}
.card {
position:relative;
width:550px;
height:250px;
margin:20px 10px;
border:1px solid #0f0f0f33;
background-color:#ffffff;
}
.words {
position:absolute;
left:310px;
top:50px;
text-align:center;
width:200px;
}
}
@media screen and (min-width:1440px) {
#container {
width:1140px;
margin:50px auto;
}
.card {
position:relative;
float:left; /* 카드를 가로로 배치 */
width:550px;
height:250px;
margin:10px;
border:1px solid #0f0f0f33;
background-color:#ffffff;
}
.words {
position:absolute;
left:310px;
top:50px;
text-align:center;
width:200px;
}
}
이 예제는 중단점별 레이아웃 변화를 보여 주지만 데스크톱 카드 배치에 float를 사용한다. 새 코드에서는 부모에 display: flex; flex-wrap: wrap을 적용하는 편이 높이 계산과 간격 관리에 유리하다.