flyer flyer - 3 years ago 151
Javascript Question

How to create an html <table> that has a fixed header **and** pinned columns using primarily CSS

I've had really good luck applying a css transform on scroll in order to have a fixed table header. But now I would like to try the same technique when scrolling horizontally to have pinned columns.

The problem I'm running into is that the pinned columns in the header appear behind instead of on top when scrolling horizontally. The browser is deciding which DOM elements should be on top after doing the transformation. Anything I can do to control this? (yes, I've tried z-index)

EDIT I'm so sorry, my demo was messed up. I fixed it to be what I had intended

Demo



(function() {
var app = angular.module("soDemo", []);
app.controller("soDemoController", SoDemoController);

SoDemoController.$inject = ['$scope', '$document'];

function SoDemoController($scope, $document) {
var vm = {
data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
};

$scope.vm = vm;
$('.table-container').on('scroll', onScroll);

return;

/////////// IMPLEMENATION ////////
function onScroll() {
var translate = "translate(0," + this.scrollTop + "px)";
$("table thead th:not(.pinned)").css('transform', translate);

translate = "translate(" + this.scrollLeft + "px,0)";
$("table tbody .pinned").css('transform', translate);

translate = "translate(" + this.scrollLeft + "px," + this.scrollTop + "px)";
$("table thead th.pinned").css('transform', translate);
}
}
})();

.table-container {
overflow: auto;
height: 200px;
width: 300px
}

table {
table-layout: fixed;
border-spacing: 0;
}

td {
padding: 3px;
white-space: nowrap;
border-right: 1px solid #ccc;
}

th {
background: #999;
}

th.pinned {
background: #ccc;
}

td.pinned {
background: #eee;
}

input {
margin-top: 20px;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.js"></script>

<div class="sample" ng-app="soDemo" ng-controller="soDemoController">
<p>When you scroll horizontally, the header row scrolls on top of col0 and col1 instead of behind it.</p>
<p>When you scroll vertically, the tbody cells render over top of Col 0 and Col 1 headers instead of behind it</p>
<div class="table-container">
<table>
<thead>
<tr>
<th class="pinned">Col 0</th>
<th class="pinned">Col 1</th>
<th>Col 2</th>
<th>Col 3</th>
<th>Col 4</th>
<th>Col 5</th>
<th>Col 6</th>
<th>Col 7</th>
<th>Col 8</th>
<th>Col 9</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in vm.data">
<td class="pinned">Data {{item}}</td>
<td class="pinned">Data {{item}}</td>
<td>Data {{item}}</td>
<td>Data {{item}}</td>
<td>Data {{item}}</td>
<td>Data {{item}}</td>
<td>Data {{item}}</td>
<td>Data {{item}}</td>
<td>Data {{item}}</td>
<td>Data {{item}}</td>
</tr>
</tbody>
</table>
</div>
</div>




Answer Source

I think it is still a z-index issue.

Thanks to @sascha10000 they got me thinking about what was missing. If you add position: relative to the th.pinned class in your css and also add a positive z-index. (i.e. z-index:20) It appears to work.

(function() {
  var app = angular.module("soDemo", []);
  app.controller("soDemoController", SoDemoController);

  SoDemoController.$inject = ['$scope', '$document'];

  function SoDemoController($scope, $document) {
    var vm = {
      data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    };

    $scope.vm = vm;
    $('.table-container').on('scroll', onScroll);

    return;

    /////////// IMPLEMENATION ////////
    function onScroll() {
      var translate = "translate(0," + this.scrollTop + "px)";
      $("table thead th:not(.pinned)").css('transform', translate);

      translate = "translate(" + this.scrollLeft + "px,0)";
      $("table tbody .pinned").css('transform', translate);

      translate = "translate(" + this.scrollLeft + "px," + this.scrollTop + "px)";
      $("table thead th.pinned").css('transform', translate);
    }
  }
})();
.table-container {
  overflow: auto;
  height: 200px;
  width: 300px
}

table {
  table-layout: fixed;
  border-width: 0;
  border-spacing: 0;
}

td {
  padding: 3px;
  white-space: nowrap;
  border-right: 1px solid #ccc;
}

th {
  background: #999;
}

th.pinned {
  position: relative; /**** <===  added this ****/
  z-index: 20;        /**** <===  added this ****/
  background: #ccc;
}

td.pinned {
  background: #eee;
}

input {
  margin-top: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.js"></script>

<div class="sample" ng-app="soDemo" ng-controller="soDemoController">
  <p>When you scroll horizontally, the header row scrolls on top of col0 and col1 instead of behind it.</p>
  <p>When you scroll vertically, the tbody cells render over top of Col 0 and Col 1 headers instead of behind it</p>
  <div class="table-container">
    <table>
      <thead>
        <tr>
          <th class="pinned">Col 0</th>
          <th class="pinned">Col 1</th>
          <th>Col 2</th>
          <th>Col 3</th>
          <th>Col 4</th>
          <th>Col 5</th>
          <th>Col 6</th>
          <th>Col 7</th>
          <th>Col 8</th>
          <th>Col 9</th>
        </tr>
      </thead>
      <tbody>
        <tr ng-repeat="item in vm.data">
          <td class="pinned">Data {{item}}</td>
          <td class="pinned">Data {{item}}</td>
          <td>Data {{item}}</td>
          <td>Data {{item}}</td>
          <td>Data {{item}}</td>
          <td>Data {{item}}</td>
          <td>Data {{item}}</td>
          <td>Data {{item}}</td>
          <td>Data {{item}}</td>
          <td>Data {{item}}</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download