How to Lock the First Row and First Column of a Table When Scrolling, Possibly Using JavaScript and CSS

Making html table scrollable with first row and column fixed

You can do something like this(View in full screen for good visualization):

$(document).ready(function() {

$('tbody').scroll(function(e) { //detect a scroll event on the tbody

$('thead').css("left", -$("tbody").scrollLeft()); //fix the thead relative to the body scrolling

$('thead th:nth-child(1)').css("left", $("tbody").scrollLeft()); //fix the first cell of the header

$('tbody td:nth-child(1)').css("left", $("tbody").scrollLeft()); //fix the first column of tdbody

});

});
table {

position: relative;

width: 900px;

background-color: #aaa;

overflow: hidden;

border-collapse: collapse;

}

/*thead*/

thead {

position: relative;

display: block;

width: 900px;

overflow: visible;

}

thead th {

background-color: #99a;

min-width: 120px;

height: 32px;

border: 1px solid #222;

}

thead th:nth-child(1) {

/*first cell in the header*/

position: relative;

display: block;

/*seperates the first cell in the header from the header*/

background-color: #88b;

}

/*tbody*/

tbody {

position: relative;

display: block;

width: 900px;

height: 239px;

overflow: scroll;

}

tbody td {

background-color: #bbc;

min-width: 120px;

border: 1px solid #222;

}

tbody tr td:nth-child(1) {

position: relative;

display: block;

height: 40px;

background-color: #99a;

}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<head>

<title>sample</title>

<meta charset="utf-8" http-equiv="refresh" content="300">

<link href="https://fonts.googleapis.com/css?family=Lato&display=block" rel="stylesheet">

<link rel="stylesheet" type="text/css" href="css/style.css">

</head>

<body>

<div class="container">

<table class="checkBox">

<thead>

<tr>

<th>Name</th>

<th>user1</th>

<th>user2</th>

<th>user3</th>

<th>user4</th>

<th>user5</th>

<th>user6</th>

<th>user7</th>

<th>user8</th>

<th>user9</th>

<th>user10</th>

<th>user11</th>

<th>user12</th>

<th>user13</th>

</tr>

</thead>

<tbody>

<tr id='row1'>

<td>row1</td>

<td id='user1'></td>

<td id='user2'></td>

<td id='user3'></td>

<td id='user4'></td>

<td id='user5'></td>

<td id='user6'></td>

<td id='user7'></td>

<td id='user8'></td>

<td id='user9'></td>

<td id='user10'></td>

<td id='user11'></td>

<td id='user12'></td>

<td id='user13'></td>

</tr>

<tr id='row2'>

<td>row2</td>

<td id='user1'></td>

<td id='user2'></td>

<td id='user3'></td>

<td id='user4'></td>

<td id='user5'></td>

<td id='user6'></td>

<td id='user7'></td>

<td id='user8'></td>

<td id='user9'></td>

<td id='user10'></td>

<td id='user11'></td>

<td id='user12'></td>

<td id='user13'></td>

</tr>

<tr id='row3'>

<td>row3</td>

<td id='user1'></td>

<td id='user2'></td>

<td id='user3'></td>

<td id='user4'></td>

<td id='user5'></td>

<td id='user6'></td>

<td id='user7'></td>

<td id='user8'></td>

<td id='user9'></td>

<td id='user10'></td>

<td id='user11'></td>

<td id='user12'></td>

<td id='user13'></td>

</tr>

<tr id='row4'>

<td>row4</td>

<td id='user1'></td>

<td id='user2'></td>

<td id='user3'></td>

<td id='user4'></td>

<td id='user5'></td>

<td id='user6'></td>

<td id='user7'></td>

<td id='user8'></td>

<td id='user9'></td>

<td id='user10'></td>

<td id='user11'></td>

<td id='user12'></td>

<td id='user13'></td>

</tr>

<tr id='row5'>

<td>row5</td>

<td id='user1'></td>

<td id='user2'></td>

<td id='user3'></td>

<td id='user4'></td>

<td id='user5'></td>

<td id='user6'></td>

<td id='user7'></td>

<td id='user8'></td>

<td id='user9'></td>

<td id='user10'></td>

<td id='user11'></td>

<td id='user12'></td>

<td id='user13'></td>

</tr>

<tr id='row6'>

<td>row6</td>

<td id='user1'></td>

<td id='user2'></td>

<td id='user3'></td>

<td id='user4'></td>

<td id='user5'></td>

<td id='user6'></td>

<td id='user7'></td>

<td id='user8'></td>

<td id='user9'></td>

<td id='user10'></td>

<td id='user11'></td>

<td id='user12'></td>

<td id='user13'></td>

</tr>

<tr id='row7'>

<td>row7</td>

<td id='user1'></td>

<td id='user2'></td>

<td id='user3'></td>

<td id='user4'></td>

<td id='user5'></td>

<td id='user6'></td>

<td id='user7'></td>

<td id='user8'></td>

<td id='user9'></td>

<td id='user10'></td>

<td id='user11'></td>

<td id='user12'></td>

<td id='user13'></td>

</tr>

<tr id='row8'>

<td>row8</td>

<td id='user1'></td>

<td id='user2'></td>

<td id='user3'></td>

<td id='user4'></td>

<td id='user5'></td>

<td id='user6'></td>

<td id='user7'></td>

<td id='user8'></td>

<td id='user9'></td>

<td id='user10'></td>

<td id='user11'></td>

<td id='user12'></td>

<td id='user13'></td>

</tr>

<tr id='row9'>

<td>row9</td>

<td id='user1'></td>

<td id='user2'></td>

<td id='user3'></td>

<td id='user4'></td>

<td id='user5'></td>

<td id='user6'></td>

<td id='user7'></td>

<td id='user8'></td>

<td id='user9'></td>

<td id='user10'></td>

<td id='user11'></td>

<td id='user12'></td>

<td id='user13'></td>

</tr>

<tr id='row10'>

<td>row10</td>

<td id='user1'></td>

<td id='user2'></td>

<td id='user3'></td>

<td id='user4'></td>

<td id='user5'></td>

<td id='user6'></td>

<td id='user7'></td>

<td id='user8'></td>

<td id='user9'></td>

<td id='user10'></td>

<td id='user11'></td>

<td id='user12'></td>

<td id='user13'></td>

</tr>

</tbody>

</table>

</div>

</body>

How do I create an HTML table with a fixed/frozen left column and a scrollable body?

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>

Freeze first row and first column of table

Freeze First Row

Freezing the first row can be done with CSS by setting the table body to overflow: auto, and giving a fixed width to the table cells. (See example 1)

Freeze First Row & First Column

However, to get this behavior for both first row and first column, you need to separate the first row, first column, and first cell from the table, and then continuously set the position of these elements based on the scrolled position of the table body, upon a scroll event. (See example 2)

Example 1: (Freeze first row only)

table thead tr {

display: block;

}

table th, table td {

width: 80px;

}

table tbody {

display: block;

height: 90px;

overflow: auto;

}

th {

text-align: center;

}

td {

text-align: center;

white-space: pre;

}
<table class="table table-bordered">

<thead>

<tr>

<th>ID</th>

<th>Tanggal</th>

<th>Judul Pekerjaan</th>

<th>Deskripsi</th>

<th>Level</th>

<th>Category</th>

<th>Severity</th>

</tr>

</thead>

<tbody>

<tr>

<td>1</td>

<td>1 May 2017</td>

<td>Satu</td>

<td>Satu</td>

</tr>

<tr>

<td>2</td>

<td>2 May 2017</td>

<td>Dua</td>

<td>Dua</td>

</tr>

<tr>

<td>3</td>

<td>3 May 2017</td>

<td>Tiga</td>

<td>Tiga</td>

</tr>

<tr>

<td>3</td>

<td>3 May 2017</td>

<td>Tiga</td>

<td>Tiga</td>

</tr>

<tr>

<td>2</td>

<td>2 May 2017</td>

<td>Dua</td>

<td>Dua</td>

</tr>

<tr>

<td>3</td>

<td>3 May 2017</td>

<td>Tiga</td>

<td>Tiga</td>

</tr>

<tr>

<td>3</td>

<td>3 May 2017</td>

<td>Tiga</td>

<td>Tiga</td>

</tr>

</tbody>

</table>

Frozen Column and Rows for large scrollable table

So I decided to start from scratch and finally was able to write something that works for multiple tables on a single page and only needs the application of a single class to the main table container to get desired results. Here is the code:

JS -

$(document).ready(function() {
var wrapper = document.getElementsByClassName('scroll-table-1');

for (var i = 0; i < wrapper.length; i++) {
wrapper[i].addEventListener('scroll', function (e) {

var table = this.closest('table');
var headerRow = table.querySelector('thead');
var firstCol = table.querySelectorAll('tbody tr td:first-child');
var topLeftCorner = table.querySelector('thead th:first-child');

headerRow.setAttribute('style','top:' + this.scrollTop + 'px');
topLeftCorner.setAttribute('style','left:' + this.scrollLeft + 'px');

firstCol.forEach((n) => {
n.setAttribute('style','left:' + this.scrollLeft + 'px');
});
});
};
});

CSS:

.scroll-table-1 {
position: relative;
display: block;
width: auto;
height: 350px;
overflow: scroll;
}

.scroll-table-1 thead {
position: relative;
display: block;
width: auto;
overflow: visible;
}

.scroll-table-1 tbody td {
background-color: #bbc;
min-width: 154px;
border: 1px solid #222;
display: table-cell;
}

.scroll-table-1 tbody tr td:nth-child(1) {
position: relative;
display: table-cell;
background-color: #99a;
min-width: 154px;
max-width: 154px;
z-index: 10;
}

.scroll-table-1 thead th {
background-color: #99a;
min-width: 154px;
border: 1px solid #222;
word-break: break-word;
z-index: 15;
}

.scroll-table-1 thead th:nth-child(1) {
position: relative;
display: table-cell;
background-color: #88b;
min-width: 154px;
max-width: 154px;
z-index: 100;
}

The strange thing is that it works perfectly smooth when I scroll on my monitor, but for some reason when I drag the window onto my mac screen the scrolling becomes a bit jittery/jumpy. I have no idea what is causing this nor do I believe it makes any sense but it works so I'll take it for now. If someone has an explanation as to why this bizarre display change behavior happens I would love to hear it.....

How do I freeze the first and last columns of an html table in a scrollable div?

I've experimented with a few methods (thanks to everyone who helped) and here's what I've come up with using jQuery. It seems to work well in all browsers I tested. Feel free to take it and use it however you wish. Next step for me will be turning it into a reusable jQuery plugin.

Summary:

I started with a normal table with everything in it (Id="ladderTable"), and I wrote Three methods - one to strip the first column, one to strip the last column, and one to fix the row heights.

The stripFirstColumn method creates a new table (Id="nameTable"), traverses the original table and takes out the first column, and adds those cells to the nameTable.

The stripLastColumn method does basically the same thing, except it takes out the last column and adds the cells to a new table called totalTable.

The fixHeights method looks at each row in each table, calculates the maximum height, and applies it to the related tables.

In the document ready event, I called all three methods in order. Note that all three tables float left so they'll just stack horizontally.

The HTML Structure:

<h1>Current Ladder</h1> 
<div id="nameTableSpan" style="float:left;width:100px;border-right:2px solid gray;"></div>
<div id="ladderDiv" style="float:left;width:423px;overflow:auto;border:1px solid gray;margin-top:-1px;">
<table id="ladderTable" class="ladderTable">
<thead>
<tr><td>Name</td><td>Round 1</td> ... <td>Round 50</td><td class="scoreTotal">Total</td></tr>
</thead>
<tr><td>Bob</td><td>11</td> ... <td>75</td><td>421</td></tr>
... (more scores)
</table>
</div>
<div id="totalTableSpan" style="float:left;width:70px;border-left:2px solid gray;"></div>

The jQuery:

function stripFirstColumn() {                
// pull out first column:
var nt = $('<table id="nameTable" cellpadding="3" cellspacing="0" style="width:100px;"></table>');
$('#ladderTable tr').each(function(i)
{
nt.append('<tr><td style="color:'+$(this).children('td:first').css('color')+'">'+$(this).children('td:first').html()+'</td></tr>');
});
nt.appendTo('#nameTableSpan');
// remove original first column
$('#ladderTable tr').each(function(i)
{
$(this).children('td:first').remove();
});
$('#nameTable td:first').css('background-color','#8DB4B7');
}

function stripLastColumn() {
// pull out last column:
var nt = $('<table id="totalTable" cellpadding="3" cellspacing="0" style="width:70px;"></table>');
$('#ladderTable tr').each(function(i)
{
nt.append('<tr><td style="color:'+$(this).children('td:last').css('color')+'">'+$(this).children('td:last').html()+'</td></tr>');
});
nt.appendTo('#totalTableSpan');
// remove original last column
$('#ladderTable tr').each(function(i)
{
$(this).children('td:last').remove();
});
$('#totalTable td:first').css('background-color','#8DB4B7');
}

function fixHeights() {
// change heights:
var curRow = 1;
$('#ladderTable tr').each(function(i){
// get heights
var c1 = $('#nameTable tr:nth-child('+curRow+')').height(); // column 1
var c2 = $(this).height(); // column 2
var c3 = $('#totalTable tr:nth-child('+curRow+')').height(); // column 3
var maxHeight = Math.max(c1, Math.max(c2, c3));

//$('#log').append('Row '+curRow+' c1=' + c1 +' c2=' + c2 +' c3=' + c3 +' max height = '+maxHeight+'<br/>');

// set heights
//$('#nameTable tr:nth-child('+curRow+')').height(maxHeight);
$('#nameTable tr:nth-child('+curRow+') td:first').height(maxHeight);
//$('#log').append('NameTable: '+$('#nameTable tr:nth-child('+curRow+')').height()+'<br/>');
//$(this).height(maxHeight);
$(this).children('td:first').height(maxHeight);
//$('#log').append('MainTable: '+$(this).height()+'<br/>');
//$('#totalTable tr:nth-child('+curRow+')').height(maxHeight);
$('#totalTable tr:nth-child('+curRow+') td:first').height(maxHeight);
//$('#log').append('TotalTable: '+$('#totalTable tr:nth-child('+curRow+')').height()+'<br/>');

curRow++;
});

if ($.browser.msie)
$('#ladderDiv').height($('#ladderDiv').height()+18);
}

$(document).ready(function() {
stripFirstColumn();
stripLastColumn();
fixHeights();
$("#ladderDiv").attr('scrollLeft', $("#ladderDiv").attr('scrollWidth')); // scroll to the last round
});

If you have any questions or if there's anything that wasn't clear, I'm more than happy to help.

It took me quite a while to work out that there was nothing that I could really reuse and it took a bit longer to write this. I'd hate for someone to go to the same trouble.



Related Topics



Leave a reply



Submit