table의 thead 고정과 tbody 스크롤 처리하기

골치아픈 테이블 스크롤 자체 템플릿 박제하기

진행중인 프로젝트가 유난히 테이블과의 싸움이다. 그 중 하나가 헤더 고정 이슈인데 <tr>이 한개가 아니고 colspan, rowspan으로 셀이 합쳐지면서 헤더가 고정이 되는 부분들이 있다보니 은근히 노가다 이슈가 있다.

image

위 그림의 table은 .table-wrapper > .result-table > .sticky-th, .stickty-th-two와 같은 구조를 가지고 있고 코드는 아래와 같다. 특정 프로젝트에 실제 사용된 코드이다보니 다른 프로젝트에 적용하려면 필수 커스터마이징이 필요하고 코드는 개인적인 참고용으로 작성하였다.

HTML

<div class="table-wrapper">
  <table width="100%" class="contentTable table-bordered result-table" style="max-height:36px;">
    <colgroup>
      <col width="8%" />
      <col width="27%" />
      <col width="13%" />
      <col width="13%" />
      <col width="13%" />
      <col width="13%" />
      <col width="13%" />
    </colgroup>
    <thead>
      <tr>
        <th class="text-center fixedHeader sticky-th" rowspan="2"></th>
        <th class="text-center fixedHeader sticky-th" rowspan="2"></th>
        <th class="text-center fixedHeader sticky-th" colspan="2"></th>
        <th class="text-center fixedHeader sticky-th" colspan="2"></th>
        <th class="text-center fixedHeader sticky-th" rowspan="2"></th>
      </tr>
      <tr>
        <th class="text-center fixedHeader2 sticky-th-two"></th>
        <th class="text-center fixedHeader2 sticky-th-two"></th>
        <th class="text-center fixedHeader2 sticky-th-two"></th>
        <th class="text-center fixedHeader2 sticky-th-two"></th>
      </tr>
    </thead>
    <tbody>
      <template v-for="(item, item_idx) in empListBySort">
        <tr v-if="lvStatus[item.finalLevel] || !item.finalLevel " :key="item_idx" class="emp-by-sort">
          <td class="text-center"></td>
          <td><img :src="item.avatarUrl" class="table_avatar" /></td>
          <td class="text-center"></td>
          <td class="text-center txt-700"></td>
          <td class="text-center"></td>
          <td class="text-center txt-700"></td>
          <td class="text-center txt-700 text-primary"></td>
        </tr>
      </template>
    </tbody>
  </table>
</div>

CSS

.table-wrapper {
  overflow-y: auto;
  border-collapse: collapse;
  height: 500px;
  padding-top: 0px;
}

.result-table {
  margin: 0px;
}

.result-table td {
  text-align: center;
}

.result-table thead .sticky-th {
  position: sticky;
  top: 0px;
}
.result-table thead .sticky-th-two {
  position: sticky;
  top: 38px;
}

.result-table thead .sticky-th:after {
  content: '';
  position: absolute;
  left: 0;
  width: 1px;
  height: 76px;
  background: #dee2e6;
}
.result-table thead .sticky-th:before {
  content: '';
  position: absolute;
  left: 0;
  width: 100%;
  height: 2px;
  background: #dee2e6;
}
.result-table thead .sticky-th:after {
  top: 0px;
}
.result-table thead .sticky-th:before {
  top: -1px;
}

.result-table thead .sticky-th-two:after {
  content: '';
  position: absolute;
  top: -2px;
  left: 0;
  width: 100%;
  height: 2px;
  background: #dee2e6;
}
.result-table thead .sticky-th-two:before {
  content: '';
  position: absolute;
  left: 0;
  width: 1px;
  height: 38px;
  background: #dee2e6;
}
.result-table thead .sticky-th-two:after {
  top: -2px;
}
.result-table thead .sticky-th-two:before {
  bottom: 0px;
}