Sticky table head and foot

Pure javascript using transformation. No DOM tree changing.

HTML

<h1>Sticky table head and table foot</h1>

<p>This example uses <b>transformations</b> which avoids cloning of tables.
However, the table head cannot have a transparent background,
it must be opaque.
Furthermore, problems arise with collapsing table borders,
so use separate borders for each cell.
<table border>
<caption>This is a demo table</caption>
  <thead>
    <tr><th>Column 1<th>Column 2<th>Column 3
    </thead>
     <tr><td>info<td>1<td>info
     <tr><td>info<td>2<td>info
     <tr><td>info<td>some really long line here instead<td>info
     <tr><td>info<td>3<td>info
     <tr><td>info<td>4<td>info
     <tr><td>info<td>5<td>info
     <tr><td>info<td height=30>6<td>info
     <tr><td>info<td>7<td>info
     <tr><td>info<td>8<td>info
     <tr><td>info<td>9<td>info
     <tr><td>info<td>10<td>info
     <tr><td>info<td>11<td>info
     <tr><td>info<td height=30>12<td>info
 <tfoot><tr><td colspan=2>Total<td>30 €</tfoot>
</table>
<p>Firefox output is a bit flickery but the DOM tree is kept in original form
so it's best for more DOM modifications as sorting.
Keeping <b>tfoot</b> visible is a little bit optional.
As a good gimmick, <b>caption</b> goes into calculation too, for top or bottom side given by CSS <tt>table.caption-side</tt>.

<p>It doesn't work in Internet Explorer 11 but in Firefox and Chrome.

CSS

body { height: 1000px; counter-reset:TBL;}
thead{background-color:yellow;}
tfoot{background-color:lightgray;}
/*thead>tr>th {  border:1px solid black;}*/
table {caption-side:bottom-outside; border-collapse:separate;margin:0.8em 0;}
caption {font-style:italic;}
caption:before {content:"Table "counter(TBL)": ";counter-increment:TBL;}
h1 {font-size:1.5em;background:lightgreen;border-radius:8px;padding:0.3em 1em;margin:0.5em;}

JavaScript

// Anonymous function wrapper (encapsulates all variables)
(function(){
 function onResize() {
// Precalculate heights after page load (and after each page resize),
// not for each scroll event again
  var tbl=document.querySelector("table");
  var size =tbl.getBoundingClientRect();
  var top=size.top;
  var bottom=size.bottom;
  var thead=tbl.querySelector("thead");
  var hhead=thead?thead.getBoundingClientRect().height:0;
  var tfoot=tbl.querySelector("tfoot");
  var hfoot=tfoot?tfoot.getBoundingClientRect().height:0;
  var tcapt=tbl.querySelector("caption");
  var hcapt=tcapt?tcapt.getBoundingClientRect().height:0;
  if (getComputedStyle(tbl).captionSide.match("bottom")) bottom-=hcapt;
  else top+=hcapt;
  var tfrst=tbl.querySelector("tbody>tr:first-child");
  var hfrst=tfrst?tfrst.getBoundingClientRect().height:0;
  var tlast=tbl.querySelector("tbody>tr:last-child");
  var hlast=tlast?tlast.getBoundingClientRect().height:0;
// Now we have all constant data to capture into the scroll event handler
  function onScroll() {
   var dY=window.pageYOffset;
   if (thead && dY>top  && dY<bottom) {
// This will generate a smooth scroll-out effect for a visible last tbody line
    var ey=bottom-hfoot-hhead-hlast;	// last useful placement
    if (dY>ey) dY=ey;
// Firefox seems to need this 1px correction (for the border width?)
    thead.style.transform="translate(0,"+(dY-top-1)+"px)";
   }else{
    thead.style.transform="inherit";	// resets translation
   }
   var h=window.innerHeight;
   if (tfoot && dY+h<bottom && dY<bottom) {
    dY+=h-bottom;
// This will generate a smooth scroll-out effect for a visible first tbody line
    var ay=top+hhead+hfrst+hfoot-bottom;	// negative
    if (dY<ay) dY=ay;	// too negative: limit
    tfoot.style.transform="translate(0,"+(dY+1)+"px)";
   }else{
    tfoot.style.transform="inherit";	// resets translation
   }
  };
  window.removeEventListener("scroll",onScroll);
  onScroll();	// vorher schon umplatzieren
 ...