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;...