CSS 강의 3편. Grid Layout 완벽 이해

안녕하세요?

CSS 강의 3편입니다.

지난 시간까지는 Flexbox에 대해 심도 있게 공부했었는데요.

오늘은 Grid Layout에 대해 알아보겠습니다.

전체 강의 리스트입니다.

  1. CSS 강의 1편. Flexbox 완벽 이해

  2. CSS 강의 2편. Flexbox Growing, Shrinking, Gaps, Wrapping 이해

  3. CSS 강의 3편. Grid Layout 완벽 이해

  4. CSS 강의 4편. CSS Variables와 calc 함수 완벽 이해

  5. CSS 강의 5편. CSS에서 div를 중앙에 위치시키는 방법


** 목 차 **

  1. CSS Grid

  2. Grid flow

  3. Grid 구조화

  4. 암시적 행 vs 명시적 행

  5. 자식 요소 배치하기

  6. Grid areas

  7. 정렬

    7.1 justify-content

    7.2 justify-items

    7.3 justify-self

    7.4 align-content

    7.5 align-items

    7.6 align-self

  8. 두 줄짜리 센터링 트릭


1. CSS Grid

Grid 레이아웃은 레이아웃 알고리즘 중 하나이며, 주로 HTML의 구조화에 쓰이는 레이아웃인데요.

Flexbox와 더불어 오늘날 가장 많이 쓰이는 레이아웃입니다.

위와 같이 Header, Nav, Main, Footer 등 HTML을 구조화하기 위해 Flexbox 보다 더 강력한 기능을 제공해 줍니다.

위 그림을 보시면 점선이 보이시는데요.

구조화를 도식화하기 위해 점선을 넣었지, 실제로는 화면에 점선, 실선 모두 나타나지 않습니다.

CSS가 다른 레이아웃 알고리즘과는 다르게 Grid 레이아웃이 적용된 컨테이너 안에 있는 걸 나눠서 행과 열로 구분하는데요.

반대로 Table 레이아웃은 행과 열을 tr, td 태그로 분명하게 지정해야 합니다.

CSS Grid 맛보기는 끝났으니까요, 본격적으로 들어가 보겠습니다.


2. Grid flow

Grid를 적용하려면 CSS로 아래와 같이 지정하면 됩니다.

.wrapper {
  display: grid;
}

CSS Grid는 디폴트로 한 개의 열(column)을 가지는데요, 반면에 행(rows)은 grid wrapper 안에 있는 block 기반 자식 요소의 숫자에 따라 무수히 많이 만들 수 있습니다.

이걸 전문용어로는 암시적 그리드(implicit grid)라고 합니다.

암시적 그리드는 매우 다이내믹한데요, 자식 요소 수에 따라 행을 무수히 만들어 냅니다.

그리고 각 자식 요소는 그 자체로 한 개의 행(row)이 됩니다.

기본적으로 CSS Grid에 있어 grid 컨테이너의 높이(height)는 자식에 따라 달라지는데요.

자식 수가 많을수록 늘어나고 자식 수가 줄어들면 grid 컨테이너의 높이도 줄어들게 됩니다.

이 방식은 CSS Grid 방식도 아니고요, 기본 레이아웃 모드인 Flow 레이아웃 모드의 특성 때문입니다.

왜냐하면 grid 컨테이너는 그 안에 있는 게 grid 레이아웃을 따르는 거고, grid 컨테이너 자체는 Flow 레이아웃을 따르는 거기 때문입니다.

그리고 Flow 레이아웃에서는 블록 컨텐츠는 수직으로 grow 하는 특성이 있기 때문이죠.

만약에 grid 컨테이너 자체의 높이(height)를 지정하면 어떻게 될까요?

.grid-wrapper {
  display: grid;
  height: 300px;
}

위와 같이 grid-wrapper의 height를 300 px로 지정했습니다.

자식 수가 3개 4개 일 경우를 볼까요?

위 두 개의 그림처럼 grid-wrapper의 전체 높이 300 px는 그대로이고 안의 자식 하나당 높이가 자식 수에 따라 변합니다.


3. Grid 구조화

CSS Grid는 디폴트 값으로 한 개의 열을 가진다고 했는데요.

그러면 여러 개의 열(column)을 지정하려면 어떻게 해야 할까요?

이럴 때 쓰는 것이 바로 grid-template-columns 항목입니다.

이름이 조금 길죠.

<style>
  .parent {
    display: grid;
    grid-template-columns: 25% 75%;
  }
</style>

<div class="parent">
  <div class="child">1</div>
  <div class="child">2</div>
</div>

사용법은 grid-template-columns 항목 뒤에 텍스트로 2개, 3개 등 원하는 열의 숫자만큼 구분 지어 적어주면 됩니다.

위에서는 퍼센티지로 2개의 열에 대한 너비를 구분 지었는데요.

위와 같이 25%와 75%로 완벽하게 분할된 열을 볼 수 있을 겁니다.

참고로 그림상 점선은 이해를 돕기 위해 추가한 겁니다.

grid-template-columns 항목에 들어갈 수 있는 거는 width를 지정하는 모든 단위인 pixels, rems, viewport units, 퍼센티지 등 모든 게 들어갈 수 있고, Grid 레이아웃에만 적용되는 fr 이라는 단위도 들어갈 수 있습니다.

fr 이라는 단위는 fraction이라는 뜻으로 말 그대로 1/2, 2/3 같이 분수를 나타냅니다.

만약 grid-template-columns: 1fr 3fr; 이렇게 지정했으면 어떻게 해석해야 할까요?

첫 번째 열은 1개의 공간을, 두 번째 열은 3개 공간을 나타냅니다.

그러면 분수로 나타내면 총 4개 중 첫 번째 열은 1/4가 되고 두 번째 열은 3/4을 차지하는 공간을 가지게 됩니다.

결과적으로, grid-template-columns: 25% 75%;와 같은 뜻이 됩니다.

그런데 fr 단위를 쓰는 이유가 있는데요.

왜냐하면 퍼센티지나 숫자로 너비를 지정한 단위는 강제 제약 조건이 되고, fr 단위는 CSS Grid 레이아웃에 있어 상황에 맞게 유연하게 작동하게끔 하는 특징이 있습니다.

fr 단위와 % 단위의 차이를 그림을 보면서 이해해 보도록 하겠습니다.

위 그림과 같이 %를 이용한 방식에서 바깥 그리드 컨테이너의 크기를 줄였을 때는 아래와 같이 작동됩니다.

위와 같이 비율대로 줄여져서 첫 번째 열에 들어간 그림이 overflow가 되었네요.

다음으로 fr 방식입니다.

이랬던 그림이 grid 컨테이너를 줄이면 아래와 같이 변합니다.

위와 같이 첫 번째 열은 그 안에 있는 항목의 최소 컨텐트 사이즈(minimum content size)를 감안해서 shrink 되는군요.

이제 fr 단위를 쓰는 이유를 짐작할 수 있을 겁니다.

fr 단위가 flex-grow와 같은 방식으로 작동하기 때문입니다.

fr은 남는 여유 공간을 분배하는 역할을 합니다.

그래서 각 열의 내용에 따라 남는 여유 공간을 계산해서 분배하기 때문에 첫 번째 그림이 overflow가 발생되지 않게 됩니다.

반면에, % 바식은 단순하게 숫자를 나누어서 계산하기 때문에 overflow가 발생하는 겁니다.

fr 단위와 % 단위를 비교하는 다른 예는 바로 gap을 넣어 보는 건데요.

먼저, fr 단위의 예입니다.

위 그림을 보시면 1fr 3fr이기 때문에 1/4, 3/4으로 구분 지어 나누어지는데요.

gap을 16px를 줬습니다.

그런데, fr 방식이기 때문에 전체 grid 컨테이너 안에서 합리적이며 유연하게 나머지 공간을 분배하는 데 반해, 아래 그림은 %를 사용한 방식으로 두 번째 열이 overflow가 되었습니다.

% 방식은 엄격한 계산 룰에 따르기 때문에 예외가 없네요.

% 방식은 전체 grid의 너비를 계산하고 있고, 두 개의 열이 100%이기 때문에 여기에 gap에 해당 되는 16px이 들어갈 공간이 없기 때문입니다.

그래서 overflow가 발생 되는 거죠.


gap vs grid-gap

원래 Grid Layout의 gap은 이름이 grid-gap 이었는데요.

이게 너무 유용한 항목이라 flexbox에도 적용 시켜달라는 대중들의 요구에 의해 gap이란 이름으로 바뀌게 된 겁니다.


4. 암시적 행 vs 명시적 행

만약에 행이 두 개인 그리드에서 더 많은 자식 요소를 추가하면 어떻게 될까요?

위 그림과 같이 grid는 자동으로 한 개의 행을 추가로 만듭니다.

grid 알고리즘은 자식들 모두 grid 셀이 되는 걸 보장하는 방식으로 작동합니다.

그래서 위와 같이 행이 두 개인 그리드에서 세 번째 셀은 혼자인데도 한 개의 행이 추가로 생긴 이유입니다.

만약, 표시할 사진의 숫자가 다이내믹할 때 Flexbox보다 Grid로 틀을 짜면 아주 유연하게 새로운 행이 생기면서 모든 사진을 보여줄 겁니다.

맨 처음에 말했듯이 Grid 레이아웃은 열을 지정하면 행은 자동으로 계산된다고 했는데요.

사실 grid-template-rows라고 직접 지정할 수 있습니다.

grid-template-rowsgrid-template-columns 두 개를 동시에 사용하면 사용자는 완벽하게 명시적인 구조체의 틀을 만들 수 있습니다.


반복자

만약 아래와 같은 달력 UI를 만든다고 할 때, 어떻게 하실 건가요?

한 행은 7일이기 때문에 grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;처럼 7개만 지정하면 나머지는 두 번째 행, 세 번째 행이 자동으로 생길 건데요.

1fr을 7번 반복하는 건 너무 귀찮죠.

그래서 CSS 자체적으로 반복자를 제공해 줍니다.

.calendar {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
}

5. 자식 요소 배치하기

디폴트 값으로 CSS Grid는 자식 요소에 대해 맨 처음 생긴 아무것도 안 채워진 셀에 첫 번째 자식을 배치합니다.

그런데, 임의 요소에 직접 자식 요소를 배치할 수 있는 명령어가 있습니다.

아래와 같이 4 x 4 행렬이 있습니다.

첫 번째 행, 첫 번째 열을 선택하려면 위 그림과 같이 grid-row: 1;grid-column: 1; 같이 직접 지정하면 됩니다.

아래 그림은 3번째 행과 두 번째 열의 시작과 세 번째 열을 선택한 건데요.

grid-column: 2 / 4; 이게 뭘 뜻하는 걸까요?

절대 나눗셈이 아닙니다.

바로 다음의 줄임말입니다.

grid-column-start: 2;
grid-column-end: 4;

grid-column-start와 grid-column-end를 두 개를 축약해서 표현하는 방법입니다.

여기서 세 번째 행의 두 번째 세 번째 열이 선정되었는데, 숫자는 2 / 4인 이유가 이상한데요.

정확한 표현은 아래와 같이 선의 시작 순서입니다.

위 그림을 보시면 왜 2 / 4 인 게 명확하죠.

가상의 점선이 있고 그걸 선이라고 하면 1분부터 그 선이 시작되기 때문입니다.

이 선에 따른 숫자가 구현되는 걸 꼭 외우셔야 합니다.

우리가 선을 숫자를 왼쪽부터 1, 2, 3 이런 식으로 붙였는데요.

만약 오른쪽부터 붙이면 -1, -2, -3 이런 식이 됩니다.

그래서 아래그림처럼 해도 문제가 없는 거죠.

꼭 외우셔야 할 거는 선의 숫자이지 몇 번째 셀인지 외우면 안 되는 겁니다.

첫 번째 선, 두 번째 선 이런 식으로 외우셔야 합니다.


6. Grid areas

지금 배울 건 바로 Grid의 가장 멋진 기능인데요.

다음과 같은 레이아웃으로 페이지를 구성한다고 합시다.

.grid {
  display: grid;
  grid-template-columns: 2fr 5fr;
  grid-template-rows: 50px 1fr;
}
.sidebar {
  grid-column: 1;
  grid-row: 1 / 3;
}
header {
  grid-column: 2;
  grid-row: 1;
}
main {
  grid-column: 2;
  grid-row: 2;
}

그러면 위와 같이 CSS를 짤건데요.

grid areas를 이용하면 다음과 같이 짤 수 있습니다.

.parent {
  display: grid;
  grid-template-columns: 2fr 5fr;
  grid-template-rows: 50px 1fr;
  grid-template-areas:
    'sidebar header'
    'sidebar main';
}
.child1 {
  grid-area: sidebar;
}
.child2 {
  grid-area: header;
}
.child3 {
  grid-area: main;
}

해당 grid-area를 이름으로 지정했고

grid-template-areas를 이용해서 아까 지정한 이름으로 구조화했습니다.

이렇게 하면 좀 더 쉽게 span을 사용할 수 있습니다.

위 코드에서 보시면 sidebar 위 아래로 span 된 걸 보실 수 있습니다.

참고로 2 x 2에서 한 구역을 아무것도 지정하지 않으려면 그 자리에 .을 넣으면 됩니다.

grid-template-areas:
  'sidebar header'
  '.       main';

위와 같이 (.)을 넣으시면 됩니다.


7. 정렬

그리드 컨테이너의 크기가 180px이고 두 개의 열의 합이 180px 보다 작다면 분명히 남는 공간이 생기는데요.

이렇게 되면 우리가 Flexbox에서 배웠던 justify-content 속성을 여기서 갖다가 쓸 수 있습니다.


7-1. justify-content

사실 grid의 justify-content는 flexbox에서 가져온 게 맞습니다.

해당 값으로는 start(기본값), center, end, space-between, space-around, space-evenly가 올 수 있습니다.

flexbox와의 차이는 grid에서는 행 자체를 정렬하는 것이지 아이템을 정렬하는 게 아니라는 겁니다.

아이템을 정렬하려면 justify-items 항목을 이용하면 됩니다.

Flexbox에서는 align-items였는데 Grid에서는 justify-items 이름이네요.

둘 차이가 명확히 이해되어야 합니다.

지난 시간에 배웠던 content vs items 부분을 읽어 보시면 둘의 차이를 이해할 수 있을 겁니다.


7-2. justify-items

justify-items의 기본 특성은 Flow 레이아웃의 기본 특성인 stretch입니다.

지정하지 않으면 기본적으로 stretch 방식으로 작동한다는 뜻입니다.

다른 값으로 start, center, end가 올 수 있습니다.

위와 같이 작동합니다.

그러면 개별 셀의 특성은 어떻게 정렬할까요?

바로 justify-self 입니다.


7-3. justify-self

justify-self는 기본값이 stretch입니다.

지정하지 않으면 기본적으로 stretch 방식으로 작동한다는 뜻입니다.

다른 값으로 start, center, end가 올 수 있습니다.

위와 같이 작동합니다.


7-4. align-content

justify가 수평 방향으로 정렬했다면 align 방식은 수직 방향으로 정렬합니다.

해당 값으로는 start, center, end, space-between, space-around, space-evenly가 올 수 있습니다.


7-5. align-items

해당 값으로는 stretch, start, center, end 값이 올 수 있습니다.


7-6. align-self

justify-self 처럼 align-self도 있으니까 꼭 확인해 보시기 바랍니다.

align-self는 한 개의 그리드 셀의 수직 위치에 대해 관여하는 겁니다.


8. 두 줄짜리 센터링 트릭

Grid 레이아웃을 배웠으면 이 트릭을 꼭 머릿속에서 심도 있게 생각해 보고 실전에 사용하시기 바랍니다.

.parent {
  display: grid;
  place-content: center;
}

이렇게 하면 아이템을 가운데 정렬할 수 있습니다.

사실 place-contetn는 줄임말인데요.

.parent {
  justify-content: center;
  align-content: center;
}

justify-content와 align-content를 각각 center로 놓는다는 얘기입니다.

다시 한번 맨 처음 코드인 아래 코드를 분석해 보면

.parent {
  display: grid;
  place-content: center;
}

display로 grid를 선택했고, grid-template-columns 항목이 없습니다.

그러면 당연히 한 개의 열이고 또 당연히 한 개의 행이 생성되겠죠.

그리고 place-content를 이용해서 justify-content를 center로 지정했습니다.

즉, 열의 배치를 center로 지정한다는 뜻이고, align-content를 center로 지정했다는 뜻은 행의 위치를 가운데로 위치시킨다는 뜻입니다.

결국 아이템은 Grid 컨테이너의 한가운데 위치되게 됩니다.

끝.