391

I need a simple solution. I know it's similar to some other questions, like:

But I need just a single left column to be frozen and I would prefer a simple and script-less solution.

Ratan Uday Kumar
  • 5,738
  • 6
  • 35
  • 54
agsamek
  • 8,734
  • 11
  • 36
  • 43

27 Answers27

469

If you want a table where only the columns scroll horizontally, you can position: absolute the first column (and specify its width explicitly), and then wrap the entire table in an overflow-x: scroll block. Don't bother trying this in IE7, however...

Relevant HTML & CSS:

table {
  border-collapse: separate;
  border-spacing: 0;
  border-top: 1px solid grey;
}

td,
th {
  margin: 0;
  border: 1px solid grey;
  white-space: nowrap;
  border-top-width: 0px;
}

div {
  width: 500px;
  overflow-x: scroll;
  margin-left: 5em;
  overflow-y: visible;
  padding: 0;
}

.headcol {
  position: absolute;
  width: 5em;
  left: 0;
  top: auto;
  border-top-width: 1px;
  /*only relevant for first row*/
  margin-top: -1px;
  /*compensate for top border*/
}

.headcol:before {
  content: 'Row ';
}

.long {
  background: yellow;
  letter-spacing: 1em;
}
<div>
  <table>
    <tr>
      <th class="headcol">1</th>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
    </tr>
    <tr>
      <th class="headcol">2</th>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
    </tr>
    <tr>
      <th class="headcol">3</th>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
    </tr>
    <tr>
      <th class="headcol">4</th>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
    </tr>
    <tr>
      <th class="headcol">5</th>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
    </tr>
    <tr>
      <th class="headcol">6</th>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
      <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
    </tr>
  </table>
</div>

Fiddle

Alexander Nied
  • 12,804
  • 4
  • 25
  • 45
Eamon Nerbonne
  • 47,023
  • 20
  • 101
  • 166
  • 2
    Note that getting the borders right (i.e., pixel perfectly aligned) is very tricky; in practice if you really want borders, I'd place them below the td level (by including all content of the td inside a div, for instance). – Eamon Nerbonne Aug 21 '09 at 16:04
  • 41
    This doesn't handle arbitrary labels. Unless you have very predictably short labels, you will get this: http://jsfiddle.net/YMvk9/3724/ – AaronLS Mar 10 '14 at 22:05
  • 9
    @AaronLS: yep, you need to specify the width of the first column. If you need to deal with arbitrary-length labels, you could use `text-overflow: ellipsis` to cleanly display those. – Eamon Nerbonne Mar 17 '14 at 12:59
  • 16
    @EamonNerbonne ellipsis was a good option I considered. Another option was to do something like this. Repeating the first column hidden as well as fixed. The second hidden column ensures the height of the rows syncs. Only works if you have a fixed width column though: http://jsfiddle.net/abkNM/2224/ – AaronLS Mar 17 '14 at 15:33
  • @AaronLS I don't see how your approach works. In the jsfiddle you provided the first column does not have the same height as the following one. This becomes obvious if you replace the *border-top* by a *border-bottom*: http://jsfiddle.net/m4r73n/abkNM/4740/ – m4r73n Dec 06 '14 at 19:56
  • @m4r73n You'll see it works fine with `border-top`. If you need to use `border-bottom`, then you will have to look for a different solution. If you find one that handles the various combinations of wrapping content well, then by all means please post it as an answer. – AaronLS Dec 08 '14 at 06:26
  • 4
    For anyone who is interested. Essentially the solution moves the headers off from the table and set each of them a 5em width. The table itself is pushed to the right for the same 5em width. This makes a visual appearance that the headers are still part of the normal flow in the table. – ABCD Dec 29 '14 at 04:27
  • @EamonNerbonne having `position: absolute` on td and/or th causes width problems – Kashif Khan Oct 13 '15 at 12:38
  • @KashifKhan As demonstrated in the demo link, you can use td's with position absolute as long as you *explicitly* specify the width. If you're still having "width problems" you'll need to be more explicit about what those are. – Eamon Nerbonne Oct 14 '15 at 12:35
  • @AaronLS you should post your fiddle as an answer, this was exactly what I was looking for. – Scott Jungwirth Nov 19 '15 at 23:41
  • @Eamon Nerbonne: how can set change height for fixed column based on content inside it ?? – Yogesh Jun 17 '16 at 18:46
  • @Yogesh: because the first column is absolutely positioned, it won't automatically have the same height as the others. You'll need to explicitly size the tds of the exceptional row, e.g. `.mySpecialHighRow > td { height: 3em; }` – Eamon Nerbonne Jun 17 '16 at 18:55
  • 1
    @Eamon Nerbonne: i can set height but my issue that, I have dynamic data in fixed column so i can't height explicitly. Is there any other way?? like calculating the height or something ?? – Yogesh Jun 17 '16 at 19:03
  • @Yogesh: unfortunately not with this technique! – Eamon Nerbonne Jun 18 '16 at 05:24
  • @EamonNerbonne can you suggest some other way. I am stuck at this. I am making this table using angular ng-repeat. – Yogesh Jun 18 '16 at 05:33
  • @Yogesh, since it's going to be quite a different solution, consider asking a new question with your specific needs. Alternatively, consider some of the other answers in this question, particularly those using javascript. – Eamon Nerbonne Jun 20 '16 at 19:01
  • Unfortunately this does not work if the table has th tags. – Dean Peterson Jul 22 '16 at 05:38
  • 1
    @DeanPeterson the proof-of-concept does not include `th` tags, but the technique works with th tags too. – Eamon Nerbonne Jul 22 '16 at 09:49
  • @DeanPeterson updated to use th's too, for illustration purposes. – Eamon Nerbonne Jul 22 '16 at 09:54
  • sorry, I meant th tags inside a thead tag – Dean Peterson Jul 22 '16 at 17:22
  • @EamonNerbonne : I want more than one columns (Left side) to be fixed. I tried to edit the fiddle, but couldn't succeed. Can you please help. – Ankuli Dec 28 '16 at 08:53
  • 10
    Here is a smaller example without unnecessary code http://jsfiddle.net/kashesandr/rLv5b1ft/1/ – kashesandr Mar 03 '17 at 11:46
  • This technique fails if row contains no data, right? I tried it on my table and set the first two columns on freeze mode, worked. But if one of the rows has no data it collapses on itself! the row above it and the one below it kind of squashes the empty row. Also I get some glitches when resizing the window (td background color changes to white in some of the td areas..!) – Khaled Nov 19 '17 at 07:30
  • @DeanPeterson It works with `th` inside a `thead` block. Just add the same .headcol class to the first such `th`. – quickshiftin Feb 14 '18 at 16:36
  • Is there a solution for max-height of table? Then You need also vertical scroll. Works with mail table but not with fixed column. Is there a solution without javascript? – piernik Mar 20 '18 at 18:38
  • I just did something similar to this, only to discover that it works great in all browsers except Firefox, which puts all the left column headers on top of each other next to the first row. It only does if the table is hidden and then shown; if it's visible all along, no problem. – Paul Kienitz Oct 10 '18 at 03:20
  • My first "sticky" column was covering the second column, so I had to add a thick border-left (roughly the width of the first column) to the second column ( td:nth-child(2) ). That got the second column pushed right enough to show fully. – deebs Jul 03 '19 at 17:03
  • Here is also an option with flex that I whipped up based on Eamon Nerbonne great answer. Not sure how well it will work in the wild though. The upper left cell doesn't seem to work quite as well either. https://jsfiddle.net/65shyfxe/ – Waltari Jan 23 '20 at 15:27
  • @EamonNerbonne - I got this partially working, I have locked column1 using `position:absolute;width:15em;` however column2 now sits behind column1. When I scroll the rest of the colums they are scrolling behind colum1 and visibly pushing to the left of column1. What could be causing this? – rolinger Jul 10 '20 at 20:32
  • Like @HelloWorld mentioned above, due to `left:0`, this technique moves the first column to the left which messes with alignment. If you don't use `left:0` then column2 starts behind column1. I found that if you do `position:sticky;width:5em;left:0;z-index:1` that you get the desire effect EXCEPT the main table now scrolls behind the entire column 1 vs scolling disappearing at the front edge of column1. Its kind of a nice effect to. – rolinger Jul 10 '20 at 21:15
176

You can use sticky position. Here is a sample code. This is HTML/CSS solution. No js is required.

.view {
  margin: auto;
  width: 600px;
}

.wrapper {
  position: relative;
  overflow: auto;
  border: 1px solid black;
  white-space: nowrap;
}

.sticky-col {
  position: -webkit-sticky;
  position: sticky;
  background-color: white;
}

.first-col {
  width: 100px;
  min-width: 100px;
  max-width: 100px;
  left: 0px;
}

.second-col {
  width: 150px;
  min-width: 150px;
  max-width: 150px;
  left: 100px;
}
<div class="view">
  <div class="wrapper">
    <table class="table">
      <thead>
        <tr>
          <th class="sticky-col first-col">Number</th>
          <th class="sticky-col second-col">First Name</th>
          <th>Last Name</th>
          <th>Employer</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td class="sticky-col first-col">1</td>
          <td class="sticky-col second-col">Mark</td>
          <td>Ham</td>
          <td>Micro</td>
        </tr>
        <tr>
          <td class="sticky-col first-col">2</td>
          <td class="sticky-col second-col">Jacob</td>
          <td>Smith</td>
          <td>Adob Adob Adob AdobAdob Adob Adob Adob Adob</td>
        </tr>
        <tr>
          <td class="sticky-col first-col">3</td>
          <td class="sticky-col second-col">Larry</td>
          <td>Wen</td>
          <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

codeply code: https://www.codeply.com/p/oZ4NjpvwbO

Circuit Breaker
  • 3,298
  • 4
  • 17
  • 19
  • 3
    Sure, this is the easiest and the most logical way to do it, I did only: $("table th:first").css("position", "sticky").css("left", "0px").css("background-color", $("body").css("background-color")); and in loop for every row and that was it! – Dejan Dozet Jun 18 '19 at 07:04
  • 7
    This should be the accepted answer. No position: absolute hacking and table cell sizes are not fixed. – Felix Lemke Jun 08 '20 at 10:40
  • is it possible to make only 1 column sticky? I am trying your link but i am unable to make it? Like i want to make employer column sticky – Muneem Habib Jun 09 '20 at 09:51
  • @MuneemHabib If you want only the first column to be sticky, see [this](https://www.codeply.com/p/EPwWDVVw2D) – Circuit Breaker Jun 09 '20 at 15:24
  • 3
    This is by far the best answer. Nothing is better than stickying the columns. Works great on Chrome, Safari and Firefox. Didn't test it on other browsers. – nir shabi Jul 16 '20 at 08:41
  • The solution works perfectly. The only issue is that the "non-sticky" columns are seen-through when scrolling horizontally. https://ibb.co/kJdHdpC – Abishek H Sep 10 '20 at 09:10
  • @AbishekH You can use cell border-color white. – Circuit Breaker Sep 10 '20 at 15:42
  • @CircuitBreaker. Unfortunately, that didn't fix the issue. I am using an inset box-shadow on the right side. It looks a little better than having a border. Anyway, thank you for the solution, man. You saved me from hours of googling. – Abishek H Sep 11 '20 at 06:12
  • 3
    Great solution. Here is a Plunker based on this answer with fixed columns, fixed header, and non even row heights https://plnkr.co/plunk/l0m9pF3His2BrKjS – Cesar Nov 17 '20 at 01:58
  • In December 2020, this still has very inconsistent cross-browser support. https://caniuse.com/css-sticky – Preston Badeer Dec 23 '20 at 17:10
  • In Chrome and Firefox on Android 10 (on my Samsung M51), this only works for when the table is zoomed fully out, making it unsuitable for a large table because the text becomes tiny. – Nick Hope Feb 16 '21 at 16:08
  • Is this possible to do with `width: auto;`? If I don't know the width of my columns I can't use this `left` trick. Unless I start dabbling with js. – Melendowski Oct 18 '21 at 19:23
  • @Melendowski, you need with for sticky columns. – Circuit Breaker Oct 18 '21 at 21:17
  • Can I have different odd and even `background-color` with this solution? – Soheil Rahsaz Apr 16 '22 at 10:51
  • This does not work if you remove the `border: 1px solid black;` in the wrapper. Why? – Victor Jun 12 '22 at 13:26
54

For most browsers released after 2017:

You can use the position: sticky. See https://caniuse.com/#feat=css-sticky.

There is no need for a fixed width column.

Run the code snippet below to see how it works.

.tscroll {
  width: 400px;
  overflow-x: scroll;
  margin-bottom: 10px;
  border: solid black 1px;
}

.tscroll table td:first-child {
  position: sticky;
  left: 0;
  background-color: #ddd;
}

.tscroll td, .tscroll th {
  border-bottom: dashed #888 1px;
}
<html>
<div class="tscroll">
  <table>
    <thead>
      <tr>
        <th></th>
        <th colspan="5">Heading 1</th>
        <th colspan="8">Heading 2</th>
        <th colspan="4">Heading 3</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>9:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
      <tr>
        <td>10:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
      <tr>
        <td>11:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
      <tr>
        <td>12:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
      <tr>
        <td>13:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
      <tr>
        <td>14:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
      <tr>
        <td>15:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
      <tr>
        <td>16:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
      <tr>
        <td>17:00</td>
        <td>AAA</td>
        <td>BBB</td>
        <td>CCC</td>
        <td>DDD</td>
        <td>EEE</td>
        <td>FFF</td>
        <td>GGG</td>
        <td>HHH</td>
        <td>III</td>
        <td>JJJ</td>
        <td>KKK</td>
        <td>LLL</td>
        <td>MMM</td>
        <td>NNN</td>
        <td>OOO</td>
        <td>PPP</td>
        <td>QQQ</td>
      </tr>
    </tbody>
    </table>
</div>
kojow7
  • 10,308
  • 17
  • 80
  • 135
  • 6
    Already exist an answer suggestion `sticky`, so no need for one more – Asons Aug 24 '19 at 18:08
  • 13
    @LGSon I disagree, there were only two other answers using sticky. One is using bootstrap CSS to implement sticky. The other answer provides code not directly related to the question, is incorrect, and uses a fixed width for the sticky column. – kojow7 Aug 24 '19 at 22:04
  • 1
    Whether one use Bootstrap and one use fix width, doesn't make `position: sticky` change in its behavior, hence using it already been shown. – Asons Aug 25 '19 at 08:09
  • 2
    Doing away with fixed with is of interest to me. However, I don't see how this solution could scale to 2+ sticky columns without fixed width for the sticky columns coming into play, since it seems we need an accurate `left` property on all sticky columns. – Ben Oct 13 '21 at 17:29
  • 1
    @Ben There are several ways to deal with this depending on your situation. If the first column has a known width, then set the `left` property on the second column to be the width of the first column. If that does not work for you, a nested table should work. If you don't like the idea of a nested table, you can also use nested divs with table-like properties. – kojow7 Oct 14 '21 at 14:16
  • It's a pity that you have to scroll the vertical bar to see the horizontal bar – Savage Oct 28 '21 at 16:28
  • @Savage If you know the table is scrollable, you can scroll with mouse or trackpad without using the horizontal bar. – kojow7 Oct 28 '21 at 17:29
  • Well, you have to use Shift+Mousescroll to scroll left-right, which almost nobody knows. I've implemented a solution which solves this, but it requires some jQuery, which doesn't meet the requirements of the original post. – Savage Oct 28 '21 at 20:25
  • 1
    @Savage True enough, I am now mostly using the Magic Mouse 2 which allows you to scroll both horizontally and vertically by swiping your finger. It is the default mouse for Mac, but you can install the drivers on Windows for scrolling to work there as well. Not a solution for everyone, but worth a mention anyhow. The only annoyance is you can't use it and charge it at the same time. – kojow7 Oct 28 '21 at 22:58
27

This is an interesting jQuery plugin that creates fixed headers and/or columns. Toggle fixed column to be true on the demo page to see it in action.

Rigel1121
  • 2,022
  • 1
  • 17
  • 24
Markos Fragkakis
  • 7,499
  • 18
  • 65
  • 103
  • 1
    this is for rows, not columns – Fortega Sep 11 '12 at 11:52
  • 4
    @Fortega: It is _also_ for columns, if you set the property fixedColumn: true. There is a demo you can check out. – Markos Fragkakis Sep 16 '12 at 10:49
  • ah ok, that was not clear. The demo was not working last week. But it is now. – Fortega Sep 17 '12 at 08:50
  • is this plug in also working in jQuery 1.9? I've problems. Error: TypeError: $.browser is undefined – Thomas1703 Apr 01 '13 at 17:30
  • 1
    @Thomas1703 You should use the Migrate Plugin from jQuery if a plugin does not work with 1.9.x -> http://jquery.com/download/ – 321X May 22 '13 at 13:34
  • 4
    This plugin is really slow with large tables. – A1rPun Nov 29 '13 at 11:08
  • Thanks, In myTheme.css if this code will be changed like bellow ( adding min-width: 80px; max-width: 80px; ) it would be better; the table headers will be same width with columns. Some times the column and headers are not equal for this library. .fancyTable td, .fancyTable th { /* appearance */ border: 1px solid #778899; /* size */ padding: 5px; min-width: 80px; max-width: 80px; white-space: normal; word-wrap: break-word; } – Muhammad Ashikuzzaman Apr 06 '19 at 11:30
  • Doesn't work. Even example on demo page can't have fixed column – Jurion Jun 18 '19 at 16:23
20

In case of fixed width left column the best solution is provided by Eamon Nerbonne.

In case of variable width left column the best solution I found is to make two identical tables and push one above another. Demo: http://jsfiddle.net/xG5QH/6/.

<!DOCTYPE html>
<html>
<head>
<style type="text/css">
/* important styles */

.container {
   /* Attach fixed-th-table to this container,
      in order to layout fixed-th-table
      in the same way as scolled-td-table" */
   position: relative;

   /* Truncate fixed-th-table */
   overflow: hidden;
}

.fixed-th-table-wrapper td,
.fixed-th-table-wrapper th,
.scrolled-td-table-wrapper td,
.scrolled-td-table-wrapper th {
   /* Set background to non-transparent color
      because two tables are one above another.
    */
   background: white;
}
.fixed-th-table-wrapper {
   /* Make table out of flow */
   position: absolute;
}
.fixed-th-table-wrapper th {
    /* Place fixed-th-table th-cells above 
       scrolled-td-table td-cells.
     */
    position: relative;
    z-index: 1;
}
.scrolled-td-table-wrapper td {
    /* Place scrolled-td-table td-cells
       above fixed-th-table.
     */
    position: relative;
}
.scrolled-td-table-wrapper {
   /* Make horizonal scrollbar if needed */
   overflow-x: auto;
}


/* Simulating border-collapse: collapse,
   because fixed-th-table borders
   are below ".scrolling-td-wrapper table" borders
*/

table {
    border-spacing: 0;
}
td, th {
   border-style: solid;
   border-color: black;
   border-width: 1px 1px 0 0;
}
th:first-child {
   border-left-width: 1px;
}
tr:last-child td,
tr:last-child th {
   border-bottom-width: 1px;
}

/* Unimportant styles */

.container {
    width: 250px;
}
td, th {
   padding: 5px;
}
</style>
</head>

<body>
<div class="container">

<div class="fixed-th-table-wrapper">
<!-- fixed-th-table -->
<table>
    <tr>
         <th>aaaaaaa</th>
         <td>ccccccccccc asdsad asd as</td>
         <td>ccccccccccc asdsad asd as</td>
    </tr>
    <tr>
         <th>cccccccc</th>
         <td>xxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyy zzzzzzzzzzzzz</td>
         <td>xxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyy zzzzzzzzzzzzz</td>
    </tr>
</table>
</div>

<div class="scrolled-td-table-wrapper">
<!-- scrolled-td-table
     - same as fixed-th-table -->
<table>
    <tr>
         <th>aaaaaaa</th>
         <td>ccccccccccc asdsad asd as</td>
         <td>ccccccccccc asdsad asd as</td>
    </tr>
    <tr>
         <th>cccccccc</th>
         <td>xxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyy zzzzzzzzzzzzz</td>
         <td>xxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyy zzzzzzzzzzzzz</td>
    </tr>
</table>
</div>

</div>
</body>
</html>
Community
  • 1
  • 1
Marcin Raczyński
  • 944
  • 1
  • 10
  • 14
  • 1
    +10 very clever. Had to set border-collapse to `seperate` under bootstap, other wise the borders don't float with the cells. Bit of border cleaning required after that. – Patrick Feb 25 '16 at 02:14
  • Very Nice! Thank You! i didn't use for my own reasons, i used a class to indicate which column were to be fixed, and, for good measure, added a `visibility: collapse; ` to the columns to hide from the fixed table (gotta love the `:not()` css selector!). i also used jQuery to `.clone()` the table, once it has been generated by php+MySQL+ajax, and insert it into a cleaned out div... – Peter Mar 06 '18 at 22:27
16

You can just make the first column position: sticky; z-index: 9. It will make the column/row stick to its current position. Checkout my example codepen here https://codepen.io/swastikmishra/pen/zYYdKBQ

HTML Example

table {
  text-align: center;
}

.table-container {
  width: 500px;
  height: 300px;
  overflow: scroll;
}

table th,
table td {
  white-space: nowrap;
  padding: 10px 20px;
  font-family: Arial;
}

table tr th:first-child,
table td:first-child {
  position: sticky;
  width: 100px;
  left: 0;
  z-index: 10;
  background: #fff;
}

table tr th:first-child {
  z-index: 11;
}

table tr th {
  position: sticky;
  top: 0;
  z-index: 9;
  background: #fff;
}
<div class="table-container">
  <table>
    <tr>
      <th>Hello World</th>
      <th>Hello World</th>
      <th>Hello World</th>
      <th>Hello World</th>
      <th>Hello World</th>
      <th>Hello World</th>
      <th>Hello World</th>
    </tr>
    <tr>
      <td>H11</td>
      <td>H12</td>
      <td>H13</td>
      <td>H14</td>
      <td>H15</td>
      <td>H16</td>
      <td>H17</td>
    </tr>
    <tr>
      <td>H21</td>
      <td>H22</td>
      <td>H23</td>
      <td>H24</td>
      <td>H25</td>
      <td>H26</td>
      <td>H27</td>
    </tr>
    <tr>
      <td>H31</td>
      <td>H32</td>
      <td>H33</td>
      <td>H34</td>
      <td>H35</td>
      <td>H36</td>
      <td>H37</td>
    </tr>
    <tr>
      <td>H41</td>
      <td>H42</td>
      <td>H44</td>
      <td>H44</td>
      <td>H45</td>
      <td>H46</td>
      <td>H47</td>
    </tr>
    <tr>
      <td>H51</td>
      <td>H52</td>
      <td>H54</td>
      <td>H54</td>
      <td>H55</td>
      <td>H56</td>
      <td>H57</td>
    </tr>
    <tr>
      <td>H61</td>
      <td>H62</td>
      <td>H64</td>
      <td>H64</td>
      <td>H65</td>
      <td>H66</td>
      <td>H67</td>
    </tr>
    <tr>
      <td>H71</td>
      <td>H72</td>
      <td>H74</td>
      <td>H74</td>
      <td>H75</td>
      <td>H76</td>
      <td>H77</td>
    </tr>
    <tr>
      <td>H81</td>
      <td>H82</td>
      <td>H84</td>
      <td>H84</td>
      <td>H85</td>
      <td>H86</td>
      <td>H87</td>
    </tr>
  </table>
</div>
Miheo
  • 517
  • 9
  • 19
Swastik Mishra
  • 161
  • 1
  • 3
15

A little late but I did run across this thread when trying out solutions for myself. Assuming you're using modern browsers nowadays, I came up with a solution using CSS calc() to help guarantee widths met up.

.table-fixed-left table,
.table-fixed-right table {
  border-collapse: collapse;
}
.table-fixed-right td,
.table-fixed-right th,
.table-fixed-left td,
.table-fixed-left th {
  border: 1px solid #ddd;
  padding: 5px 5px;
}
.table-fixed-left {
  width: 120px;
  float: left;
  position: fixed;
  overflow-x: scroll;
  white-space: nowrap;
  text-align: left;
  border: 1px solid #ddd;
  z-index: 2;
}
.table-fixed-right {
  width: calc(100% - 145px);
  right: 15px;
  position: fixed;
  overflow-x: scroll;
  border: 1px solid #ddd;
  white-space: nowrap;
}
.table-fixed-right td,
.table-fixed-right th {
  padding: 5px 10px;
}
<div class="table-fixed-left">
  <table>
    <tr>
      <th>Normal Header</th>
    </tr>
    <tr>
      <th>Header with extra line
        <br/>&nbsp;</th>
    </tr>
    <tr>
      <th>Normal Header</th>
    </tr>
    <tr>
      <th>Normal with extra line
        <br/>&nbsp;</th>
    </tr>
    <tr>
      <th>Normal Header</th>
    </tr>
    <tr>
      <th>Normal Header</th>
    </tr>
  </table>
</div>
<div class="table-fixed-right">
  <table>
    <tr>
      <th>Header</th>
      <th>Another header</th>
      <th>Header</th>
      <th>Header really really really really long</th>
    </tr>
    <tr>
      <td>Info Long</td>
      <td>Info
        <br/>with second line</td>
      <td>Info
        <br/>with second line</td>
      <td>Info Long</td>
    </tr>
    <tr>
      <td>Info Long</td>
      <td>Info Long</td>
      <td>Info Long</td>
      <td>Info Long</td>
    </tr>
    <tr>
      <td>Info
        <br/>with second line</td>
      <td>Info
        <br/>with second line</td>
      <td>Info
        <br/>with second line</td>
      <td>Info</td>
    </tr>
    <tr>
      <td>Info</td>
      <td>Info</td>
      <td>Info</td>
      <td>Info</td>
    </tr>
    <tr>
      <td>Info</td>
      <td>Info</td>
      <td>Info</td>
      <td>Info</td>
    </tr>
  </table>
</div>

Hope this helps someone!

Sergey
  • 1,608
  • 1
  • 27
  • 40
prozac
  • 239
  • 2
  • 5
12

Style the left column with position: fixed. (You'll presumably want to use top and left styles to control where exactly it occurs.)

chaos
  • 122,029
  • 33
  • 303
  • 309
  • Yeah; you should skip the whole "table" part and just use two divs, one for the left column and one for the rest of the content. Left column div gets `position: fixed` and stays put, rest of content acts normally (presumably with a left margin set so it doesn't overlap the left column). – chaos Aug 21 '09 at 14:46
  • 8
    This won't work if you're looking for generic styling for one or more tables that can appear at arbitrary positions on a page, i.e. if your design is responsive. – Giles Roberts Sep 20 '13 at 13:49
9

I took Earmon Nerbonne's answer and edited it to work with tables that fill the whole width.

http://jsfiddle.net/DYgD6/6/

<!DOCTYPE html>
<html><head><title>testdoc</title>
<style type="text/css">
            body {
        font:16px Calibri;
    }
    table {
        border-collapse:separate;
        border-top: 3px solid grey;
    }
    td {
        margin:0;
        border:3px solid grey;
        border-top-width:0px;
        white-space:nowrap;
    }
    #outerdiv {
        position: absolute;
        top: 0;
        left: 0;
        right: 5em;
    }
    #innerdiv {
        width: 100%;
        overflow-x:scroll;
        margin-left: 5em;
        overflow-y:visible;
        padding-bottom:1px;
    }
    .headcol {
        position:absolute;
        width:5em;
        left:0;
        top:auto;
        border-right: 0px none black;
        border-top-width:3px;
        /*only relevant for first row*/
        margin-top:-3px;
        /*compensate for top border*/
    }
    .headcol:before {
        content:'Row ';
    }
    .long {
        background:yellow;
        letter-spacing:1em;
    }
</style></head><body>
  <div id="outerdiv">
   <div id="innerdiv">
    <table>
        <tr>
            <td class="headcol">1</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
        <tr>
            <td class="headcol">2</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
        <tr>
            <td class="headcol">3</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
        <tr>
            <td class="headcol">4</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
        <tr>
            <td class="headcol">5</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
        <tr>
            <td class="headcol">6</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
        <tr>
            <td class="headcol">7</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
        <tr>
            <td class="headcol">8</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
        <tr>
            <td class="headcol">9</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
        </tr>
    </table>
</div></div>
</body></html>

The width of the fixed column still needs to be a set value though.

Jonathan.
  • 53,997
  • 54
  • 186
  • 290
8

If you're developing something more complicated and want multiple columns to be fixed/stuck to the left, you'll probably need something like this.

.wrapper {
    overflow-x: scroll;
}

td {
    min-width: 50px;
}

.fixed {
    position: absolute;
    background: #aaa;
}
<div class="content" style="width: 400px">

  <div class="wrapper" style="margin-left: 100px">

      <table>
        <thead>
          <tr>
            <th class="fixed" style="left: 0px">aaa</th>
            <th class="fixed" style="left: 50px">aaa2</th>
            <th>a</th>
            <th>b</th>
            <th>c</th>
            <th>d</th>
            <th>e</th>
            <th>f</th>
            <th>a</th>
            <th>b</th>
            <th>c</th>
            <th>d</th>
            <th>e</th>
            <th>f</th>
            <th>a</th>
            <th>b</th>
            <th>c</th>
            <th>d</th>
            <th>e</th>
            <th>f</th>
            <th>a</th>
            <th>b</th>
            <th>c</th>
            <th>d</th>
            <th>e</th>
            <th>f</th>        
          </tr>
        </thead>
        <tbody>
          <tr>
            <td class="fixed" style="left: 0px">aaa</td>
            <td class="fixed" style="left: 50px">aaa2</td>
            <td>a</td>
            <td>b</td>
            <td>c</td>
            <td>d</td>
            <td>e</td>
            <td>f</td>
            <td>a</td>
            <td>b</td>
            <td>c</td>
            <td>d</td>
            <td>e</td>
            <td>f</td>
            <td>a</td>
            <td>b</td>
            <td>c</td>
            <td>d</td>
            <td>e</td>
            <td>f</td>
            <td>a</td>
            <td>b</td>
            <td>c</td>
            <td>d</td>
            <td>e</td>
            <td>f</td>
          </tr>
          <tr>
            <td class="fixed" style="left: 0">bbb</td>
            <td class="fixed" style="left: 50px">bbb2</td>
            <td>a</td>
            <td>b</td>
            <td>c</td>
            <td>d</td>
            <td>e</td>
            <td>f</td>
            <td>a</td>
            <td>b</td>
            <td>c</td>
            <td>d</td>
            <td>e</td>
            <td>f</td>
            <td>a</td>
            <td>b</td>
            <td>c</td>
            <td>d</td>
            <td>e</td>
            <td>f</td>
            <td>a</td>
            <td>b</td>
            <td>c</td>
            <td>d</td>
            <td>e</td>
            <td>f</td>
          </tr>
        </tbody>
      </table>

  </div>

</div>
kashesandr
  • 1,521
  • 28
  • 35
3

If you're in Webdevelopper hell and need to make this work for IE6, here's a sample code I used:

<html>
<head>
<style type="text/css">
.fixme {
    position: relative;
    left: expression( ( 20 + ( ignoreMe2 = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft ) ) + 'px' );
    background-color: #FFFFFF;
}
</style>
</head>
<body>
<table width="1500px" border="2">
    <tr>
        <td class="fixme" style="width: 200px;">loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet</td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
    </tr>
    <tr>
        <td class="fixme" style="width: 200px;">loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
    </tr>
    <tr>
        <td class="fixme" style="width: 200px;">loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
    </tr>
    <tr>
        <td class="fixme" style="width: 200px;">loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
        <td>loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet loremp ispum dolor sit amet </td>
    </tr>
</table>
</body>
</html>

This will work probably ONLY for IE6, so use conditional comments for the CSS.

Barnee
  • 3,212
  • 8
  • 41
  • 53
mrmuggles
  • 2,081
  • 4
  • 25
  • 44
2

No need to add any plugin, CSS can do this job !!!

The idea is to make the position of all the first cells in each column absolute, and make width fixed. Ex:

max-width: 125px;
min-width: 125px;
position: absolute;

This hides some parts of some columns under the first column, so add an empty second column (add second empty td) with width same as the first column.

I tested and this works in Chrome and Firefox.

Lane
  • 2,669
  • 3
  • 25
  • 38
Guru
  • 1,303
  • 18
  • 32
  • I tried this and it works in IE8 as well. But having this, how can I make the first row also fixed. I tried it but because I set the left column absolute to have it frozen, this is affecting when making the first row frozen. Please help.....As I noticed, either we make first row frozen or first column frozen, but making both frozen does not work..... – Isaac May 15 '15 at 13:27
  • Could you please provide an example? – kashesandr Mar 03 '17 at 12:24
  • This solution works like a charm but messing the column data if there is a pagination in the table while scrolling vertically – Vin Nov 24 '20 at 08:13
2

Eamon Nerbonne, I changed some css in your code and it's better now(the scroll bar starts from the first row)

http://jsfiddle.net/At8L8/

I just add two line :

.div : padding-left:5em;
.headcol : background-color : #fff;
  • Can you please tell me how you got the scroll bar start from frozen column?? Also does changing the to have any impact? – theCrow Jan 23 '19 at 06:13
2

Here is another modification of the most popular answer, but with handling of variable length of text in the first column labels: http://jsfiddle.net/ozx56n41/

Basically, I'm using the second column for creating row height, like was mentioned. But my fiddle actually works unlike most mentioned above.

HTML:

<div id="outerdiv">
    <div id="innerdiv">
        <table>
            <tr>
                <td class="headcol"><div>This is a long label</div></td>
                <td class="hiddenheadcol"><div>This is a long label</div></td>
                <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
                <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            </tr>
            <tr>
                <td class="headcol"><div>Short label</div></td>
                <td class="hiddenheadcol"><div>Short label</div></td>
                <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
                <td class="long">QWERTYUIOPASDFGHJKLZXCVBNM</td>
            </tr>
        </table>
    </div>
</div>

CSS:

body {
    font: 16px Calibri;
}
#outerdiv {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    width: 100%;
    border-top: 1px solid grey;
}
#innerdiv {
    overflow-x: scroll;
    margin-left: 100px;
    overflow-y: visible;
    padding-bottom: 1px;
}
table {
    border-collapse:separate;
}
td {
    margin: 0;
    border: 1px solid grey;
    border-top-width: 0;
    border-left-width: 0px;
    padding: 10px;
}
td.headcol {
    /* Frozen 1st column */
    position: absolute;
    left: 0;
    top: auto;
    border-bottom-width: 1px;
    padding: 0;
    border-left-width: 1px;
}
td.hiddenheadcol {
    /* Hidden 2nd column to create height */
    max-width: 0;
    visibility: hidden;
    padding: 0;
}
td.headcol div {
    /* Text container in the 1st column */
    width: 100px;
    max-width: 100px;
    background: lightblue;
    padding: 10px;
    box-sizing: border-box;
}
td.hiddenheadcol div {
    /* Text container in the 2nd column */
    width: 100px;
    max-width: 100px;
    background: red;
    padding: 10px;
}
td.long {
    background:yellow;
    letter-spacing:1em;
}
Daniel
  • 21
  • 1
2

For me this was the only one that worked perfectly (thanks to Paul O'Brien!): https://codepen.io/paulobrien/pen/gWoVzN

Here's the snippet:

// requires jquery library
jQuery(document).ready(function() {
  jQuery(".main-table").clone(true).appendTo('#table-scroll').addClass('clone');   
 });
  .table-scroll {
    position:relative;
    max-width:600px;
    margin:auto;
    overflow:hidden;
    border:1px solid #000;
  }
.table-wrap {
 width:100%;
 overflow:auto;
}
.table-scroll table {
 width:100%;
 margin:auto;
 border-collapse:separate;
 border-spacing:0;
}
.table-scroll th, .table-scroll td {
 padding:5px 10px;
 border:1px solid #000;
 background:#fff;
 white-space:nowrap;
 vertical-align:top;
}
.table-scroll thead, .table-scroll tfoot {
 background:#f9f9f9;
}
.clone {
 position:absolute;
 top:0;
 left:0;
 pointer-events:none;
}
.clone th, .clone td {
 visibility:hidden
}
.clone td, .clone th {
 border-color:transparent
}
.clone tbody th {
 visibility:visible;
 color:red;
}
.clone .fixed-side {
 border:1px solid #000;
 background:#eee;
 visibility:visible;
}
.clone thead, .clone tfoot{background:transparent;}
<div id="table-scroll" class="table-scroll">
  <div class="table-wrap">
    <table class="main-table">
      <thead>
        <tr>
          <th class="fixed-side" scope="col">&nbsp;</th>
          <th scope="col">Header 2</th>
          <th scope="col">Header 3</th>
          <th scope="col">Header 4</th>
          <th scope="col">Header 5</th>
          <th scope="col">Header 6</th>
          <th scope="col">Header 7</th>
          <th scope="col">Header 8</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th class="fixed-side">Left Column</th>
          <td>Cell content<br>
            test</td>
          <td><a href="#">Cell content longer</a></td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
        </tr>
        <tr>
          <th class="fixed-side">Left Column</th>
          <td>Cell content</td>
          <td>Cell content longer</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
        </tr>
        <tr>
          <th class="fixed-side">Left Column</th>
          <td>Cell content</td>
          <td>Cell content longer</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
        </tr>
        <tr>
          <th class="fixed-side">Left Column</th>
          <td>Cell content</td>
          <td>Cell content longer</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
        </tr>
        <tr>
          <th class="fixed-side">Left Column</th>
          <td>Cell content</td>
          <td>Cell content longer</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
        </tr>
        <tr>
          <th class="fixed-side">Left Column</th>
          <td>Cell content</td>
          <td>Cell content longer</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
          <td>Cell content</td>
        </tr>
      </tbody>
      <tfoot>
        <tr>
          <th class="fixed-side">&nbsp;</th>
          <td>Footer 2</td>
          <td>Footer 3</td>
          <td>Footer 4</td>
          <td>Footer 5</td>
          <td>Footer 6</td>
          <td>Footer 7</td>
          <td>Footer 8</td>
        </tr>
      </tfoot>
    </table>
  </div>
</div>

<p>See <a href="https://codepen.io/paulobrien/pen/LBrMxa" target="blank">position Sticky version </a>with no JS</p>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script>
Árthur
  • 139
  • 1
  • 6
2
.div1 {
    width: 600px;
    height: 400px;
    overflow: scroll;
    border: 1px solid #777777;
}

.div1 table {
    border-spacing: 0;
}

.div1 th {
    border-left: none;
    border-right: 1px solid #bbbbbb;
    padding: 5px;
    width: 80px;
    min-width: 80px;
    position: sticky;
    top: 0;
    background: #727272;
    color: #e0e0e0;
    font-weight: normal;
}

.div1 td {
    border-left: none;
    border-right: 1px solid #bbbbbb;
    border-bottom: 1px solid #bbbbbb;
    padding: 5px;
    width: 80px;
    min-width: 80px;
}

.div1 th:nth-child(1),
.div1 td:nth-child(1) {
    position: sticky;
    left: 0;
    width: 150px;
    min-width: 150px;
}

.div1 th:nth-child(2),
.div1 td:nth-child(2) {
    position: sticky;
    /* 1st cell left/right padding + 1st cell width + 1st cell left/right border width */
    /* 0 + 5 + 150 + 5 + 1 */
    left: 161px;
    width: 50px;
    min-width: 50px;
}

.div1 td:nth-child(1),
.div1 td:nth-child(2) {
    background: #ffebb5;
}

.div1 th:nth-child(1),
.div1 th:nth-child(2) {
    z-index: 2;
}

the HTML,

<div class="div1">
    <table>
        <tr>
            <th>Column 1</th>
            <th>Column 2</th>
            <th>Column 3</th>
            <th>Column 4</th>
            <th>Column 5</th>
            <th>Column 6</th>
        </tr>
        <tr>
            <td>Row Data 1</td>
            <td>Row Data 2</td>
            <td>Row Data 3</td>
            <td>Row Data 4</td>
            <td>Row Data 5</td>
            <td>Row Data 6</td>
        </tr>
        <tr>
            <td>Row Data 1</td>
            <td>Row Data 2</td>
            <td>Row Data 3</td>
            <td>Row Data 4</td>
            <td>Row Data 5</td>
            <td>Row Data 6</td>
        </tr>
    </table>
</div>

Youtube Video Walkthrough: https://www.youtube.com/watch?v=_dpSEjaKqSE

Example 1: Fixed width & height

https://adriancs.com/demo/freeze_table_column_row.html

Example 2: Responsive width & height (adjusted by using viewport)

https://adriancs.com/html-css-js/298/html-table-freeze-row-and-column-with-css/

Full tutorial explanation:

https://adriancs.com/demo/freeze_table_column_row_responsive_viewport.html

mjb
  • 7,649
  • 8
  • 44
  • 60
1

Opera was buggy for all of the previous answers when I tested them on my mac. If you scroll through the table the fixed column disappears after you pass the first unfixed column. I went ahead and wrote the code below. It works in all the browsers I have locally installed. I don't know how ie handles it though.

Just keep that in mind that if you intend to skip rows in one table and not the other or change the heights of the rows you might need to adjust this code.

<table class = "fixedColumns">
    <tr><td> row 1 </td></tr>
    <tr><td> row 2 </td></tr>
</table>
<table class = "scrollableTable">
    <tr><td> col 1 </td> <td> col 2 </td><td> col 3 </td><td> col 4 </td></tr>
    <tr><td> col 1 </td> <td> col 2 </td><td> col 3 </td><td> col 4 </td></tr>
</table>

<style type = "text/css" >
    .fixedColumns
    {
        vertical-align:top;
        display: inline-block;
    }
    .scrollableTable
    {
        display: inline-block;
        width:50px;
        white-space: nowrap;
        overflow-x: scroll;
    }
</style>
1
//If the table has tbody and thead, make them the relative container in which we can fix td and th as absolute

table tbody {
    position: relative;
}

table thead {
    position: relative;
}

//Make both the first header and first data cells (First column) absolute so that it sticks to the left

table td:first-of-type {
    position: absolute;
}

table th:first-of-type {
    position: absolute;
}

//Move Second column according to the width of column 1 

table td:nth-of-type(2) {
    padding-left: <Width of column 1>;
}

table th:nth-of-type(2) {
    padding-left: <Width of column 1>;
}
Pradep
  • 1,874
  • 5
  • 21
  • 31
  • This worked really well for me and very simple to implement on existing tables by adding to current CSS - Thank you! – mike spurr Mar 23 '22 at 00:44
1

I just made the right-most sticky column of a table sticky.

th:last-of-type {
 position: sticky;
 right: 0;
 width: 120px;
 background: #f7f7f7;
}


td:last-of-type {
 position: sticky;
 right: 0;
 background: #f7f7f7;
 width: 120px;
}

I believe if you'll do {position: sticky; left: 0;}, you'll get the desired result.

1

<div style="max-width: 780px; overflow: scroll;">
    <table style="">
        <tr>
            <th style="position: sticky;left:0;background-color:aquamarine;">aaaaaaa</th>
            <th style="position: sticky;left:111px;background-color:aquamarine;">aaaaaaa</th>
            <th>aaaaaaa</th>
            <th>aaaaaaa</th>
            <th>aaaaaaa</th>
            <th>aaaaaaa</th>
            <th>aaaaaaa</th>
        </tr>
        <tr>
            <th style="position: sticky;left:0;background-color:aquamarine;">111111111111</th>
            <th style="position: sticky;left:111px;background-color:aquamarine;">111111111111</th>
            <th>111111111111</th>
            <th>111111111111</th>
            <th>111111111111</th>
            <th>111111111111</th>
            <th>111111111111</th>
        </tr>
    </table>
</div>
M Komaei
  • 7,006
  • 2
  • 28
  • 34
1

.container {
    width: 500px;
    height: 300px;
    background-color: #ddd;
    overflow: auto;
    border: 1px solid #ccc;
}
table {
  table-layout: fixed;
  width: 100%;
  overflow-x: scroll;
    border-collapse: collapse;
}

td,th {
    border: 1px solid #ccc;
}
th {
    font-weight: 600;
    text-align: left;
    background-color: #f1f4f7;
}
.fixed-td {
    position: sticky;
    width: 100px;
    z-index: 2;
    left: 0;
    background-color: #fff;
}
.fixed-hd {
    position: sticky;
    top: 0;
    z-index: 1;
}
.left-top-td {
    z-index: 3;
}
.scrollable-td {
    width: 200px;
}
<div class="container">
    <table>
        <tr>
            <th class="fixed-td fixed-hd left-top-td">Fixed</td>
            <th class="scrollable-td fixed-hd">Scrollable 1</td>
            <th class="scrollable-td fixed-hd">Scrollable 2</td>
            <th class="scrollable-td fixed-hd">Scrollable 3</td>
            <th class="scrollable-td fixed-hd">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable 4</td>
        </tr>
        <tr>
            <td class="fixed-td">Fixed last</td>
            <td class="scrollable-td">Scrollable 1</td>
            <td class="scrollable-td">Scrollable 2</td>
            <td class="scrollable-td">Scrollable 3</td>
            <td class="scrollable-td">Scrollable last</td>
        </tr>
    </table>
 </div>
dilip kumar
  • 377
  • 4
  • 13
0

Alternatively, style the tbody with a predetermined size (via height:20em, for example) and use overflow-y:scroll;

Then, you can have a huge tbody, which will scroll independently of the rest of the page.

Eamon Nerbonne
  • 47,023
  • 20
  • 101
  • 166
0

$(document).ready(function() {
    var table = $('#example').DataTable( {
        scrollY:        "400px",
        scrollX:        true,
        scrollCollapse: true,
        paging:         true,
        fixedColumns:   {
            leftColumns: 3
        }
    } );
} );
<head>
 <title>table</title>
 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/fixedcolumns/3.2.4/css/fixedColumns.dataTables.min.css">
<script type="text/javascript" src="http://cdn.datatables.net/1.10.2/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/fixedcolumns/3.2.4/js/dataTables.fixedColumns.min.js"></script>


<style>
       th, td { white-space: nowrap; }
    div.dataTables_wrapper {
        width: 900px;
        margin: 0 auto;
    }
</style>

</head>
<table id="example" class="stripe row-border order-column" style="width:100%">
        <thead>
            <tr>
                <th>First name</th>
                <th>Last name</th>
                <th>Position</th>
                <th>Office</th>
                <th>Age</th>
                <th>Start date</th>
                <th>Salary</th>
                <th>Extn.</th>
                <th>E-mail</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>Tiger</td>
                <td>Nixon</td>
                <td>System Architect</td>
                <td>Edinburgh</td>
                <td>61</td>
                <td>2011/04/25</td>
                <td>$320,800</td>
                <td>5421</td>
                <td>t.nixon@datatables.net</td>
            </tr>
            <tr>
                <td>Garrett</td>
                <td>Winters</td>
                <td>Accountant</td>
                <td>Tokyo</td>
                <td>63</td>
                <td>2011/07/25</td>
                <td>$170,750</td>
                <td>8422</td>
                <td>g.winters@datatables.net</td>
            </tr>
            <tr>
                <td>Ashton</td>
                <td>Cox</td>
                <td>Junior Technical Author</td>
                <td>San Francisco</td>
                <td>66</td>
                <td>2009/01/12</td>
                <td>$86,000</td>
                <td>1562</td>
                <td>a.cox@datatables.net</td>
            </tr>
            <tr>
                <td>Cedric</td>
                <td>Kelly</td>
                <td>Senior Javascript Developer</td>
                <td>Edinburgh</td>
                <td>22</td>
                <td>2012/03/29</td>
                <td>$433,060</td>
                <td>6224</td>
                <td>c.kelly@datatables.net</td>
            </tr>
            <tr>
                <td>Airi</td>
                <td>Satou</td>
                <td>Accountant</td>
                <td>Tokyo</td>
                <td>33</td>
                <td>2008/11/28</td>
                <td>$162,700</td>
                <td>5407</td>
                <td>a.satou@datatables.net</td>
            </tr>
            <tr>
                <td>Brielle</td>
                <td>Williamson</td>
                <td>Integration Specialist</td>
                <td>New York</td>
                <td>61</td>
                <td>2012/12/02</td>
                <td>$372,000</td>
                <td>4804</td>
                <td>b.williamson@datatables.net</td>
            </tr>
            <tr>
                <td>Herrod</td>
                <td>Chandler</td>
                <td>Sales Assistant</td>
                <td>San Francisco</td>
                <td>59</td>
                <td>2012/08/06</td>
                <td>$137,500</td>
                <td>9608</td>
                <td>h.chandler@datatables.net</td>
            </tr>
            <tr>
                <td>Rhona</td>
                <td>Davidson</td>
                <td>Integration Specialist</td>
                <td>Tokyo</td>
                <td>55</td>
                <td>2010/10/14</td>
                <td>$327,900</td>
                <td>6200</td>
                <td>r.davidson@datatables.net</td>
            </tr>
            <tr>
                <td>Colleen</td>
                <td>Hurst</td>
                <td>Javascript Developer</td>
                <td>San Francisco</td>
                <td>39</td>
                <td>2009/09/15</td>
                <td>$205,500</td>
                <td>2360</td>
                <td>c.hurst@datatables.net</td>
            </tr>
            <tr>
                <td>Sonya</td>
                <td>Frost</td>
                <td>Software Engineer</td>
                <td>Edinburgh</td>
                <td>23</td>
                <td>2008/12/13</td>
                <td>$103,600</td>
                <td>1667</td>
                <td>s.frost@datatables.net</td>
            </tr>
            <tr>
                <td>Jena</td>
                <td>Gaines</td>
                <td>Office Manager</td>
                <td>London</td>
                <td>30</td>
                <td>2008/12/19</td>
                <td>$90,560</td>
                <td>3814</td>
                <td>j.gaines@datatables.net</td>
            </tr>
             <tr>
                <td>Sakura</td>
                <td>Yamamoto</td>
                <td>Support Engineer</td>
                <td>Tokyo</td>
                <td>37</td>
                <td>2009/08/19</td>
                <td>$139,575</td>
                <td>9383</td>
                <td>s.yamamoto@datatables.net</td>
            </tr>
            <tr>
                <td>Thor</td>
                <td>Walton</td>
                <td>Developer</td>
                <td>New York</td>
                <td>61</td>
                <td>2013/08/11</td>
                <td>$98,540</td>
                <td>8327</td>
                <td>t.walton@datatables.net</td>
            </tr>
            <tr>
                <td>Finn</td>
                <td>Camacho</td>
                <td>Support Engineer</td>
                <td>San Francisco</td>
                <td>47</td>
                <td>2009/07/07</td>
                <td>$87,500</td>
                <td>2927</td>
                <td>f.camacho@datatables.net</td>
            </tr>
            <tr>
                <td>Serge</td>
                <td>Baldwin</td>
                <td>Data Coordinator</td>
                <td>Singapore</td>
                <td>64</td>
                <td>2012/04/09</td>
                <td>$138,575</td>
                <td>8352</td>
                <td>s.baldwin@datatables.net</td>
            </tr>
            <tr>
                <td>Zenaida</td>
                <td>Frank</td>
                <td>Software Engineer</td>
                <td>New York</td>
                <td>63</td>
                <td>2010/01/04</td>
                <td>$125,250</td>
                <td>7439</td>
                <td>z.frank@datatables.net</td>
            </tr>
            <tr>
                <td>Zorita</td>
                <td>Serrano</td>
                <td>Software Engineer</td>
                <td>San Francisco</td>
                <td>56</td>
                <td>2012/06/01</td>
                <td>$115,000</td>
                <td>4389</td>
                <td>z.serrano@datatables.net</td>
            </tr>
            <tr>
                <td>Jennifer</td>
                <td>Acosta</td>
                <td>Junior Javascript Developer</td>
                <td>Edinburgh</td>
                <td>43</td>
                <td>2013/02/01</td>
                <td>$75,650</td>
                <td>3431</td>
                <td>j.acosta@datatables.net</td>
            </tr>
            <tr>
                <td>Cara</td>
                <td>Stevens</td>
                <td>Sales Assistant</td>
                <td>New York</td>
                <td>46</td>
                <td>2011/12/06</td>
                <td>$145,600</td>
                <td>3990</td>
                <td>c.stevens@datatables.net</td>
            </tr>
            <tr>
                <td>Hermione</td>
                <td>Butler</td>
                <td>Regional Director</td>
                <td>London</td>
                <td>47</td>
                <td>2011/03/21</td>
                <td>$356,250</td>
                <td>1016</td>
                <td>h.butler@datatables.net</td>
            </tr>
            <tr>
                <td>Lael</td>
                <td>Greer</td>
                <td>Systems Administrator</td>
                <td>London</td>
                <td>21</td>
                <td>2009/02/27</td>
                <td>$103,500</td>
                <td>6733</td>
                <td>l.greer@datatables.net</td>
            </tr>
            <tr>
                <td>Jonas</td>
                <td>Alexander</td>
                <td>Developer</td>
                <td>San Francisco</td>
                <td>30</td>
                <td>2010/07/14</td>
                <td>$86,500</td>
                <td>8196</td>
                <td>j.alexander@datatables.net</td>
            </tr>
            <tr>
                <td>Shad</td>
                <td>Decker</td>
                <td>Regional Director</td>
                <td>Edinburgh</td>
                <td>51</td>
                <td>2008/11/13</td>
                <td>$183,000</td>
                <td>6373</td>
                <td>s.decker@datatables.net</td>
            </tr>
            <tr>
                <td>Michael</td>
                <td>Bruce</td>
                <td>Javascript Developer</td>
                <td>Singapore</td>
                <td>29</td>
                <td>2011/06/27</td>
                <td>$183,000</td>
                <td>5384</td>
                <td>m.bruce@datatables.net</td>
            </tr>
            <tr>
                <td>Donna</td>
                <td>Snider</td>
                <td>Customer Support</td>
                <td>New York</td>
                <td>27</td>
                <td>2011/01/25</td>
                <td>$112,000</td>
                <td>4226</td>
                <td>d.snider@datatables.net</td>
            </tr>
        </tbody>
    </table>

This can be easily done with the help of datatables. People who are new to data tables, please refer to https://datatables.net/ .Its a plugin and offers a lot of features.In the the code given, header is fixed,first 3 columns are fixed and several other features are also there.

alexander.polomodov
  • 5,396
  • 14
  • 39
  • 46
kiran
  • 1
  • 2
0

I didn't check each and every answer for this question, but after analyzing most of them I found that design fails in case of multiline data in cells or head. I used Javascript to solve this. I hope someone finds this helpful.

https://codepen.io/kushagrarora/pen/zeYaoY

var freezeTables = document.getElementsByClassName("freeze-pane");

[].forEach.call(freezeTables, ftable => {
  var wrapper = document.createElement("div");
  wrapper.className = "freeze-pane-wrapper";
  var scroll = document.createElement("div");
  scroll.className = "freeze-pane-scroll";

  wrapper.appendChild(scroll);

  ftable.parentNode.replaceChild(wrapper, ftable);

  scroll.appendChild(ftable);

  var heads = ftable.querySelectorAll("th:first-child");

  let maxWidth = 0;

  [].forEach.call(heads, head => {
    var w = window
      .getComputedStyle(head)
      .getPropertyValue("width")
      .split("px")[0];
    if (Number(w) > Number(maxWidth)) maxWidth = w;
  });

  ftable.parentElement.style.marginLeft = maxWidth + "px";
  ftable.parentElement.style.width = "calc(100% - " + maxWidth + "px)";
  [].forEach.call(heads, head => {
    head.style.width = maxWidth + "px";
    var restRowHeight = window
      .getComputedStyle(head.nextElementSibling)
      .getPropertyValue("height");
    var headHeight = window.getComputedStyle(head).getPropertyValue("height");
    if (headHeight > restRowHeight)
      head.nextElementSibling.style.height = headHeight;
    else head.style.height = restRowHeight;
  });
});
@import url("https://fonts.googleapis.com/css?family=Open+Sans");
* {
  font-family: "Open Sans", sans-serif;
}

.container {
  width: 400px;
  height: 90vh;
  border: 1px solid black;
  overflow: hidden;
}

table,
th,
td {
  border: 1px solid #eee;
}

.table {
  width: 100%;
  margin-bottom: 1rem;
  table-layout: fixed;
  border-collapse: collapse;
}

.freeze-pane-wrapper {
  position: relative;
}

.freeze-pane-scroll {
  overflow-x: scroll;
  overflow-y: visible;
}

.freeze-pane th:first-child {
  position: absolute;
  background-color: pink;
  left: 0;
  top: auto;
  max-width: 40%;
}
<div class="container">
  <table class="freeze-pane">
    <tbody>
      <tr>
        <th>
          <p>Model</p>
        </th>
        <th>
          <p>Mercedes Benz AMG C43 4dr</p>
        </th>
        <th>
          <p>Audi S4 Premium 4dr</p>
        </th>
        <th>
          <p>BMW 440i 4dr sedan</p>
        </th>
      </tr>
      <tr>
        <th>
          <p>Passenger capacity</p>
        </th>
        <td>
          <p>5</p>
        </td>
        <td>
          <p>5</p>
        </td>
        <td>
          <p>5</p>
        </td>
      </tr>
      <tr>
        <th>
          <p>Front (Head/Shoulder/Leg) (In.)</p>
        </th>
        <td>
          <p>37.1/55.3/41.7</p>
        </td>
        <td>
          <p>38.9/55.9/41.3</p>
        </td>
        <td>
          <p>39.9/54.8/42.2</p>
        </td>
      </tr>
      <tr>
        <th>
          <p>Second (Head/Shoulder/Leg) (In.)</p>
        </th>
        <td>
          <p>37.1/55.5/35.2</p>
        </td>
        <td>
          <p>37.4/54.5/35.7</p>
        </td>
        <td>
          <p>36.9/54.3/33.7</p>
        </td>
      </tr>
    </tbody>
  </table>
</div>

Note: the "container" div is just to demonstrate that code is compatible with mobile-view.

Kushagr Arora
  • 1,978
  • 1
  • 10
  • 11
0

I improve Circuit Breaker's example, original example code will shake fixed columns since table has padding, I use border-collapse: collapse disable it

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <!--mobile friendly-->
  <meta name="viewport" content="width=device-width, user-scalable=yes">
  <style>
    .view {
      margin: auto;
      width: 600px;
    }

    .wrapper {
      position: relative;
      overflow: auto;
      border: 1px solid black;
      white-space: nowrap;
    }

    .sticky-col {
      position: -webkit-sticky;
      position: sticky;
      background-color: white;
    }

    .first-col {
      padding: 0;
      margin: 0;
      left: 0;
    }

    .second-col {
      left: 200px;
    }

    .col {
      width: 200px;
      min-width: 200px;
      max-width: 200px;
    }
  </style>
</head>
<body>
<div class="view">
  <div class="wrapper">
    <table class="table" style="border-collapse: collapse;">
      <colgroup>
        <col class="col"/>
        <col class="col"/>
        <col class="col"/>
        <col class="col"/>
      </colgroup>
      <thead>
      <tr>
        <th class="sticky-col first-col">Number</th>
        <th class="sticky-col second-col">First Name</th>
        <th>Last Name</th>
        <th>Employer</th>
      </tr>
      </thead>
      <tbody>
      <tr>
        <td class="sticky-col first-col">1</td>
        <td class="sticky-col second-col">Mark</td>
        <td>Ham</td>
        <td>Micro</td>
      </tr>
      <tr>
        <td class="sticky-col first-col">2</td>
        <td class="sticky-col second-col">Jacob</td>
        <td>Smith</td>
        <td>Adob Adob Adob AdobAdob Adob Adob Adob Adob</td>
      </tr>
      <tr>
        <td class="sticky-col first-col">3</td>
        <td class="sticky-col second-col">Larry</td>
        <td>Wen</td>
        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
      </tr>
      </tbody>
    </table>
  </div>
</div>
</body>
</html>
chikadance
  • 3,591
  • 4
  • 41
  • 73
-1

In HTML5, you can use CSS style.transform.
However, i reccomend you "swipe between pages" turn off If you implement on Mac.

look at sample codePen

let l  = 0;
let t  = 0;

const MouseWheelHandler = (e) => {
  // vertical scroll
  if (e.deltaX == -0) {
    // t = t - e.deltaY

  // horizonal scroll
  } else if (e.deltaY == -0) {
    l = l - e.deltaX
    if (l >= 0) {
      l = 0;
      document.getElementById("gantt_task").style.transform = "translateX(1px)"
      document.getElementById("gantt_task_header").style.transform = "translateX(1px)"
      return false
    } 
    document.getElementById("gantt_task").style.transform = "translateX(" + l.toString() + "px)"
    document.getElementById("gantt_task_header").style.transform = "translateX(" + l.toString() + "px)"
  }
  return false;
}

window.addEventListener("wheel", MouseWheelHandler, false);
.row {
  border-bottom: 1px solid #979A9A
}
#gantt_grid_header {
  height:   30px;
  width:    100px;
  position: fixed;
  z-index:  3;
  top:      0px;
  left:     0px;
  border:   1px solid #cecece;
  background-color: #F08080;
}     

#gantt_task_header {
  height:   30px;
  width:    400px;
  position: fixed;
  z-index:  2;
  top:      0px;
  left:     100px;
  border:   1px solid #cecece;
  background-color: #FFC300;
}

#gantt_grid {
  width:    100px; 
  height:   400px;
  position: absolute;
  left:     0px;
  top:      0px;
  z-index:  1;
  border:   1px solid #cecece;
  background-color: #DAF7A6;
}

#gantt_task {
  width:    400px; 
  height:   400px;
  position: absolute;
  left:     100px;
  top:      0px;
  border:   1px solid #cecece;
  background-color: #FF5733;
}
<html>
    <div id="gantt_grid_header">
      HEADER
    </div>
    <div id="gantt_grid">
      <div class="row">V Scroll OK</div>
      <div class="row">V Scroll OK</div>
      <div class="row">V Scroll OK</div>
      <div class="row">V Scroll OK</div>
      <div class="row">V Scroll OK</div>
      <div class="row">V Scroll OK</div>
      <div class="row">V Scroll OK</div>
      <div class="row">V Scroll OK</div>
      <div class="row">V Scroll OK</div>
    </div>
    <div id="gantt_task_header">
      DATA HEADER
    </div>
    <div id="gantt_task">
      <div class="row">Vertical,Horizenal Scroll OK</div>
      <div class="row">Vertical,Horizenal Scroll OK</div>
      <div class="row">Vertical,Horizenal Scroll OK</div>
      <div class="row">Vertical,Horizenal Scroll OK</div>
      <div class="row">Vertical,Horizenal Scroll OK</div>
      <div class="row">Vertical,Horizenal Scroll OK</div>
      <div class="row">Vertical,Horizenal Scroll OK</div>
      <div class="row">Vertical,Horizenal Scroll OK</div>
      <div class="row">Vertical,Horizenal Scroll OK</div>
    </div>
</html>
Hajime
  • 139
  • 4
-1

If you don't want to touch your current table too much you can make a fake pinned column in front of the table.

The example shows one way of doing it without JS

table {
  border-collapse: collapse;
  border-spacing: 0;
  border: 1px solid #ddd;
  min-width: 600px;
}

.labels {
  display:flex;
  flex-direction: column
}

.overflow {
  overflow-x: scroll;
  min width: 400px;
  flex: 1;
}

.label {
  display: flex;
  align-items: center;
  white-space:nowrap;
  padding: 10px;
  flex: 1;
  border-bottom: 1px solid #ddd;
  border-right: 2px solid #ddd;
}

.label:last-of-type {
  overflow-x: scroll;
  border-bottom: 0;
}

td {
  border: 1px solid #ddd;
  padding: 10px;
}

.flex {
  display:flex;
  max-width: 600px;
  padding: 0;
  border: 5px solid #ddd;
}
<div class="flex">
  <div class="labels">
    <span class="label">Label 1</span>
    <span class="label">Lorem ipsum dolor sit amet.</span>
    <span class="label">Lorem ipsum dolor.</span>
  </div>
  <div class="overflow">
    <table>
      <tr>
        <td class="long">Lorem ipsum dolor sit amet consectetur adipisicing</td>
        <td class="long">Lorem ipsum dolor sit amet consectetur adipisicing</td>
      </tr>
      <tr>
        <td class="long">Lorem ipsum dolor sit amet consectetur adipisicing</td>
        <td class="long">Lorem ipsum dolor sit amet consectetur adipisicing</td>
      </tr>
      <tr>
        <td class="long">Lorem ipsum dolor sit amet consectetur adipisicing</td>
        <td class="long">Lorem ipsum dolor sit amet consectetur adipisicing</td>
      </tr>
  </table>
  </div>
</div>
m.popov
  • 494
  • 3
  • 14