Skip to content

Instantly share code, notes, and snippets.

@coderek
Last active May 2, 2019 03:54
Show Gist options
  • Save coderek/86b8ba934f2c9bacdb14195344b98055 to your computer and use it in GitHub Desktop.
Save coderek/86b8ba934f2c9bacdb14195344b98055 to your computer and use it in GitHub Desktop.
Fixed column/header with infinite scrolling - https://jsbin.com/pulakak
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>Table</title>
<link rel="stylesheet" href="style.css" type="text/css" media="screen" title="no title" charset="utf-8">
</head>
<body onload="ready()">
<div id='container'></div>
<div id="log"></div>
<script charset="utf-8" src='main.js'></script>
</body>
</html>
const enableVirtualizedScrolling = true
const headerHeight = 30
const bodyHeight = 500
const headers = ['id', 'title', 'name', 'age', 'address']
const data = [
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize ', 'Building a ', ' th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
['The first component I worked', 'me realize what makes React such a powerful library. React’s componen', 'Building a good data table is a common design challenge most of us have had to solve at least once. By nature, a table has an inflexible grid shape with a nearly infinite potential to grow bot', 'For a data table to fit all screen sizes and orientations, it needs to accommodate th'],
]
function ready() {
const container = document.querySelector('#container')
const containerHeight = container.clientHeight
data.forEach((d, i) => d.unshift(i))
const {table,
measure,
pop,
push,
shift,
unshift} = renderTable(onScroll)
const maxBodyHeight = enableVirtualizedScrolling? containerHeight- headerHeight: Infinity // - table.querySelector('thead').clientHeight
container.appendChild(table)
const scroller = document.createElement('div')
scroller.style.width = container.clientWidth + 'px'
scroller.style.height = '5px'
scroller.style.overflowX = 'auto'
scroller.style.position = 'absolute'
scroller.style.bottom = 0 + 'px'
scroller.style.border = '1px solid aliceblue'
const scrollerContent = document.createElement('div')
scroller.appendChild(scrollerContent)
scrollerContent.style.width = table.querySelector('table').getBoundingClientRect().width + 'px'
scrollerContent.style.height = '5px'
// container.appendChild(scroller)
// scroller.addEventListener('scroll', function () {
// console.log('setting table scrollLeft', table.scrollLeft);
// table.scrollLeft = scroller.scrollLeft
// })
let bodyHeight = 0
let i = 0
while (bodyHeight < maxBodyHeight) {
const h = push(data)
console.log(h);
if (!h) break
bodyHeight += h
}
let maxRowHeight = 500
let minRowHeight = 100
container.addEventListener('scroll', debounce(onScroll))
function onScroll() {
const rowHeights = [...measure()]
// const tbodyHeight = rowHeights.reduce((s, r) => s+r, 0)
const tbodyHeight = table.querySelector('.scroll-container:nth-child(2) table').clientHeight
const scrollTop = table.querySelector('.scroll-container:nth-child(2)').scrollTop
const scrollBottom = tbodyHeight - scrollTop - maxBodyHeight
log('table: ' + tbodyHeight, 'containerHeight: ' + containerHeight, 'scrollTop: ' + scrollTop, 'scrollBottom: ' + scrollBottom, 'minRowHeight: ' + minRowHeight, 'maxRowHeight: ' + maxRowHeight);
if (scrollTop <= minRowHeight) {
unshift(data)
}
if (scrollTop > maxRowHeight) {
shift()
}
if (scrollBottom <= minRowHeight) {
push(data)
}
if (scrollBottom > maxRowHeight) {
pop()
}
setTimeout(onScroll, 1000)
}
}
function renderTable(onScroll) {
const table = document.createElement('table')
const headerTable = document.createElement('table')
const thead = document.createElement('thead')
const tbody = document.createElement('tbody')
const headRow = document.createElement('tr')
const accumulatedHeaderWidth = []
for (let i=0;i<headers.length;i++) {
const t = headers[i]
const th = document.createElement('th')
if (i<2) {
th.className = 'fixed'
setTimeout(() => {
th.style.left = i === 0? 0: (accumulatedHeaderWidth[i-1] + 'px')
if (accumulatedHeaderWidth.length === 0) {
accumulatedHeaderWidth.push(th.getBoundingClientRect().width)
} else {
accumulatedHeaderWidth.push(th.clientWidth + accumulatedHeaderWidth[accumulatedHeaderWidth.length -1])
}
})
}
th.textContent = t
headRow.appendChild(th)
}
thead.appendChild(headRow)
const clone = thead.cloneNode(true)
table.style.marginTop = -headerHeight + 'px'
table.appendChild(clone)
clone.style.visibility='hidden'
function renderRow(r) {
console.log(accumulatedHeaderWidth);
const tr = document.createElement('tr')
for (let i=0;i<r.length;i++) {
const d = r[i]
const td = document.createElement('td')
if (i<2) {
td.className = 'fixed'
setTimeout(() => {
td.style.left = i == 0? 0: (accumulatedHeaderWidth[i-1] + 'px')
})
}
td.textContent = d
tr.appendChild(td)
}
return tr
}
function getLastRow() {
if (tbody.children.length) {
return tbody.children[tbody.children.length - 1]
}
return null
}
function getFirstRow() {
if (tbody.children.length) {
return tbody.children[0]
}
return null
}
function push(data) {
const lastRow = getLastRow()
let idx = -1
if (lastRow) {
idx = parseInt(lastRow.getAttribute('idx'), 10)
if (data.length <= idx + 1) {
return null
}
}
const d = data[idx+1]
const appended = tbody.appendChild(renderRow(d))
appended.setAttribute('idx', idx+1)
return appended.clientHeight
}
function pop() {
const lastRow = getLastRow()
if (lastRow) {
tbody.removeChild(lastRow)
}
}
function unshift(d) {
const firstRow = getFirstRow()
if (firstRow && parseInt(firstRow.getAttribute('idx'), 10) > 0) {
const dataIndex = parseInt(firstRow.getAttribute('idx'), 10) - 1
const inserted = tbody.insertBefore(renderRow(data[dataIndex]), firstRow)
inserted.setAttribute('idx', dataIndex)
return inserted.clientHeight
}
}
function shift() {
const firstRow = getFirstRow()
if (firstRow) {
tbody.removeChild(firstRow)
}
}
table.appendChild(tbody)
function * measure() {
for(const c of tbody.children) {
yield c.clientHeight
}
}
const innerContainer = document.createElement('div')
const wrapper2 = document.createElement('div')
wrapper2.className = 'scroll-container'
const wrapper1 = document.createElement('div')
wrapper1.className = 'scroll-container'
wrapper2.appendChild(table)
headerTable.appendChild(thead)
wrapper1.appendChild(headerTable)
wrapper1.style.overflow = 'hidden'
innerContainer.appendChild(wrapper1)
innerContainer.appendChild(wrapper2)
headerTable.style.height = headerHeight + 'px'
wrapper2.style.height = bodyHeight + 'px'
wrapper2.addEventListener('scroll', function () {
onScroll()
wrapper1.scrollLeft = wrapper2.scrollLeft
})
return {
table: innerContainer,
measure,
pop,
push,
shift,
unshift,
}
}
function log(...args) {
const s = args.map(a => a.toString()).join(' ')
const logEl = document.querySelector('#log')
logEl.textContent = s
}
function debounce(cb) {
let start = Date.now()
let fired = false
return function () {
if (fired) {
start = Date.now()
}
if (Date.now() - start < 1000) {
return
}
setTimeout(cb, 1000)
}
}
#container {
max-width: 100vw;
position: relative;
height: 600px;
}
.scroll-container {
overflow: auto;
}
.scroll-container:nth-child(2) {
}
.scroll-container:nth-child(1) {
}
.fixed {
position: sticky;
left: 0;
white-space: unset;
color: #b8cef2;
text-align: left;
backface-visibility: hidden;
word-wrap: break-word;
word-break: break-word;
overflow-wrap: break-word;
box-shadow: 1px 0px 3px white;
background-color: black;
}
table {
color: #47494c;
width: 900px;
border-collapse: collapse;
/* border: 1px solid black; */
border-spacing: 0px;
}
td, th {
border: 1px solid white;
color: #fff;
}
th {
min-width: 200px;
}
@import url(https://fonts.googleapis.com/css?family=Roboto:400,500,300,700);
body{
background: -webkit-linear-gradient(left, #25c481, #25b7c4);
background: linear-gradient(to right, #25c481, #25b7c4);
font-family: 'Roboto', sans-serif;
}
section{
margin: 50px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment