3

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)

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>
flyer
  • 1,414
  • 12
  • 13
  • 2
    It works as you want in the Snippet...? The header rows are in front of the column headers. Or did you mean it the other way around? Maybe the z-index is still an option: https://philipwalton.com/articles/what-no-one-told-you-about-z-index/ I had an very similar issue and I tricked my way into it with z-index and positioning. – sascha10000 Jul 25 '17 at 23:29
  • I totally messed up the demo at the time of your comment (the pinned cells in the tbody were supposed to scroll vertically but not horizontally, similar to what you normally see with pinned columns) Sorry, was multi-tasking at home at the time. I've cleaned it up to show the issue and your comment lead to the answer by another user. Thanks so much. – flyer Jul 26 '17 at 02:19
  • The z-index article that you linked was excellent. Makes much more sense now. Stacking contexts... – flyer Jul 26 '17 at 02:34
  • Possible duplicate of [Table with fixed header and fixed column on pure css](https://stackoverflow.com/questions/15811653/table-with-fixed-header-and-fixed-column-on-pure-css) – Rob Jul 26 '17 at 02:35
  • I had already reviewed that SO post and the solutions are not nearly as clean IMO. And I'm not taking credit for the overall idea, as I am doing the pinned columns in the same way as this answer did for fixed headers: https://stackoverflow.com/a/25902860/3512462. I'm just extending his idea to pinned columns. – flyer Jul 26 '17 at 02:43
  • I have an issue where the `th` borders don't appear to **translate** with it. Any idea how I can fix it? – Vishnu Y S Apr 18 '18 at 05:28

3 Answers3

3

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>
Joe B.
  • 1,124
  • 7
  • 14
  • This is brilliant! I'll be reading the z-index article from @sascha10000 tonite. I fought with this for hours this afternoon and was about to throw in the towel. Works beautifully! This is now, the only CSS only solution I've ever come across to have a fixed header and pinned columns. I would imagine that many people would be interested in this elegant solution. Because of that I've cleaned up my demo code to show the error better. If you copy it over and then re-apply your fix, it's a much nicer demo of the solution and I'm sure the votes will rain down! :) – flyer Jul 26 '17 at 02:15
  • FYI: I just added border-spacing: 0; on the table CSS so that you no longer see the cells scroll behind the fixed/pinned cells. – flyer Jul 26 '17 at 02:38
  • 1
    Great solution. It was late last night, so I didn't have the time to come up with a solution. Im happy that the article helped you. – sascha10000 Jul 26 '17 at 11:08
0

This is the nature of how tables work you can emulate a table to achieve this functionality but you can't to my knowledge use a table because tables tend to ignore z-index values.

Lime
  • 13,400
  • 11
  • 56
  • 88
0

Have you checked out position: sticky? I'm fairly certain this is exactly what you're looking for. You'll need to set a z-index on the row/column to keep it above the rest.

position: sticky on MDN

jhpratt
  • 6,841
  • 16
  • 40
  • 50
  • on http://caniuse.com/#search=sticky it's still very unsupported. But thanks for the info. – flyer Jul 26 '17 at 02:10