홈으로
Issue No. 20240425

이번에야말로 CSS Flex를 익혀보자

인프런 강의 - 3D 웹사이트 만들기
Three.js로 게임같은 3D 웹사이트 만들기
three.js 강의
Three.js 종합강의

이 튜토리얼은 “차세대 CSS 레이아웃” 시리즈의 첫번째 포스트입니다.

벌써부터 스크롤의 압박이 느껴지고,
‘좀 편안하게 누군가 나의 공부를 이끌어주면 좋겠다.’라고 생각하고 계신다면, 아래의 배너의 강좌를 살펴보세요.

학습시간_절약의_길.jpg

바야흐로 2020년입니다.
2020 원더키디에서 처럼 우주선이 우리 눈 앞에 막 날아 다니지는 않지만, 온갖 핑계(주로 “그거 IE에서 안되잖아요!”)로 미루어왔던 Flex와 Grid를 공부해야 할 때는 충분히 되었다고 생각합니다. 당장 회사에서 안쓴다고 “에이 난 필요없어~”하시는 분 분명 계실텐데.. 평생 그 회사 다니실거 아니잖아요?
전설의 테이블(table) 코딩을 해보셨나요? 머지 않은 미래에, 우리가 지금 많이 사용하는 float과 inline-block을 여기저기 바르는 방식은 이제 바로 그 테이블 코딩처럼 구닥다리 취급을 당할지도 몰라요. 우리는 그 전에 준비하고, 이미 익숙해져 있어야 합니다. 프로라면 그러셔야 합니다. 아 물론 아직까지 float를 이용한 방식도 여전히 많이 쓰이기 때문에, 사실 다 알고 계시는게 좋아요..
동기부여는 이 정도면 충분히 되었다고 생각하고.. 어쨋든, 이 포스트를 모두 보고 난 여러분들이 이렇게 외치기를 기대합니다.

그럼 정말 시작!

Flex(플렉스)는 Flexible Box, Flexbox라고 부르기도 합니다.
Flex는 레이아웃 배치 전용 기능으로 고안되었습니다. 그래서 레이아웃을 만들 때 딱히 사용할게 없어서 쓰던 float나 inline-block 등을 이용한 기존 방식보다 훨씬 강력하고 편리한 기능들이 많아요.

Flex에 관한 오해

“Grid가 나온 이상, Flex는 구시대의 유물일 뿐이다.”

저는 아니라고 생각합니다. 더 나중에 나온 스펙인 Grid로도 Flex와 똑같이 구현할 수 있는 경우가 많지만, Grid로는 구현이 어려운 레이아웃이거나 Flex를 쓰는게 더 편리한 경우도 있습니다. 그리고 마소가 싼 똥 버렸지만 우리는 여전히 지원해줘야 하는 인터넷 익스플로러(IE)같은 경우는 Flex는 표준 스펙을 지원하지만 Grid는 legacy(고인물) 버전만 지원하기 때문에, 둘 다 잘 알아두고 적재적소에 활용하는 것이 가장 좋다고 생각돼요.

실무에서 사용을 해보시려는 분들은 제일 궁금한게 아마 “인터넷 익스플로러(이하 IE)에서도 지원이 되나?” 일거예요.
https://caniuse.com/#search=CSS%20Flexible%20Box
“Can I use?” 닷컴에서 보면 IE 10과 11은 부분 지원임을 알 수 있습니다.
10과 11도 큰 차이가 있는데요, 10은 자기만의 독자적인 스펙(초기 스펙인 display: box와는 또 다른 거;)을 지원하고, 11은 그래도 표준 스펙을 지원합니다. 버그는 꽤나 있지만, 속성들의 특징과 기능을 정확하게 파악하고 있다면 해결해나갈 수 있는 버그들이니 크게 걱정하지 않으셔도 돼요. 저도 IE는 11만 지원해도 되는 프로젝트라면 Flex를 적극적으로 사용하고 있습니다. 버그 그까이꺼 보다, Flex가 주는 편리함이 너무 크거든요! (라고 했지만 상황에 따라서는 쓰지 않는게 나을 수 있습니다 ㅋㅋㅋ 그렇지만 저는 반드시 씁니다!)
참고로 IE의 버그는 위의 Can I use? 닷컴 링크의 “Known Issues(알려진 이슈들)” 탭에서 확인해 볼 수 있습니다.

Flex 레이아웃을 만들기 위한 기본적인 HTML 구조는 다음과 같습니다.

<div class="container">
	<div class="item">helloflex</div>
	<div class="item">abc</div>
	<div class="item">helloflex</div>
</div>

부모 요소인 div.container를 Flex Container(플렉스 컨테이너)라고 부르고,
자식 요소인 div.item들을 Flex Item(플렉스 아이템)이라고 부릅니다.
“컨테이너가 Flex의 영향을 받는 전체 공간이고, 설정된 속성에 따라 각각의 아이템들이 어떤 형태로 배치되는 것”이라고 생각하시면 됩니다.

Flex의 속성들은,

  • 컨테이너에 적용하는 속성
  • 아이템에 적용하는 속성

이렇게 두 가지로 나뉘어요.
먼저, 컨테이너에 적용하는 속성들 부터 알아볼게요.

Flex 컨테이너에 적용하는 속성들

display: flex;

Flex 컨테이너에 display: flex;를 적용하는게 시작이에요.
이 한 줄의 CSS만으로 아이템들은 기본적으로 아래 그림과 같이 배치됩니다.

.container {
	display: flex;
	/* display: inline-flex; */
}
직접 테스트 해보세요

Flex 아이템들은 가로 방향으로 배치되고, 자신이 가진 내용물의 width 만큼만 차지하게 되지요. 마치 inline 요소들 처럼요. height는 컨테이너의 높이만큼 늘어납니다.
height가 알아서 늘어나는 특징은 컬럼 레이아웃을 만들 때 아주 편리하겠네요~
(물론 나중에 정렬 속성을 통해 height를 어떻게 처리할지도 조정할 수 있습니다.)

컬럼의 높이가 자동으로 쫙- 맞는다

inline-flex도 있는데, 이건 block과 inline-block의 관계를 생각하시면 돼요.
아이템의 배치와 관련이 있다기 보다는, 컨테이너가 주변 요소들과 어떻게 어우러질지 결정하는 값입니다. inline-flex는 inline-block처럼 동작해요.

아이템들이 배치된 방향의 축을 메인축(Main Axis),
메인축과 수직인 축을 수직축 또는 교차축(Cross Axis)이라고 불러요.
좀 뜬금 없지만, 앞으로 메인축“오뎅꼬치”라고 생각하세요.
?????
나중에 정렬을 배울 때 헷갈리지 않으려고 하는 건데요,
오뎅(Flex 아이템)들이 꼬치(메인축)을 따라 쭉 꽂혀서 정렬된 상태를 생각하고 계시면 됩니다.
바로 이렇게요↓

배치 방향 설정
flex-direction

아이템들이 배치되는 축의 방향을 결정하는 속성입니다. 즉 “메인축(오뎅꼬치)의 방향을 가로로 할거냐 세로로 할거냐”를 정해주는 거예요.

.container {
	flex-direction: row;
	/* flex-direction: column; */
	/* flex-direction: row-reverse; */
	/* flex-direction: column-reverse; */
}

row (기본값)

아이템들이 행(가로) 방향으로 배치됩니다.

row-reverse

아이템들이 역순으로 가로 배치됩니다.

column

아이템들이 열(세로) 방향으로 배치됩니다.
그냥 block 요소들을 쌓아 놓은 것처럼 보이죠?

column-reverse

아이템들이 역순으로 세로 배치 됩니다.

크기가 작은 모바일 기기에서 column으로 배치하다가 일정 폭 이상이 되면 row로 바꿔주는 식으로 반응형 레이아웃을 구현할 수도 있겠네요~

줄넘김 처리 설정
flex-wrap

컨테이너가 더 이상 아이템들을 한 줄에 담을 여유 공간이 없을 때
아이템 줄바꿈을 어떻게 할지 결정하는 속성입니다.

.container {
	flex-wrap: nowrap;
	/* flex-wrap: wrap; */
	/* flex-wrap: wrap-reverse; */
}

nowrap (기본값)

줄바꿈을 하지 않습니다. 넘치면 그냥 삐져 나가요.

wrap

줄바꿈을 합니다. float이나 inline-block으로 배치한 요소들과 비슷하게 동작해요.

wrap-reverse

줄바꿈을 하는데, 아이템을 역순으로 배치해요. 그림을 보면 이해하실 수 있을 거예요.

flex-flow

flex-direction과 flex-wrap을 한꺼번에 지정할 수 있는 단축 속성이에요.
flex-direction, flex-wrap의 순으로 한 칸 떼고 써주시면 됩니다.

.container {
	flex-flow: row wrap;
	/* 아래의 두 줄을 줄여 쓴 것 */
	/* flex-direction: row; */
	/* flex-wrap: wrap; */
}

자, 이제 “정렬”을 할건데요. 시작하기 전에 기억해 두실게 있어요.
이건 나중에 Grid까지 이어지니까 지금 알아 두세요~

“justify”는 메인축(오뎅꼬치) 방향으로 정렬
“align”은 수직축(오뎅을 뜯어내는) 방향으로 정렬

한 번만 나올게 아니니까 이건↑ 지금 꼭 머리에 넣어 두세요!

메인축 방향 정렬
justify-content

justify 키워드가 나왔죠? 메인축 방향으로 아이템을들 정렬하는 속성이에요.

.container {
	justify-content: flex-start;
	/* justify-content: flex-end; */
	/* justify-content: center; */
	/* justify-content: space-between; */
	/* justify-content: space-around; */
	/* justify-content: space-evenly; */
}

flex-start (기본값)

아이템들을 시작점으로 정렬합니다.
flex-direction이 row(가로 배치)일 때는 왼쪽, column(세로 배치)일 때는 위예요.

flex-end

아이템들을 끝점으로 정렬합니다.
flex-direction이 row(가로 배치)일 때는 오른쪽, column(세로 배치)일 때는 아래예요.

center

아이템들을 가운데로 정렬합니다.

space-between

아이템들의 “사이(between)”에 균일한 간격을 만들어 줍니다.

space-around

아이템들의 “둘레(around)”에 균일한 간격을 만들어 줍니다.

space-evenly

아이템들의 사이와 양 끝에 균일한 간격을 만들어 줍니다.
주의! IE와 엣지(Edge)에서는 지원되지 않습니다👎
이 웹사이트의 메뉴 부분은 브라우저 폭이 1024px 이상일 때 space-evenly가 적용되도록 했는데요, IE와 엣지에서만 space-around로 적용이 되도록 처리해 두었어요.

space-between, space-around, space-evenly는 비슷한듯 하면서 살짝 다른데요, 아래 그림을 봐보세요~

수직축 방향 정렬
align-items

align 키워드가 나왔죠? 수직축 방향으로 아이템을들 정렬하는 속성이에요.
justify-content와 수직 방향의 정렬이라고 생각하시면 됩니다.

.container {
	align-items: stretch;
	/* align-items: flex-start; */
	/* align-items: flex-end; */
	/* align-items: center; */
	/* align-items: baseline; */
}

stretch (기본값)

아이템들이 수직축 방향으로 끝까지 쭈욱 늘어납니다.

flex-start

아이템들을 시작점으로 정렬합니다.
flex-direction이 row(가로 배치)일 때는 위, column(세로 배치)일 때는 왼쪽이에요.

flex-end

아이템들을 끝으로 정렬합니다.
flex-direction이 row(가로 배치)일 때는 아래, column(세로 배치)일 때는 오른쪽이에요.

center

아이템들을 가운데로 정렬합니다.

baseline

아이템들을 텍스트 베이스라인 기준으로 정렬합니다.

justify-content: center;
align-item: center;
를 해주면, 아이템을 이렇게↓ 한 가운데에 놓는 것도 매우 쉽습니다.

여러 행 정렬
align-content

flex-wrap: wrap;이 설정된 상태에서, 아이템들의 행이 2줄 이상 되었을 때의 수직축 방향 정렬을 결정하는 속성입니다.

.container {
	flex-wrap: wrap;
	align-content: stretch;
	/* align-content: flex-start; */
	/* align-content: flex-end; */
	/* align-content: center; */
	/* align-content: space-between; */
	/* align-content: space-around; */
	/* align-content: space-evenly; */
}

위의 정렬들, justify-content랑 align-items를 보셨다면 어떤 느낌인지 이거↑ 테스트 해보시면 아실 거예요..(무책임)
정리하려다가 말이 너무 중복되는 느낌이라서요 ㅋㅋㅋ
눌러보시니까 딱 와 닿죠?
역시나 space-evenly는 MS 계열 브라우저(IE, 엣지)에서는 지원되지 않습니다👎👎

자, 이제 컨테이너에 적용하는 속성은 마무리가 되었고
이제 아이템에 적용하는 속성들에 대해 살펴 볼게요.

Flex 아이템에 적용하는 속성들

유연한 박스의 기본 영역
flex-basis

flex-basis는 Flex 아이템의 기본 크기를 설정합니다(flex-direction이 row일 때는 너비, column일 때는 높이).

.item {
	flex-basis: auto; /* 기본값 */
	/* flex-basis: 0; */
	/* flex-basis: 50%; */
	/* flex-basis: 300px; */
	/* flex-basis: 10rem; */
	/* flex-basis: content; */
}

flex-basis의 값으로는 우리가 width, height 등에 사용하는 각종 단위의 수가 들어갈 수 있고요, 기본값 auto는 해당 아이템의 width값을 사용합니다. width를 따로 설정하지 않으면 컨텐츠의 크기가 되겠지요. content는 컨텐츠의 크기로, width를 따로 설정하지 않은 경우와 같아요. 좀 헷갈리죠? ㅋㅋ 일단 지금은 아이템의 기본 점유 크기를 설정한다고 생각해 주세요. 있다가 나오는 다른 속성들과 결합해서 몇가지 테스트를 하면서 좀 더 익숙해져 보겠습니다.

.item {
	flex-basis: 100px;
}

원래의 width가 100px이 안되는 AAA와 CCC는 100px로 늘어났고, 원래 100px이 넘는 BBB는 그대로 유지되죠~

반면에 width를 설정하면, 원래 100px을 넘는 BBB도 100px로 맞춰집니다.
(아래처럼 BBBBBBBBBBB가 다음 줄로 넘어가도록 하려면, CSS에 word-wrap: break-word;를 적용해주세요. 안그러면 영역만 100px로 줄어들고 BBBBBB는 옆으로 쭉- 삐져나간답니다.)

.item {
	width: 100px;
}

둘 다 설정하면?

.item {
	flex-basis: 100px;
	width: 100px;
}

유연하게 늘리기
flex-grow

flex-grow는 아이템이 flex-basis의 값보다 커질 수 있는지를 결정하는 속성이에요.
flex-grow에는 숫자값이 들어가는데, 몇이든 일단 0보다 큰 값이 세팅이 되면 해당 아이템이 유연한(Flexible) 박스로 변하고 원래의 크기보다 커지며 빈 공간을 메우게 됩니다.
기본값이 0이기 때문에, 따로 적용하기 전까지는 아이템이 늘어나지 않았던 거예요.

.item {
	flex-grow: 1;
	/* flex-grow: 0; */ /* 기본값 */
}
flex-grow에 0과 1을 세팅한 경우

flex-grow에 들어가는 숫자의 의미는, 아이템들의 flex-basis를 제외한 여백 부분을 flex-grow에 지정된 숫자의 비율로 나누어 가진다고 생각하시면 됩니다.
이게 말로 설명하니 좀 애매한데..
아래 실제 코드가 적용된 예시와 그림을 보시면 와 닿을거예요.

/* 1:2:1의 비율로 세팅할 경우 */
.item:nth-child(1) { flex-grow: 1; }
.item:nth-child(2) { flex-grow: 2; }
.item:nth-child(3) { flex-grow: 1; }

각 아이템의 컨텐츠가 “AAAAA”, “B”, “CCC”로 원래의 크기가 다르기 때문에 전체 아이템의 크기가 살짝 애매한 비율로 보이지만, 여백의 비로 생각해 보면 정확히 1:2:1이죠!
정수 뿐 아니라 3.141592같은 소수도 가능합니다.

유연하게 줄이기
flex-shrink

flex-shrink는 flex-grow와 쌍을 이루는 속성으로, 아이템이 flex-basis의 값보다 작아질 수 있는지를 결정합니다.
flex-shrink에는 숫자값이 들어가는데, 몇이든 일단 0보다 큰 값이 세팅이 되면 해당 아이템이 유연한(Flexible) 박스로 변하고 flex-basis보다 작아집니다.
기본값이 1이기 때문에 따로 세팅하지 않았어도 아이템이 flex-basis보다 작아질 수 있었습니다.

.item {
	flex-basis: 150px;
	flex-shrink: 1; /* 기본값 */
}

flex-shrink를 0으로 세팅하면 아이템의 크기가 flex-basis보다 작아지지 않기 때문에 고정폭의 컬럼을 쉽게 만들 수 있어요. 고정 크기는 width로 설정합니다.
아주 자주 쓰는, 이런↓ 레이아웃도 손쉽게!

.container {
	display: flex;
}
.item:nth-child(1) {
	flex-shrink: 0;
	width: 100px;
}
.item:nth-child(2) {
	flex-grow: 1;
}
컨테이너의 폭을 100%와 250px로 왔다리갔다리. flex-shrink: 0; 덕분에 컨테이너가 아무리 작아져도 첫번째 아이템은 찌그러지지 않고 폭이 100px로 유지된다.

flex

flex-grow, flex-shrink, flex-basis를 한 번에 쓸 수 있는 축약형 속성입니다.
이 세 속성들은 서로 관련이 깊기 때문에, 이 축약형을 쓰는 편이 여러모로 편리합니다.

.item {
	flex: 1;
	/* flex-grow: 1; flex-shrink: 1; flex-basis: 0%; */
	flex: 1 1 auto;
	/* flex-grow: 1; flex-shrink: 1; flex-basis: auto; */
	flex: 1 500px;
	/* flex-grow: 1; flex-shrink: 1; flex-basis: 500px; */
}

주의할 점은, flex: 1; 이런 식으로 flex-basis를 생략해서 쓰면 flex-basis의 값은 0이 됩니다.

축약형 flex로 좀 더 예시를 들어 볼게요.

.item {
	flex: 1 1 0;
}
.item:nth-child(2) {
	flex: 2 1 0;
}

flex-basis: 0; 으로, 기본 점유 크기를 0으로 만들어버려 결국 전체 크기를 1:2:1로 나누어 가져서, 영역 자체의 크기가 정확히 1:2:1의 비율로 설정되었습니다.
여백의 비가 아닌, 영역 자체를 원하는 비율로 분할하기를 원한다면 이렇게 flex-basis을 0으로 하면 손쉽게 처리할 수 있어요.

flex-wrap과 flex-basis를 이용해서 2단 컬럼의 사각형 목록을 만들어 볼게요.

.container {
	display: flex;
	flex-wrap: wrap;
}
.item {
	flex: 1 1 40%;
}

flex-basis가 40%면, 100%에는 2개까지만 들어가므로 하나의 row에는 2개까지만 남고 다음줄로 넘어가게 되어서 2단 컬럼이 유지가 됩니다.

flex: 1 1 40%는 2단, flex: 1 1 30%는 3단

수직축으로 아이템 정렬
align-self

align이니 수직축 정렬이겠죠~
align-items의 아이템 버전입니다. align-items가 전체 아이템의 수직축 방향 정렬이라면, align-self는 해당 아이템의 수직축 방향 정렬입니다.

.item {
	align-self: auto;
	/* align-self: stretch; */
	/* align-self: flex-start; */
	/* align-self: flex-end; */
	/* align-self: center; */
	/* align-self: baseline; */
}

기본값은 auto로, 기본적으로 align-items 설정을 상속 받습니다.
align-self는 align-items보다 우선권이 있어요~ 전체 설정보다 각각의 개별 설정이 우선한다는 것, 외우지 않아도 자연스럽게 다가오죠?
auto외의 나머지 값들은 align-items와 동일합니다.

아래는 align-self 값을 BBB는 center, CCC는 flex-start로 설정한 예시예요.

배치 순서
order

각 아이템들의 시각적 나열 순서를 결정하는 속성이에요.
숫자값이 들어가며, 작은 숫자일 수록 먼저 배치됩니다. “시각적” 순서일 뿐, HTML 자체의 구조를 바꾸는 것은 아니므로 접근성 측면에서 사용에 주의하셔야 합니다. 시각 장애인분들이 사용하는 스크린 리더로 화면을 읽을 때, order를 이용해 순서를 바꾼 것은 의미가 없다는 것을 기억해 주세요.

.item:nth-child(1) { order: 3; } /* A */
.item:nth-child(2) { order: 1; } /* B */
.item:nth-child(3) { order: 2; } /* C */

z-index

z-index로 Z축 정렬을 할 수 있어요. 숫자가 클 수록 위로 올라옵니다.
(position에서의 z-index랑 똑같이 생각하시면 됩니다.)

.item:nth-child(2) {
	z-index: 1;
	transform: scale(2);
}
/* z-index를 설정 안하면 0이므로, 1만 설정해도 나머지 아이템을 보다 위로 올라온다 */
고생 많이 하셨습니다!

Flex와 Grid에 대해 더 깊게 공부하고 싶다면 아래 링크의 온라인 강좌를 살펴보세요~
1분코딩에서 출시한 따끈한 신작입니다.

수강평을_확인해보세요.jpg