JSFiddle - React, Tailwind, and code Playground
HTML
Здесь представлены разные таблицы и ситуации. Но функция для фиксирования заголовков/столбцов универсальная. <button onclick="FixAction(this)">Зафиксировать</button>
<p>Простой случай. Зафиксирована 1 строка и 1 столбец. Объединенных ячеек нет.</p>
<table id="t1" style="width:520px;">
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
</table>
<p>Здесь зафиксировано 2 строки и 1 столбец. Простое объединение ячеек</p>
<table id="t2" style="width:640px;">
<tr><td rowspan=2 class="cntr">ФИО</td> <td colspan=4 class="cntr">2015 год</td> <td colspan=4 class="cntr">2016</td> </tr>
<tr><td class="cntr">1-й квартал</td><td class="cntr">2-й квартал</td><td class="cntr">3-й квартал</td><td class="cntr">4-й квартал</td>
<td class="cntr">1-й квартал</td><td class="cntr">2-й квартал</td><td class="cntr">3-й квартал</td><td...
CSS
* {font:13px arial,sans-serif;}
p {margin:28px 0 3px 0;}
.divFixHeaderCol {
position:relative;
border:0;
border-left:1px solid #d0d0d0;
border-bottom:1px solid #d0d0d0;
overflow:hidden;
}
.divFixHeaderCol table {border-collapse:collapse;}
.divFixHeaderCol td {
font:400 11px tahoma,arial,sans-serif;
padding:2px;
border:1px solid #d0d0d0;
border-left:0;
background:#fff;
min-height:14px; /* если высота клиентской части в ячейке меньше 14px, то IE криво отрисовывает горизонтальные линии в фиксированной области */
/* Самое интересное, что padding ни на что не влияет. Можно, например, сделать padding=16px и если клиентская высота < 14px, то всё равно IE криво отрисует линии */
/* Если padding=0, а клиентская высота >= 14px, то будет нормально, IE ровно отрисует линии */
}
.divFixHeaderCol .fixRegion td {
background:#fff no-repeat;
background-image:linear-gradient(#eee,#eee);
background-position:1px 1px;
}
.cntr {text-align:center;}
JavaScript
function gid(i) {return document.getElementById(i);}
function CEL(s) {return document.createElement(s);}
function ACH(p,c) {p.appendChild(c);}
function getScrollWidth() {
var dv = CEL('div');
dv.style.overflowY = 'scroll'; dv.style.width = '50px'; dv.style.height = '50px'; dv.style.position = 'absolute';
dv.style.visibility = 'hidden';//при display:none размеры нельзя узнать. visibility:hidden - сохраняет геометрию, а выше было position=absolute - не сломает разметку
ACH(document.body,dv);
var scrollWidth = dv.offsetWidth - dv.clientWidth;
document.body.removeChild(dv);
return (scrollWidth);
}
function setSum(tbl, rr, cc) {
var rowCount = tbl.rows.length, sum = '';
for (var i=rr; i<rowCount; i++) {
var row = tbl.rows[i];
for (var j=cc; j < row.cells.length; j++) {
sum = Math.floor(Math.random()*10000) + '';
row.cells[i,j].innerHTML = sum.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1 ');
row.cells[i,j].style.textAlign = 'right';
}
}
}
function FixAction(el) {
FixHeaderCol(gid('t1'),1,1,400,170);
FixHeaderCol(gid('t2'),2,1,400,214);
FixHeaderCol(gid('t3'),10,6,1000,415);
FixHeaderCol(gid('t4'),1,0,340,120);
FixHeaderCol(gid('t5'),0,2,340,150);
el.parentNode.removeChild(el);
}
function FixHeaderCol(tbl, fixRows, fixCols, ww, hh) {
var scrollWidth = getScrollWidth(), cont = CEL('div'), tblHead = CEL('table'), tblCol = CEL('table'), tblFixCorner = CEL('table');
cont.className = 'divFixHeaderCol';
cont.style.width = ww + 'px'; cont.style.height = hh + 'px';
tbl.parentNode.insertBefore(cont,tbl);
ACH(cont,tbl);
var rows = tbl.rows, rowsCnt = rows.length, i=0, j=0, colspanCnt=0, columnCnt=0, newRow, newCell, td;
// Берем самую первую строку (это rows[0]) и получаем истинное число столбцов в ТАБЛИЦЕ (учитывается colspan)
for (j=0; j<rows[0].cells.length; j++) {columnCnt += rows[0].cells[j].colSpan;}
var delta = columnCnt - fixCols;
// Пробежимся один раз по всем строкам и построим наши фиксированные таблицы
for (i=0; i<rowsCnt;...