CSS tableスクロールBOX

# CSS tableスクロールBOX

レスポンシブ等でスマホに表を掲載する際、表の組み方を工夫して横幅に収まる場合は良いのですが、どうしても画面に収まりきらず、overflow:auto;で横スクロールボックスにして対応せざるを得ないことがあります。
しかし、スマホでは画面を見てすぐスクロール領域と分からないため、見切れているコンテンツがあることを知らせる境界の影などガイドを表示してユーザビリティを向上させたいところです。

今回はそのためだけにjsを使ってパフォーマンスを落とすこともしたくなかったので、CSSだけで実装できないか情報を探して見つけた[こちらのサイト](https://dbushell.com/2016/03/04/css-only-responsive-tables/)を参考にサンプルを組んでみました。

## コードと解説

```ruby:sample.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Document</title>
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<style>
body {
  margin: 0;
  padding: 12px;
}

/* table */
table {
  width: 680px;
  border-collapse: separate;
  border-spacing: 0 2px;
  border-top: 1px solid rgba(0,0,0,0.2);
}
tbody th,
tbody td {
  padding: 5px 10px;
  border-bottom: 1px solid rgba(0,0,0,0.2);
}
tbody th:not(:first-child),
tbody td:not(:first-child) {
  border-left: 1px solid rgba(0,0,0,0.2);
}
tbody th {
  background-color: rgba(115, 145, 185, 0.1);
}

/* scroll box */
.scroll_box {
  overflow-x: auto;
  width: 100%;
  margin: 1em auto;
/*
  padding: 2px 0;
  border-top: 4px solid rgba(68, 85, 118, 0.3);
  border-bottom: 2px solid rgba(68, 85, 118, 0.3);
*/
}
.scroll_box::-webkit-scrollbar {
  height: 3px;
}
.scroll_box::-webkit-scrollbar-track {
  background: #F1F1F1;
}
.scroll_box::-webkit-scrollbar-thumb {
  background: #BCBCBC;
}

/* appearance 1 */
.scroll_box.appearance-1 {
  background: radial-gradient(ellipse at left, rgba(0, 0, 0, 0.2) 10%, rgba(0, 0, 0, 0) 75%) 0 center,
              radial-gradient(ellipse at right, rgba(0, 0, 0, 0.2) 10%, rgba(0, 0, 0, 0) 75%) 100% center,
              url('data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cpath%20fill%3D%22%23005bac%22%20d%3D%22M100%200%20L50%2050%20L100%20100%20Z%22%20%2F%3E%3C%2Fsvg%3E') 0% 50%,
              url('data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cpath%20fill%3D%22%23005bac%22%20d%3D%22M0%200%20L50%2050%20L0%20100%20Z%22%20%2F%3E%3C%2Fsvg%3E') 100% 50%;
  background-size: 10px 110%, 10px 110%, 14px 14px, 14px 14px;
  background-attachment: scroll, scroll, scroll, scroll;
  background-repeat: no-repeat;
}
.scroll_box.appearance-1 table {
  background: radial-gradient(ellipse at left, rgba(255,255,255,1) 30%, rgba(255,255,255,0) 70%) 0 center,
              radial-gradient(ellipse at right, rgba(255,255,255,1) 30%, rgba(255,255,255,0) 70%) 100% center;
  background-size: 30px 200%, 30px 200%;
  background-repeat: no-repeat;
}

/* appearance 2 */
.scroll_box.appearance-2 {
  background: url('data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M100%200%20L50%2050%20L100%20100%20Z%22%20%2F%3E%3C%2Fsvg%3E'),
              url('data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M0%200%20L50%2050%20L0%20100%20Z%22%20%2F%3E%3C%2Fsvg%3E'),
              linear-gradient(to right, rgba(0,0,0,0.1), rgba(0,0,0,0.1)),
              linear-gradient(to left, rgba(0,0,0,0.1), rgba(0,0,0,0.1));
  background-size: 14px 14px, 14px 14px, 20px 100%, 20px 100%;
  background-attachment: scroll, scroll, scroll, scroll;
  background-position: 0% 50%, 100% 50%, left top, right top;
  background-repeat: no-repeat;
}
.scroll_box.appearance-2 table {
  background: linear-gradient(to right, rgba(255,255,255,1) 50%, rgba(255,255,255,0) 100%) 0 center,
              linear-gradient(to left, rgba(255,255,255,1) 50%, rgba(255,255,255,0) 100%) 100% center;
  background-size: 40px 100%, 40px 100%;
  background-repeat: no-repeat;
}

/* appearance 3 解説用 */
.scroll_box.appearance-3 {
  background: url('data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M100%200%20L50%2050%20L100%20100%20Z%22%20%2F%3E%3C%2Fsvg%3E'),
              url('data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M0%200%20L50%2050%20L0%20100%20Z%22%20%2F%3E%3C%2Fsvg%3E'),
              linear-gradient(to right, rgba(0,255,255,1), rgba(0,255,255,1)),
              linear-gradient(to left, rgba(0,255,255,1), rgba(0,255,255,1));
  background-size: 14px 14px, 14px 14px, 20px 100%, 20px 100%;
  background-attachment: scroll, scroll, scroll, scroll;
  background-position: 0% 50%, 100% 50%, left top, right top;
  background-repeat: no-repeat;
}
.scroll_box.appearance-3 table {
  background: linear-gradient(to right, rgba(255,255,0,1) 50%, rgba(255,255,0,0) 100%) 0 center,
              linear-gradient(to left, rgba(255,255,0,1) 50%, rgba(255,255,0,0) 100%) 100% center;
  background-size: 40px 100%, 40px 100%;
  background-repeat: no-repeat;
  border-collapse: collapse;
  border-spacing: 0;
  border-color: #f00;
}
.scroll_box.appearance-3 th,
.scroll_box.appearance-3 td {
  background: none;
  border-color: #f00;
}
</style>
</head>
<body>

<div class="scroll_box appearance-1">
  <table>
    <tr>
      <th>th</th>
      <th>th</th>
      <th>th</th>
      <th>th</th>
      <th>th</th>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
  </table>
</div>

<div class="scroll_box appearance-2">
  <table>
    <tr>
      <th>th</th>
      <th>th</th>
      <th>th</th>
      <th>th</th>
      <th>th</th>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
  </table>
</div>

<div class="scroll_box appearance-3">
  <table>
    <tr>
      <th>th</th>
      <th>解説</th>
      <th>th</th>
      <th>th</th>
      <th>th</th>
    </tr>
    <tr>
      <th>th</th>
      <td>青 .scroll_box背景<br>スクロール領域を知らせる影</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>黄 table背景 <br>影を覆い隠すためのマスク用</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th style="background-color: #f60;">th</th>
      <td style="background-color: #f60;">セル背景色やborder は透過色にしないと<br>色がそのまま影や矢印の上に乗ってしまう</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
    <tr>
      <th>th</th>
      <td>td</td>
      <td>td</td>
      <td>td</td>
      <td>td</td>
    </tr>
  </table>
</div>

</body>
</html>
```

仕組みは .scroll_box のbackgroundに影と矢印を表示し、スクロール両端ではtableに設定したマスク背景で覆い隠すというもの。
構造的に .scroll_box より子要素のtableの方が上位レイヤーになるため、thやtdに色を付ける場合は透過色でないと背景の影は隠れてしまう。
thやtd等セルに背景色やボーダーを付けたい場合はrgbaを使い、alphaを0.1～0.3程度にすることで、なるべく.scroll_box背景が透けて見える状態にします。
境界を影で表現していますが、.scroll_box背景を透かして見せているため、境界を白いマスクで覆い隠すといったことはできません。
色の透過度の計算には[こちらのサイト](http://nullpon.moe/dev/tool/alpha_blender/)を使わせていただきました。

また、webkit系限定ですがスクロールバーがデフォルト非表示になっているのを常時表示にするために ::-webkit-scrollbarセレクタを使いスタイルを当てています。


## 参考

- [CSS only Responsive Tables](https://dbushell.com/2016/03/04/css-only-responsive-tables/)
- [アルファブレンダ](http://nullpon.moe/dev/tool/alpha_blender/)

以上、ありがとうございました。