그동안 그리드 레이아웃을 주로 설명했는데 이번에는 플렉스박스를 자세히 들여다보려고 한다. 플렉스박스가 어떤 목적으로 만들어졌는지, 어떤 기능이 좋은지, 왜 레이아웃 구성에 사용해서는 안 되는지 알아보자. 특히 스타일시트에 display: flex
를 입력하면 정확히 어떤 일이 벌어지는지 세세하게 들여다볼 예정이다.
플렉스 컨테이너 하나 주세요!
플렉스박스를 사용하려면 플렉스 컨테이너container가 될 요소가 필요하다. CSS에 display: flex
를 선언하자.
우선 display: flex
가 정확히 어떤 의미일까? 디스플레이 모듈 명세 레벨 3에서는 display: flex
를 디스플레이 속성의 모든 값이 내부 디스플레이 모델과 외부 디스플레이 모델 두 가지 조합이라고 설명한다. 웹 디자이너가 display: flex
를 입력하면 display: block flex
가 선언된다. 이 경우 플렉스 컨테이너 외부 디스플레이 형식은 block
으로 설정되어 컨테이너 자체가 일반적인 블록 요소처럼 움직이도록 한다. 내부 디스플레이 형식은 flex
로 컨테이너 안에 들어가는 아이템이 플렉스 레이아웃의 일부가 되도록 한다.
display: flex
에 대해 심도 있게 생각해본 적은 없더라도 이해는 할 수 있을 것이다. 플렉스 컨테이너는 페이지상에서 다른 블록과 똑같이 움직인다. 만약 플렉스 컨테이너 뒤에 문단을 작성한다면 두 요소는 블록이 작동하는 것과 같은 익숙한 모습을 보여줄 것이다.
컨테이너에 inline-flex
값을 부여하면 플렉스 컨테이너가 인라인 요소처럼 움직이고 자식들은 플렉스 레이아웃에 들어가도록 display: inline flex
의 효과를 낼 수 있다. 인라인 플렉스 컨테이너 내부에 있는 자식들은 플렉스 컨테이너의 자식과 동일하게 움직인다. 두 컨테이너 사이에 차이가 있다면 컨테이너 자체가 전체적인 레이아웃에서 움직이는 방식이다.
요소에 외부 디스플레이 방식을 지정하는 개념은 요소 스스로 페이지 전체에 박스로서 어떻게 동작할지 결정하고 (내부 디스플레이 방식을 통해) 자식들의 작동 방식까지 결정하기 때문에 꽤 유용하게 쓸 수 있다. 이 개념은 CSS가 사용하는 모든 박스에 적용이 가능하다. 이 요소는 어떻게 움직일까? 이 요소의 자식들은 어떻게 표시될까? 그 답은 외부와 내부 디스플레이 모델에서 찾을 수 있다.
행? 열?
플렉스 컨테이너를 만들었다면 이번엔 초깃값이 활약할 차례다. 다른 속성을 입력하지 않을 경우 플렉스 아이템들은 행으로 나열된다. 이건 flex-direction
요소의 초깃값이 row
이기 때문이다. 따로 값을 지정하지 않으면 행이 선택되는 것이다.
flex-direction
은 주축의 방향을 설정한다. flex-direction
이 가질 수 있는 다른 값들은 아래와 같다.
column
row-reverse
column-reverse
행을 따라 배치하기로 결정했다면 아이템들은 인라인 영역의 출발선에 첫 번째 아이템을 놓는 것을 시작으로 소스에 나온 순서대로 자리잡는다. 명세서에서는 이 출발선을 main-start
라 부른다.
만약 column
으로 방향을 지정했다면 아이템들은 블록 영역의 출발선부터 놓이기 시작해 열을 만들 것이다.
row-reverse
를 사용할 경우 main-start
와 main-end
의 위치가 뒤바뀌어 아이템들이 역순으로 놓이게 된다.
column-reverse
역시 같은 방식으로 작동한다. 이 값들은 ‘배치 순서를 바꾸는 것’처럼 보이지만 main-start
위치를 옮겨 아이템을 배치하는 방향을 바꿀 뿐이란 사실을 기억하자. 화면에는 아이템들이 역순으로 표시되지만 사실 컨테이너의 반대편부터 놓기 시작한 것뿐이다.
또 이런 값을 사용할 때 그 효과는 시각적으로만 적용된다는 것 역시 잊지 말아야 한다. 아이템들은 끝선부터 배열됐을 뿐 순서는 입력된 대로 유지되어 스크린 리더를 사용하거나 탭 버튼을 누를 때는 원래 순서가 적용된다. 순서를 바꾸고 싶다면 소스에서 변경하면 된다.
플렉스박스의 두 축
플렉스박스의 중요한 특징인 주축main axis을 행에서 열로 바꾸는 기능을 봤다. 내가 그리드 레이아웃의 정렬 같은 기능을 더 이해하기 쉽다고 생각하는 이유는 이 주축 변경 때문이다. 두 가지 영역 모두 제어하는 그리드를 사용한다면 양 축을 모두 같은 방법으로 조정할 수 있다. 반면 플렉스박스는 주축을 제어하느냐 교차축cross axis을 제어하느냐에 따라 다르기 때문에 조금 더 복잡하다.
사실 주축은 이미 한 번 봤다. flex-direction
속성값으로 설정한 축이 바로 주축이다. 여기서 선택되지 않은 축은 교차축이 된다. flex-direction: row
를 설정했을 때 주축은 행의 방향이 되며 교차축은 열의 방향이 된다. flex-direction:column
을 설정할 경우 주축은 열이 되며 교차축은 행을 따라 움직인다. 플렉스박스가 가진 또 다른 중요한 특징은 스크린이라는 물리적 공간에 얽매이지 않는다는 점이다. 그래서 플렉스박스를 사용할 때 행은 왼쪽에서 오른쪽으로 향하고, 열은 위에서 아래로 향한다고 말하지 않는다. 그 이유는 늘 그렇게 움직이지 않기 때문이다.
쓰기 모드
위에서 행과 열을 설명할 때 블록과 인라인 영역을 언급했다. 이 글은 가로쓰기 모드를 사용하는 영어로 작성됐다. 가로쓰기 모드를 사용한다는 것은 플렉스박스에 행 값을 설정할 때 플렉스 아이템들이 가로로 표시된다는 걸 의미한다. 이 경우 main-start
는 영어가 시작하는 왼쪽에 위치하게 된다.
만약 아랍어처럼 오른쪽에서 시작하는 언어를 사용한다면 시작선은 오른쪽에 자리할 것이다.
여기서 플렉스 컨테이너를 선언만 하고 아무런 변경도 하지 않았다면 플렉스박스 초깃값이 아이템들을 자동적으로 오른쪽부터 왼쪽으로 배치할 것이다. 인라인 방향에서의 시작선은 사용하는 쓰기 모드의 시작 위치와 같다.
만약 세로쓰기 언어를 사용한다면 텍스트가 작성되는 행의 방향이 세로이기 때문에 행은 세로로 나타날 것이다. 확인하고 싶다면 플렉스 컨테이너에 writing-mode
속성을 부여하고 값을 vertical-lr
로 설정해보자. 이때 flex-direction
을 row
로 설정하면 아이템들은 수직으로 배치된다.
행은 main-start
가 왼쪽에서 오른쪽으로 향하는 수평으로 나타날 수 있고 main-start
가 위에서부터 시작하는 수직으로도 나타날 수 있다. 수평 텍스트에 익숙해진 우리가 세로로 된 행을 받아들이기 어렵더라도 flex-direction
의 값은 여전히 row
다.
아이템들을 블록 영역에 배치하고 싶다면 flex-direction
값을 column
이나 column-reverse
로 설정해보자. 그러면 영어(나 아랍어)에서 아이템들이 컨테이너 꼭대기부터 차곡차곡 쌓이는 모습을 확인할 수 있을 것이다.
세로쓰기 모드에서는 블록이 배치되는 방향을 따라 블록 영역이 페이지 전체를 차지하게 된다. writing-mode
의 값을 vertical-lr
로 설정한 뒤 열을 불러온다면 블록들은 세로 방향을 기준으로 왼쪽에서 오른쪽으로 배치될 것이다.
하지만 블록이 어느 방향으로 표현되건 column
을 사용한다면 블록 영역을 제어하는 것이다.
행과 열이 다른 물리적 방향으로도 움직일 수 있음을 이해한다면 그리드와 플렉스박스에서 사용되는 용어를 이해하는 데 도움이 될 것이다. 플렉스박스와 그리드는 문서의 쓰기 방식에 대한 어떤 추측도 하지 않으므로 ‘상하’, ‘좌우’ 같은 표현을 사용하지 않는다. CSS의 속성 대부분은 점차 쓰기 모드에 영향을 받도록 변해가고 있다. 만약 나머지 CSS 요소들까지 같은 방식으로 움직이게 만드는 속성과 값에 관심이 있다면 CSS 논리적 속성과 값 이해하기를 읽어볼 것을 권한다.
쓰기 모드에 대해 요약하면 다음과 같으니 기억해두자.
- flex-direction: row
- 주축 = 인라인 영역
main-start
= 쓰기 모드에서 문장이 시작하는 지점- 교차축 = 블록 영역
- flex-direction: column
- 주축 = 블록 영역
main-start
= 쓰기 모드에서 블록이 나타나는 지점- 교차축 = 인라인 영역
초기 정렬
display: flex
를 선언할 때 일어나는 또 다른 부분을 살펴보자. 몇몇 초깃값이 그 주인공이다. 추후 다른 글에서 정렬에 대해 다룰 예정이지만 display: flex
를 자세히 알기 위해서는 초깃값을 확인해야 한다.
참고: 그리드의 박스 정렬 명세가 결국 플렉스박스 명세를 대체하게 되겠지만 플렉스박스 명세가 설명하듯 정렬 속성들이 플렉스박스 명세에서 시작됐다는 걸 알아두자.
주축 정렬
justify-content
의 초깃값은 flex-start
로 설정되어 있다. 다음 CSS 코드를 보자.
이 코드가 플렉스 아이템들이 플렉스 컨테이너의 시작선에 놓이는 이유다. 그리고 동시에 row-reverse
를 설정하면 같은 선이 끝선으로 바뀌고 맞은 편이 축의 시작 지점으로 바뀌는 원인이기도 하다.
justify-
로 시작되는 정렬 속성은 모두 플렉스박스의 주축에 적용된다. 그래서 justify-content
는 주축 정렬을 통해 플렉스 아이템들을 시작 지점에 정렬한다.
justify-content
가 가질 수 있는 다른 값들은 아래와 같다.
flex-end
center
space-around
space-between
space-evenly
(박스 정렬에서 추가됨)
이 값들은 플렉스 컨테이너 내부의 여유 공간 배치에 관여한다. 어떤 값을 선택하느냐에 따라 아이템들의 위치와 간격이 바뀌게 된다. 예를 들어 justify-content: space-between
을 입력할 경우 컨테이너의 여백은 각 아이템에 똑같이 나뉘는 식이다. 하지만 이런 효과를 보기 위해서는 빈 영역이 필요하다. (아이템들이 배치된 후 여유 공간이 남지 않는) 꽉 찬 플렉스 컨테이너를 만들었다면 justify-content
는 아무 역할도 하지 않는다.
flex-direction
을 column
으로 설정하면 아래와 같은 결과가 나온다. 플렉스 컨테이너의 높이를 지정하지 않으면 여유 공간이 생기지 않기 때문에 justify-content: space-between
은 아무 일도 하지 못한다. 컨테이너에 아이템을 배치하고 남을 정도의 높이를 부여한다면 justify-content: space-between
이 효과를 발휘한다.
교차축 정렬
일렬로 된 플렉스 컨테이너는 내부에 담긴 박스들 사이의 배치를 조절하는 교차축 정렬을 할 수 있다. 다음 예시에서는 박스 하나가 다른 박스들보다 많은 내용을 담고 있다. 이에 한 속성이 다른 박스들에게 같은 높이로 늘어나라고 지시한다. 바로 stretch
를 초깃값으로 갖고 있는 align-items
속성이다.
플렉스박스에서 align-
으로 시작하는 정렬 속성들은 모두 교차축 정렬을 제어하며 align-items
는 플렉스 축 안에 위치한 아이템들을 정렬한다. align-items
가 갖는 다른 값은 다음과 같다.
flex-start
flex-end
center
baseline
모든 박스들이 가장 높은 박스를 따라 늘어나는 걸 원치 않는다면 align-items: flex-start
를 사용해 교차축의 시작선에 맞출 수 있다.
플렉스 아이템의 초깃값
플렉스 내부 아이템 역시 초깃값을 갖는다.
flex-grow: 0
flex-shrink: 1
flex-basis: auto
위의 값은 주축에 남는 공간을 채우기 위해서 아이템들의 크기가 늘어나도록 만들지 않는다. 만약 flex-grow
속성이 양수 값을 갖는다면 남는 공간을 채우기 위해 커질 것이다.
대신 flex-shrink
는 1
로 설정되어 작아질 수 있다. 그래서 아주 좁은 플렉스 컨테이너를 만들 경우 내부 아이템들은 컨테이너를 넘어가지 않도록 하기 위해 줄어들게 된다. 박스에 내용을 표시할 공간이 남아 있다면 보통은 내용물이 박스 밖으로 나오기보다 내부에 있길 바라기 때문에 이는 꽤 실용적이다.
기본값으로만 최상의 레이아웃을 얻고 싶다면 flex-basis
를 auto
로 설정하는 것이 좋다. auto
는 ‘내용을 다 담을 만큼 크게’란 의미로 생각하면 된다. 아래 예시는 컨테이너 안을 채우는 플렉스 아이템 중 하나가 다른 것들보다 많은 양의 내용을 담고 있어 더 많은 공간을 차지하는 경우다.
이것이 플렉스박스가 지닌 유연성이다. 플렉스 아이템에 flex-basis
속성이 자동으로 설정되어 있고, 크기가 특정되지 않았다면, 기본 크기는 max-content
의 크기와 같아진다. 이는 아이템이 늘어난 뒤 다른 아이템을 감싸지 않을 때 갖는 크기다. 이 경우 각 아이템에서 일정 비율만큼 공간이 줄어들게 되는데 이에 대해서는 플렉스박스 명세서에서 확인할 수 있다.
“참고: 크기를 줄일 때는 플렉스 shrink 속성에 플렉스의 기본 크기가 곱해진다. 이렇게 아이템이 줄어들 수 있는 비율만큼 크기를 줄이면 큰 아이템이 눈에 띄게 줄어들기 전에 작은 아이템이 사라지는 일이 없게 된다.”
최종 레이아웃이 완성될 때 더 큰 아이템의 공간이 적게 줄어든다. 아래 두 스크린샷은 똑같은 속성으로 만든 페이지다. 하지만 첫 스크린샷에는 세 번째 박스가 적은 양의 내용물을 가지고 있어 공간을 좀 더 공평하게 나눠 갖는다.
플렉스박스는 웹 디자이너가 다른 CSS 코드를 작성하지 않아도 합리적인 결과물을 얻을 수 있게 한다. 내용이 긴 아이템이 있다면 공간을 균등하게 나눠 한 줄에 두세 단어씩 적어 높이가 길어지게 만들기보다 내용물을 모두 적을 수 있도록 많은 공간을 할당하는 식이다. 이런 부분이 플렉스박스를 실제로 사용하게 만드는 핵심적인 이유다. 플렉스박스는 아이템들을 (한 방향으로) 유연하게 내용 중심으로 배치할 때 가장 좋은 선택이다. 이번에 잠시 살펴봤지만 차후 이 알고리즘에 대해서 자세하게 다룰 예정이다.
요약
display:flex
를 선언하면 어떤 일이 일어나는지 알아보기 위해 플렉스박스의 초깃값을 다뤘다. 여기서 다룬 몇 가지 속성은 플렉스 레이아웃의 주요 기능에 속한다.
플렉스 레이아웃은 유연하다. 플렉스는 기본적으로 최고의 가독성을 위해 박스의 크기를 조정한다. 플렉스 레이아웃은 쓰기 모드를 감지해 행과 열의 방향을 결정한다. 플렉스 레이아웃은 공간을 분배하는 방식을 선택해 주축 방향 아이템들을 정렬한다. 또한 교차축에서 아이템들의 위치를 조정해 플렉스 줄 위 아이템들을 정렬한다. 중요한 것은 플렉스 레이아웃이 내용물의 크기를 감지해 표시하기 가장 좋은 방법을 선택한다는 점이다. 조만간 이런 기능에 대해 깊이 있게 다룰 예정이다. 더불어 언제, 왜, 플렉스박스를 선택해야 하는가에 대해서도 이야기하려고 한다.
레이철 앤드루의 신간 『새로운 CSS 레이아웃』
그리드를 사용한 레이아웃을 기존 레이아웃과 비교해서 살펴볼 수 있습니다. 예제를 통해 그리드 레이아웃이 어떻게 활용되는지 확인해보세요. 레이아웃만을 다룬 ‘그리드 레이아웃’ 전문서 『새로운 CSS 레이아웃』입니다. 본질의 웹 디자인 시대 흐름에 뒤처지지 않으려면 이 책을 꼭 읽어야 합니다!!
books@webactually.com