JSFiddle - React, Tailwind, and code Playground

by Jeado

HTML

<div id="viewport">
    <div id="content"></div>
</div>

<div id="debug"></div>

CSS

#debug {
    margin-left: 400px;
    width: 200px;
    background: beige;
    font-size: 9pt;
    opacity: 0.5;
    border: 1px solid gray;
    padding: 10px;
}

#viewport {
    float: left;
    border: 1px solid black;
    overflow: auto;
    width: 350px;
}

#content {
    position: relative;
    overflow: hidden;
}

.row {
    position: absolute;
    left: 0px;
    width: 100%;
    border-bottom: 1px dotted blue;
    font-size: 9pt;
}

.row:hover {
    background: rgba(0,0,255,0.05);
}

JavaScript

var th = 1000000000;            // virtual height
var h =  1000000;               // real scrollable height
var ph = h / 100;               // page height
var n = Math.ceil(th / ph);     // number of pages
var vp = 400;                   // viewport height
var rh = 50;                    // row height
var cj = (th - h) / (n - 1);    // "jumpiness" coefficient

var page = 0;                   // current page
var offset=0;                   // current page offset
var prevScrollTop = 0;

var rows = {};                  // cached row nodes

var viewport, content;


$(function() {
    viewport = $("#viewport");
    content = $("#content");
    
    viewport.css("height",vp);
    content.css("height",h);
    
    viewport.scroll(onScroll);
    viewport.trigger("scroll");
});

function onScroll() {
    var scrollTop = viewport.scrollTop();
    
    if (Math.abs(scrollTop-prevScrollTop) > vp) 
        onJump();
    else
        onNearScroll();
    
    renderViewport();
    
    logDebugInfo();
}

function onNearScroll() {
    var scrollTop = viewport.scrollTop();
    
    // next page
    if (scrollTop + offset > (page+1)*ph) {
        page++;
        offset = Math.round(page * cj);
        viewport.scrollTop(prevScrollTop = scrollTop - cj);
        removeAllRows();
    }
    // prev page
    else if (scrollTop + offset < page*ph) {
        page--;
        offset = Math.round(page * cj);
        viewport.scrollTop(prevScrollTop = scrollTop + cj);
        removeAllRows();
    }
    else
        prevScrollTop = scrollTop;
}

function onJump() {
    var scrollTop = viewport.scrollTop();
    page = Math.floor(scrollTop * ((th-vp) / (h-vp)) * (1/ph));
    offset = Math.round(page * cj);
    prevScrollTop = scrollTop;
    
    removeAllRows();
}

function removeAllRows() {
    for (var i in rows) {
        rows[i].remove();
        delete rows[i];
    }
}

function renderViewport() {
    // calculate the viewport + buffer
    var y = viewport.scrollTop() + offset,
       ...