<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Virtualized Table Demo</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
h1 {
color: #333;
}
.stats {
background-color: #f4f4f4;
padding: 10px;
margin: 10px 0;
border-radius: 4px;
}
#table-container {
border: 1px solid #ccc;
border-radius: 4px;
width: 100%;
max-width: 800px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.row-content {
display: flex;
border-bottom: 1px solid #eee;
background-color: #fff;
}
.row-content:hover {
background-color: #f9f9f9;
}
.cell {
padding: 10px;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.header {
display: flex;
background-color: #f0f0f0;
font-weight: bold;
border-bottom: 2px solid #ddd;
}
.header .cell {
padding: 10px;
}
/* Different background colors for even rows */
.virtual-row:nth-child(even) .row-content {
background-color: #f7f7f7;
}
.controls {
margin: 20px 0;
}
button {
padding: 8px 15px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<h1>Virtualized Table Demo</h1>
<div class="stats">
<div>Total Rows: <span id="total-rows">10,000</span></div>
<div>DOM Elements: <span id="dom-elements">0</span></div>
<div>Current Scroll Index: <span id="current-index">0</span></div>
</div>
<div class="controls">
<button id="scroll-to-middle">Scroll to Middle</button>
<button id="scroll-to-end">Scroll to End</button>
<button id="scroll-to-start">Scroll to Start</button>
</div>
<div class="header">
<div class="cell">ID</div>
<div class="cell">Name</div>
<div class="cell">Email</div>
<div class="cell">City</div>
</div>
<div id="table-container"></div>
<script>
// Generate a large amount of mock data
function generateMockData(count) {
const names = ['John', 'Jane', 'Michael', 'Emily', 'David', 'Sarah', 'Robert', 'Olivia', 'William', 'Sophia'];
const lastNames = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Miller', 'Davis', 'Garcia', 'Wilson', 'Martinez'];
const cities = ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix', 'Philadelphia', 'San Antonio', 'San Diego', 'Dallas', 'San Jose'];
return Array.from({ length: count }, (_, i) => ({
id: i + 1,
name: `${names[Math.floor(Math.random() * names.length)]} ${lastNames[Math.floor(Math.random() * lastNames.length)]}`,
email: `user${i + 1}@example.com`,
city: cities[Math.floor(Math.random() * cities.length)]
}));
}
class VirtualizedTable {
constructor(options) {
this.container = options.container;
this.data = options.data || [];
this.rowHeight = options.rowHeight || 40;
this.visibleRows = options.visibleRows || 10;
this.bufferRows = options.bufferRows || 5;
this.totalRows = this.data.length;
this.totalHeight = this.totalRows * this.rowHeight;
this.renderedRows = [];
this.setupContainer();
this.renderInitialView();
this.attachScrollHandler();
// Update stats
document.getElementById('total-rows').textContent = this.totalRows.toLocaleString();
}
setupContainer() {
this.container.style.position = 'relative';
this.container.style.overflow = 'auto';
this.container.style.height = (this.visibleRows * this.rowHeight) + 'px';
this.spacer = document.createElement('div');
this.spacer.style.height = this.totalHeight + 'px';
this.spacer.style.width = '100%';
this.spacer.style.position = 'relative';
this.container.appendChild(this.spacer);
}
renderInitialView() {
const totalRowsToRender = this.visibleRows + (this.bufferRows * 2);
for (let i = 0; i < Math.min(totalRowsToRender, this.totalRows); i++) {
this.createRowElement(i);
}
// Update DOM elements count in stats
document.getElementById('dom-elements').textContent = this.renderedRows.length;
}
createRowElement(dataIndex) {
const rowData = this.data[dataIndex];
const rowElement = document.createElement('div');
rowElement.className = 'virtual-row';
rowElement.style.position = 'absolute';
rowElement.style.top = (dataIndex * this.rowHeight) + 'px';
rowElement.style.height = this.rowHeight + 'px';
rowElement.style.width = '100%';
rowElement.innerHTML = `
<div class="row-content">
<div class="cell">${rowData.id}</div>
<div class="cell">${rowData.name}</div>
<div class="cell">${rowData.email}</div>
<div class="cell">${rowData.city}</div>
</div>
`;
rowElement.dataset.virtualIndex = dataIndex;
this.spacer.appendChild(rowElement);
this.renderedRows.push({
element: rowElement,
dataIndex: dataIndex
});
return rowElement;
}
updateRowElement(rowObj, newDataIndex) {
const { element } = rowObj;
const rowData = this.data[newDataIndex];
element.style.top = (newDataIndex * this.rowHeight) + 'px';
element.innerHTML = `
<div class="row-content">
<div class="cell">${rowData.id}</div>
<div class="cell">${rowData.name}</div>
<div class="cell">${rowData.email}</div>
<div class="cell">${rowData.city}</div>
</div>
`;
element.dataset.virtualIndex = newDataIndex;
rowObj.dataIndex = newDataIndex;
}
attachScrollHandler() {
this.container.addEventListener('scroll', () => {
this.updateVisibleRows();
// Update current index in stats
const firstVisibleIndex = Math.floor(this.container.scrollTop / this.rowHeight);
document.getElementById('current-index').textContent = firstVisibleIndex;
});
}
updateVisibleRows() {
const scrollTop = this.container.scrollTop;
const firstVisibleIndex = Math.floor(scrollTop / this.rowHeight);
const startIndex = Math.max(0, firstVisibleIndex - this.bufferRows);
const endIndex = Math.min(
this.totalRows - 1,
firstVisibleIndex + this.visibleRows + this.bufferRows
);
const rowsToUpdate = this.renderedRows.filter(row => {
return row.dataIndex < startIndex || row.dataIndex > endIndex;
});
const indicesToShow = [];
for (let i = startIndex; i <= endIndex; i++) {
const isRendered = this.renderedRows.some(row => row.dataIndex === i);
if (!isRendered) {
indicesToShow.push(i);
}
}
for (let i = 0; i < Math.min(rowsToUpdate.length, indicesToShow.length); i++) {
this.updateRowElement(rowsToUpdate[i], indicesToShow[i]);
}
}
scrollToIndex(index) {
const targetIndex = Math.min(Math.max(0, index), this.totalRows - 1);
this.container.scrollTop = targetIndex * this.rowHeight;
}
}
// Initialize the table when the page loads
document.addEventListener('DOMContentLoaded', () => {
const container = document.getElementById('table-container');
const mockData = generateMockData(10000); // 10,000 rows
const virtualTable = new VirtualizedTable({
container: container,
data: mockData,
rowHeight: 40,
visibleRows: 15,
bufferRows: 5
});
// Setup scroll buttons
document.getElementById('scroll-to-middle').addEventListener('click', () => {
virtualTable.scrollToIndex(5000);
});
document.getElementById('scroll-to-end').addEventListener('click', () => {
virtualTable.scrollToIndex(9999);
});
document.getElementById('scroll-to-start').addEventListener('click', () => {
virtualTable.scrollToIndex(0);
});
});
</script>
</body>
</html>
No comments:
Post a Comment