본문 바로가기
frontend

가로 세로 스크롤 되는 테이블 만들기

by marble25 2023. 4. 24.

Frontend 작업을 하다 보니 다음과 같은 테이블이 필요했다.

  1. 상하 스크롤이 필요. 단, 헤더 영역은 위에 고정
  2. 왼쪽, 오른쪽 영역은 고정된 상태로 좌우 스크롤이 필요.

 

즉, 스크롤 영역이 각자 다르기 때문에 테이블 자체가 쪼개져 있어야 한다는 뜻이다.

나는 이 문제를 해결하기 위해 다음과 같이 생각해 보았다.

  1. 6개 영역을 div로 처리하여 css grid를 활용, 테이블처럼 느껴지도록 배치
  2. 크게 왼쪽, 오른쪽, 가운데 영역으로 분할. 왼쪽, 오른쪽, 가운데 각각에 대해 스크롤 영역을 배치. 스크롤바는 오른쪽 영역에만 보여준다. 어느 하나라도 스크롤이 이루어진다면 다른 컴포넌트에 대해서도 스크롤 (js 이용)
  3. 크게 왼쪽, 오른쪽, 가운데 영역으로 분할되는 것은 같지만 스크롤 영역을 전체 div에 대해 잡는다.

1번안의 경우 th나 td 요소에 대해 같은 속성을 주는 것이 굉장히 불편했다. 또한, 코드를 보았을 때 쉽게 이해가 되지 않아서 진작 제외되었다.

2번안의 경우 기존에 구현했던 방법이다. 사실 3번안인 전체 div에 대해서 스크롤이 동작하도록 하는 것이 쉬워 보였지만, height와 overflow가 계속 꼬여서 도중에 포기하고 2번 안으로 갔다. 동작 자체는 정상적으로 했으나, js를 통해 전체 컴포넌트가 스크롤 되는 것‘처럼’ 보이는 것은 부자연스러웠다. 다양한 시도를 해 보았지만, 딜레이가 발생하고 부자연스러워 보이는 것, 그리고 버그 발생 가능성이 너무 많은 것을 해결하지 못했다.

따라서 기존 코드를 많이 바꿀 생각을 하고 3번 안을 시도해 보았다.

HTML

<div>
  <table class="left">
    <thead>
      <tr>
        <th>왼쪽1</th>
        <th>왼쪽2</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>왼11</td>
        <td>왼12</td>
      </tr>
      <tr>
        <td>왼21</td>
        <td>왼22</td>
      </tr>
      <tr>
        <td>왼31</td>
        <td>왼32</td>
      </tr>
      <tr>
        <td>왼41</td>
        <td>왼42</td>
      </tr>
      <tr>
        <td>왼51</td>
        <td>왼52</td>
      </tr>
    </tbody>
  </table>
  <table class="center">
    <thead>
      <tr>
        <th>가운데1</th>
        <th>가운데2</th>
        <th>가운데3</th>
        <th>가운데4</th>
        <th>가운데5</th>
        <th>가운데6</th>
        <th>가운데7</th>
        <th>가운데8</th>
        <th class="empty"></th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>가11</td>
        <td>가12</td>
        <td>가13</td>
        <td>가14</td>
        <td>가15</td>
        <td>가16</td>
        <td>가17</td>
        <td>가18</td>
        <td class="empty"></td>
      </tr>
      <tr>
        <td>가21</td>
        <td>가22</td>
        <td>가23</td>
        <td>가24</td>
        <td>가25</td>
        <td>가26</td>
        <td>가27</td>
        <td>가28</td>
        <td class="empty"></td>
      </tr>
      <tr>
        <td>가31</td>
        <td>가32</td>
        <td>가33</td>
        <td>가34</td>
        <td>가35</td>
        <td>가36</td>
        <td>가37</td>
        <td>가38</td>
        <td class="empty"></td>
      </tr>
      <tr>
        <td>가41</td>
        <td>가42</td>
        <td>가43</td>
        <td>가44</td>
        <td>가45</td>
        <td>가46</td>
        <td>가47</td>
        <td>가48</td>
        <td class="empty"></td>
      </tr>
      <tr>
        <td>가51</td>
        <td>가52</td>
        <td>가53</td>
        <td>가54</td>
        <td>가55</td>
        <td>가56</td>
        <td>가57</td>
        <td>가58</td>
        <td class="empty"></td>
      </tr>
    </tbody>
  </table>
  <table class="right">
    <thead>
      <tr>
        <th>오른쪽1</th>
        <th>오른쪽2</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>오11</td>
        <td>오12</td>
      </tr>
      <tr>
        <td>오21</td>
        <td>오22</td>
      </tr>
      <tr>
        <td>오31</td>
        <td>오32</td>
      </tr>
      <tr>
        <td>오41</td>
        <td>오42</td>
      </tr>
      <tr>
        <td>오51</td>
        <td>오52</td>
      </tr>
    </tbody>
  </table>
</div>

테이블이라서 코드가 좀 길다. 여기서 주목해야 할 점은

  • 테이블을 감싸는 div가 하나 있다.
  • table을 left, center, right 3개로 구성했다.
  • center 테이블의 맨 마지막 열에는 항상 empty td / th가 들어가 있다.

CSS

div {
  display: flex;
  overflow: auto;
  max-height: 100px;
  min-width: 100px;
}

table {
  table-layout: fixed;
  border-collapse: collapse;
}

table th {
  background-color: yellow;
  position: sticky;
  top: 0;
}

table td {
  border-bottom: 1px solid gray;
}

th:not(.empty), td:not(.empty) {
  text-align: left;
  width: 70px;
  min-width: 70px;
  max-width: 70px;
}

.left, .right {
  position: sticky;
  background-color: white;
  flex: none;
  z-index: 10;
}

.left {
  left: 0px;
  border-right: 1px solid gray;
}

.center {
  flex: 1;
}

.empty {
  width: 100%;
}

.right {
  right: 0px;
  border-left: 1px solid gray;
}

주목할 점은 다음과 같다.

  • 가장 바깥의 div는 flex 처리 되어있다. 또, max-height와 min-width가 주어져 있다. max-height는 y축 방향으로의 scroll을 위함이고, min-width는 테이블이 너무 작아지는 것을 막는다.
  • left, right table은 flex: none, center table은 flex: 1로 매겨져 있다. left, right는 크기가 정해져 있고, center는 컨텐츠 길이에 따라서 grow / shrink 가능하다.
  • th는 position: sticky와 top: 0, z-index가 매겨져 있다. 테이블 내에서 position sticky가 매겨지고 top / bottom / left / right가 매겨져 있으면 스크롤에 관계 없이 해당 위치에 달라붙어 있게 된다. 이를 이용해서 고정 헤더를 만들 수 있다.
  • 모든 th와 td는 width가 정해져 있고, 가장 마지막 empty 열은 width가 100%이다. center table은 flex: 1이기 때문에 flexible한 width를 가진다. 만약 가운데 열의 개수가 너무 적더라도 min-width에서 남는 부분을 채울 수 있게 된다.

동작

위와 같은 구현을 통해 js 코드 한 줄 없이 깔끔하게 복잡한 테이블을 구현할 수 있었다.

https://jsfiddle.net/ljiho1998/4msxktbg/57/

 

center table에 표시할 요소가 많을 때

center table에 표시할 요소가 적을 때

 

 

'frontend' 카테고리의 다른 글

Canvas vs Svg  (0) 2023.09.10
JS로 파일 다운로드  (0) 2023.09.10
[TIL] html에서 돋보기 뷰 구현하기  (0) 2023.04.03
[팁] IntersectionObserver에 대해 알아보자!  (0) 2023.01.10
[팁] javascript console 객체 알아보기  (0) 2022.03.01