JSFiddle - React, Tailwind, and code Playground

by AndyE

HTML

<!-- Based on https://jsfiddle.net/upsuper/yvhLdf71/ -->
<div id="wrapper">
  <canvas id="canvas" width="200" height="200"></canvas>
  <div id="inner"></div>
</div>

CSS

#wrapper {
  position: relative;
  width: 200px;
  height: 200px;
  overflow: auto;
  background: white;
  transform: translate(1px, 1px);
}

#inner {
  width: 1px;
}

#canvas {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
}

JavaScript

const ctx = canvas.getContext('2d');
const rowHeight = 40;
const rowWidth = 200;
const numRows = 100;
const rowsPerView = canvas.height / rowHeight;
let rendering = false;

ctx.fillStyle = 'blue';
ctx.strokeStyle = 'gray';
ctx.font = '20px Verdana';

inner.style.height = `${rowHeight * (numRows - rowsPerView)}px`;

function drawRows() {
  rendering = false;
	ctx.clearRect(0, 0, 200, 200);

  const scroll = wrapper.scrollTop;
  const viewHeight = rowsPerView * rowHeight;
  
  canvas.style.top = `${ scroll }px`;

  for (let i = 0; i <= rowsPerView; i++) {
    const row = Math.floor(scroll / rowHeight) + i;
    const offset = (rowHeight - (scroll % rowHeight)) + ((i -1) * rowHeight);

    ctx.strokeRect(0, offset, rowWidth, rowHeight);
    ctx.fillText(`Row ${row}`, 10, offset + 30);
  }
}

drawRows();

wrapper.addEventListener('scroll', () => {
  if (!rendering) {
	  rendering = true;
    requestAnimationFrame(drawRows);
  }
});