Last active
May 2, 2019 03:54
-
-
Save coderek/86b8ba934f2c9bacdb14195344b98055 to your computer and use it in GitHub Desktop.
Fixed column/header with infinite scrolling - https://jsbin.com/pulakak
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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