CSS 그리드로 콘퍼런스 일정 만들기


작성자 : Mark Root-Wiley 원문 : https://css-tricks.com/building-a-conference-schedule-with-css-grid/

새로운 기술에 대해 완벽한 용도를 발견했을 때 그 기분은 말로 표현할 수 없다. CSS 그리드에 대한 유용한 입문서를 읽고 훌륭한 활용 예제를 보고 감탄할 것이다. 하지만, 프로젝트에 실제로 새로운 기술을 적용하기 시작하면... 정말 그 기술에 푹 빠지게 될 것이다.

콘퍼런스 일정표의 UI를 구현하기 위해 CSS 그리드를 사용하였다. 일정표에 각각의 세션을 표현하려면 유연한 레이아웃이 필요했다. 이 프로젝트에서 핵심 요구사항은 가로, 세로로 이루어진 2차원 평면에 다른 크기이거나 서로 다른 배치를 가지는 복잡한 자식 요소를 완벽하게 표현하는 것이다. 개념 증명(PoC: Proof of Concept) 과정에서 코드를 읽기 쉽고 재미있게 작업할 수 있는 몇 가지 기술을 발견했다. 결과로 나온 예제에는 CSS 그리드 기능의 흥미로운 사용법이 포함되어 있으며, 예제를 작성하면서 평소에 잘 알지 못했던 그리드 세부사항에 대해 알게 되었다.

시작하기에 앞서, 다른 탭을 열어 완벽한 CSS 그리드 가이드를 참조한다면 이 글을 읽는데 도움이 될 것이다.

# 레이아웃 요구사항 정의하기

필자는 매년 전세계에서 열리는 워드 프레스에 대한 다양한 주제강연이 이루어지는 컨퍼런스인 워드캠프를 위해 다음과 같은 레이아웃을 구성하였다. 각각의 세션은 크기와 모양이 다르지만, 동일한 레이아웃 시스템을 사용하여 표시된다.

img1

최종적으로 완성될 레이아웃은 위 그림과 같다.

필자는 몇 번 워드 캠프 웹사이트의 스타일링 및 일정과 관련된 일을 진행하면서 기존 HTML 테이블 레이아웃의 단점을 알고 있었다. 균일한 그리드 안에 일정이 딱 맞아 들어가지 않는다면... 🤷

우선 해결 방법을 찾기 위해, 요구사항을 나열해 보겠다.

  • 가변 길이의 세션으로 이루어진다. (설정된 시간 단위로 제한)

    3개의 방에서 1시간 동안 강연이 이루어지고, 동시에 다른 방에서는 2시간 동안 워크샵을 진행된다고 가정해보자.

  • 둘 이상의 '트랙'을 사용하는 세션이 존재할 수 있다.

    트랙은 보통 행사장의 특정 방과 연관 되어있다. 시애틀에서 열리는 워드 캠프 행사장의 경우, 2개의 방 사이에 벽을 제거하여 하나의 방으로도 사용할 수 있었다.

  • 일정에는 빈 공간을 포함할 수 있다.

    행사가 시작하기 전, 거의 막바지에 일정이 갑자기 취소되거나, 번외로 짧게 진행하는 세션은 일정에 공백이 생길 수 있다.

  • 디자인은 CSS로 쉽게 사용자 정의해서 사용할 수 있다.

    워드 캠프 웹사이트는 CSS로만 테마를 적용할 수 있다.

  • 레이아웃은 CMS 내용으로 자동으로 생성될 수 있다.

    수천개의 웹사이트에서 구조화된 세션 데이터로 레이아웃이 만들어진다. 각각의 세션 데이터가 다르기 때문에 정해진 틀안에서 HTML와 CSS로만으로는 일정을 표현하기 어려울 수 있다.

# 견고한(Solid) HTML 시작하기

CSS를 작성하기 전에 코드 변동이 별로 없는 HTML을 먼저 작성한다.

최상위 <div>.schedule클래스를 가지며, 그리드 레이아웃을 가지는 래퍼 엘리먼트이다. 세션의 시작 시간은 모두 표시되어야 하는데, 사용자가 일정표를 볼 때 각 세션이 언제 어디에서 진행되는지 바로 알아볼 수 있어야 하기 때문이다.

<h2>Conference Schedule</h2>
<div class="schedule">

  <h3 class="time-slot">8:00am</h3>
  <div class="session session-1 track-1">
    <h4 class="session-title"><a href="#">Session Title</a></h4>
    <span class="session-time">8:00am - 9:00am</span>
    <span class="session-track">Track 1</span>
    <span class="session-presenter">Presenter Name</span>
  </div>
  <!-- Sessions 2, 3, 4 -->

  <h3 class="time-slot">9:00am</h3>
  <div class="session session-5 track-1">
    <h4 class="session-title"><a href="#">Session Title</a></h4>
    <span class="session-time">9:00am - 10:00am</span>
    <span class="session-track">Track 1</span>
    <span class="session-presenter">Presenter Name</span>
  </div>
  <!-- Sessions 6, 7, 8 -->

  <!-- etc... -->

</div> <!-- end .schedule -->

# 모바일 레이아웃과 그리드 폴백 완성하기

약간의 CSS를 추가하여 만들 수 있다. 모바일 레이아웃과 CSS 그리드를 지원하지 않는 브라우저에서는 다음과 같이 보일 것이다.

img2

휴대 전화를 사용하거나, 브라우저를 확대/축소하거나, 인터넷 익스플로러 사용자들까지도 콘퍼런스에서 세션을 찾는데 아무런 문제가 없을 것이다!

# 그리드 레이아웃 추가하기

이제, 실제로 CSS 그리드를 사용해보자.

여기서 잠깐, 로빈이 작성한 "CSS 그리드로 막대 차트 만들기"를 참고하면, 그리드의 행은 차트 높이의 1%에 해당하고, 막대는 %크기 만큼 행 수와 같게 표현되는 것을 확인할 수 있다.

.chart {
  display: grid;
  grid-template-rows: repeat(100, 1fr); /* 1 row = 1%! */
}

.fifty-percent-bar {
  grid-row: 51 / 101; /* 101 - 51 = 50 => 50% */
}

일정한 간격으로 나누어진 레이아웃에서 그리드는 완벽히 동작한다. 일정을 표현하기 위해서는 시간이 행을 나누는 단위가 되며, 예제에서는 30분 단위로 나누었다. 30분 말고 원하는 시간 단위로 나누어도 상관없다. (다만, 크롬에서는 그리드의 행을 1000으로 제한하고 있는 버그를 가지고 있다.)

처음에는 로빈의 막대 차트와 유사한 구문을 사용했으며, 기본적인 계산식을 사용하여 세션을 배치하였다. 오전 8시에서 12시 사이를 30분씩 총 8개의 행으로 나누었다. 그리드 라인 번호는 0이 아닌 1부터 시작하기 때문에, 그리드의 행에 1부터 9까지의 번호가 매겨질 것이다.

img3

그리드 라인에 번호가 매겨져 있으며, 하나의 열로 이루어진 가장 기본적인 버전이다.(예제 보기)

.schedule {
  display: grid;
  grid-template-rows: repeat(8, 1fr);
}

.session-1 {
  grid-row: 1 / 3; /* 오전 8시 - 9시, 3 - 1 = 2 30-minute increment */
}

.session-2 {
  grid-row: 3 / 6; /* 오전 9시 - 10시 30분, 6-3 = 3 30-minute increments */
}

위 방식의 문제점은 그리드에 행이 많아지면, 아이템을 배치하는 것이 매우 추상적이게 되고 혼란스러워진다.

이 문제점을 해결하기 위해 그리드 라인 번호를 매기지 않고, 각 라인의 해당하는 시간대를 기준으로 예측 가능한 이름을 붙였다.

img4

일정 그리드 라인에 의미 있는 이름을 붙여 세션을 배치하기 훨씬 쉬워졌다.(예제 보기)

.schedule {
  display: grid;
  grid-template-rows:
    [time-0800] 1fr
    [time-0830] 1fr
    [time-0900] 1fr
    [time-0930] 1fr
    [time-1000] 1fr
    [time-1030] 1fr;
    /* etc...
        Note: Use 24-hour time for line names */
}

.session-1 {
  grid-row: time-0800 / time-0900;
}

.session-2 {
  grid-row: time-0900 / time-1030;
}

이해하기 훨씬 쉬워졌다. 세션의 시간을 표현하기 위한 행의 갯수를 복잡하게 수학적으로 계산하지 않으며, 워드 프레스에 저장된 정보를 가지고 명명된 그리드 라인을 사용하여 세션의 레이아웃 스타일을 지정해줄 수 있다. 시작 시간과 종료 시간에 따라 명명된 그리드 라인을 스타일로 지정해준다면 그리드에 잘 표현될 것이다.

일정에는 여러 개의 트랙이 존재하고, 각 트랙마다 열로 표현된다. 그리드에서 행 라인에 의미 있는 시간 이름을 붙인 것처럼 각 열 라인에 대해 트랙 이름을 붙여 비슷한 방식으로 동작하도록 해보자. 열의 맨 첫번째는 시간 정보를 제목으로 표시하는 열이 삽입될 것이다.

.schedule { /* continued */
  grid-template-columns:
    [times] 4em
    [track-1-start] 1fr
    [track-1-end track-2-start] 1fr
    [track-2-end track-3-start] 1fr
    [track-3-end track-4-start] 1fr
    [track-4-end];
}

여기서는, 한 단계 더 나아가 각 라인에 두 개의 이름(-start, -end)을 붙였다. -start는 시작하는 트랙이고 -end는 끝나는 트랙이다. 이렇게 나누면, 세션이 둘 이상의 열(트랙)에 걸쳐 진행되는 경우 명확하게 표현할 수 있다.

시간과 트랙 기반으로 그리드 라인이 정의됨에 따라, 이제는 세션의 시간과 트랙 정보만 안다면 모두 배치할 수 있다.

.session-8 {
  grid-row: time-1030 / time-1100;
  grid-column: track-2-start / track-3-end; /* spanning two tracks! */
}

코드를 모두 합쳐보면, 조금 길지만 읽기 쉬운 코드를 얻을 수 있다.

@media screen and (min-width: 700px) {
  .schedule {
    display: grid;
    grid-gap: 1em;
    grid-template-rows:
      [tracks] auto
      [time-0800] 1fr
      [time-0830] 1fr
      [time-0900] 1fr
      [time-0930] 1fr
      [time-1000] 1fr
      [time-1030] 1fr
      [time-1100] 1fr
      [time-1130] 1fr
      [time-1200] 1fr;
    grid-template-columns:
      [times] 4em
      [track-1-start] 1fr
      [track-1-end track-2-start] 1fr
      [track-2-end track-3-start] 1fr
      [track-3-end track-4-start] 1fr
      [track-4-end];
  }

  .time-slot {
    grid-column: times;
  }
}
<div class="session session-1 track-1" style="grid-column: track-1; grid-row: time-0800 / time-0900;">
  <!-- details -->
</div>
<div class="session session-2 track-2" style="grid-column: track-2; grid-row: time-0800 / time-0900">
  <!-- details -->
</div>
<!-- etc... -->

마지막 HTML 코드는 각각의 세션에 맞게 배치하기 위해 인라인 스타일로 작성하였다. 인라인 스타일 방식을 선호하지 않고 최신 브라우저에서 작업하고 있다면 CSS 변수를 사용하여 그리드 라인 이름을 넘겨줄 수있는 방법도 있다.

# 퀵 노트 : fr 단위를 사용하였을 때 자동으로 행 높이 값 설정하기

행 높이를 정의하는데 fr 단위를 사용하는 것이다. 1fr을 사용하면, 가장 긴 내용을 가지는 행의 높이 값으로 모든 행의 높이가 동일하게 결정된다(fr에 대한 W3C 스펙 참고). 시간에 비례하여 동일한 높이를 가지는 일정을 표시할 수 있지만, 전체 레이아웃이 매우 커지는 결과를 초래할 수 있다.

img5

1fr을 사용하면 그리드에서 가장 큰 행에 의해 결정된 높이가 모든 행의 높이로 설정된다.(예제 보기)

예를 들어, 일정표가 오전 7시 부터 오후 6시 까지 15분 단위로 나뉘어져 있어 총 48개의 행으로 이루어져있다고 가정하자. 이 경우에는 행의 높이를 auto로 지정하여 내용에 따라 각 행의 높이를 맞춰주고 싶을 것이다.

img6

auto를 사용하면 내용의 높이에 따라 각 행이 높이가 결정된다.(예제 보기)

# 접근성에 대한 한마디

특정 CSS Grid 기술은 실제로 접근성 문제를 가지고 있다. 소스 코드의 순서대로 표시되지 않고 그리드 레이아웃에 의해 화면에 보이는 순서가 변경되어 보여질 때가 있는데, 특히 키보드 네비게이션을 사용하는 사람들에게 문제가 발생될 수 있다.

그리드 시스템은 그리드에 임의로 아이템을 배치하는 기능을 사용하기 때문에 사용 시 어느 정도 주의가 필요하다.

예제 코드에서는 제목에 해당하는 소스 코드와 시작 시간을 표시해주는 레이아웃이 일치해보여 안전하게 사용한 것처럼 보인다. 그러나 시간 별로 정보를 정렬하는 것은 맞지만, TAB키 사용 시 다음 행으로 이동하는 것이 아니라 다음 열을 이동한다고 생각할 수 있다. 즉, 사용자는 다음 시간대로 이동하는 것이 아니라, 같은 시간대 진행하는 다음 트랙으로 이동한다고 기대할 수 있다.

항상 접근성을 신중히 고려하자.

# 스티키(sticky) 트랙 이름 추가하기

마지막으로, 각 열의 맨 위에 테이블 헤더에 해당하는 트랙 이름을 추가해보자. 세션 트랙이 이미 표시되고 있기 때문에, aria-hidden="true"를 사용하여 보조 기술(AT: Assistive Technology) 사용자에게 노출되지 않도록 설정하였다.

트랙 이름은 그리드의 첫번째 행으로 들어가는데, 편하게 "tracks"라고 이름지었다. position: sticky를 사용하면 오버플로우 문제가 나타나지 않는 한, 사용자가 스크롤하는 동안에도 계속 화면에 표시된다.

<span class="track-slot" aria-hidden="true" style="grid-column: track-1;">Track 1</span>
<span class="track-slot" aria-hidden="true" style="grid-column: track-2;">Track 2</span>
<span class="track-slot" aria-hidden="true" style="grid-column: track-3;">Track 3</span>
<span class="track-slot" aria-hidden="true" style="grid-column: track-4;">Track 4</span>
.track-slot {
  display: none; /* only visible with Grid layout */
}

@supports( display:grid ) {
  @media screen and (min-width:700px) {
    .track-slot {
      grid-row: tracks;
      display: block;
      position: sticky;
      top: 0;
      z-index: 1000;
      background-color: rgba(255,255,255,.9);
    }
  }
}

최종적으로 예제를 완성하였다.✨

# 결과물

앞서 언급한 모든 내용을 취합한 코드는 링크에서 확인할 수 있다.

# 바로 시작해보자

일정표는 CSS 그리드를 만족스럽게 사용한 예제 중 하나이다. 그리드 라인에 데이터 기반의 의미있는 이름을 붙이는 방식이 마음에 들었고, CMS 요구사항에도 어려움 없이 완벽하게 들어맞는 점도 좋았다.

달력 레이아웃모노폴리 보드게임 레이아웃를 본 적이 있다. 일정표를 구현하는 것 외에도 축구 경기장, 타임 라인, 레스토랑 테이블 및 극장 좌석 등 다른 종류의 데이터를 사용하여 그리드 레이아웃으로 표현할 수 있는 것들이 무엇이 있을까? 바로 시작해보자.


조정은, FE Development Lab2019.07.03Back to list