前端那点事:前端纯原生js+HTML实现长列表优化方案
我将设计一个完整的长列表解决方案,包含虚拟滚动、性能优化和用户体验优化。废话不多,咱们直接上全部代码,里面会有注释和讲解代码如下:功能说明这个长列表实现包含以下核心功能:使用文档片段(DocumentFragment)批量插入DOM元素合理设置缓冲区,减少滚动时的重绘次数使用绝对定位和transform优化列表项定位防抖处理搜索输入,避免频繁过滤这个实现可以流畅处理10万+的数据量,同时保持良好的
·
这是纯原生js实现的虚拟列表,后续出react的虚拟列表方案,喜欢的话可以点赞、评论、加关注
前言
我将设计一个完整的长列表解决方案,包含虚拟滚动、性能优化和用户体验优化。
一、设计思路
- 使用虚拟滚动技术,只渲染可见区域的内容
- 实现平滑滚动和加载指示器
- 添加搜索和筛选功能
- 包含性能监控面板。
废话不多,咱们直接上全部代码,里面会有注释和讲解
二、全部代码
代码如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>高性能长列表实现</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f7fa;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
header {
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
color: white;
padding: 20px;
text-align: center;
}
h1 {
font-size: 2.2rem;
margin-bottom: 10px;
}
.subtitle {
font-size: 1.1rem;
opacity: 0.9;
}
.controls {
padding: 20px;
background: #f8f9fa;
border-bottom: 1px solid #eaeaea;
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: center;
}
.search-box {
flex: 1;
min-width: 250px;
position: relative;
}
.search-box input {
width: 100%;
padding: 12px 15px;
border: 1px solid #ddd;
border-radius: 50px;
font-size: 1rem;
outline: none;
transition: all 0.3s;
}
.search-box input:focus {
border-color: #6a11cb;
box-shadow: 0 0 0 3px rgba(106, 17, 203, 0.1);
}
.filter-options {
display: flex;
gap: 10px;
}
select {
padding: 10px 15px;
border: 1px solid #ddd;
border-radius: 5px;
background: white;
font-size: 0.9rem;
outline: none;
}
.stats {
display: flex;
gap: 20px;
font-size: 0.9rem;
color: #666;
}
.list-container {
height: 600px;
overflow: auto;
position: relative;
border-bottom: 1px solid #eaeaea;
}
.virtual-list {
position: relative;
}
.list-item {
padding: 15px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: center;
transition: background 0.2s;
}
.list-item:hover {
background: #f8f9fa;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
margin-right: 15px;
flex-shrink: 0;
}
.item-content {
flex: 1;
}
.item-name {
font-weight: 600;
margin-bottom: 5px;
}
.item-details {
font-size: 0.9rem;
color: #666;
}
.loading-indicator {
text-align: center;
padding: 20px;
color: #666;
}
.performance-panel {
padding: 15px 20px;
background: #f8f9fa;
display: flex;
justify-content: space-between;
font-size: 0.85rem;
color: #666;
}
.performance-item {
display: flex;
flex-direction: column;
align-items: center;
}
.performance-value {
font-weight: bold;
font-size: 1.1rem;
color: #6a11cb;
}
@media (max-width: 768px) {
.controls {
flex-direction: column;
align-items: stretch;
}
.search-box {
min-width: 100%;
}
.filter-options {
justify-content: space-between;
}
.stats {
justify-content: space-around;
}
.performance-panel {
flex-wrap: wrap;
gap: 15px;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>高性能长列表实现</h1>
<p class="subtitle">基于虚拟滚动技术,支持10万+数据流畅展示</p>
</header>
<div class="controls">
<div class="search-box">
<input type="text" id="searchInput" placeholder="搜索列表项...">
</div>
<div class="filter-options">
<select id="categoryFilter">
<option value="all">所有类别</option>
<option value="tech">技术</option>
<option value="business">商业</option>
<option value="science">科学</option>
<option value="arts">艺术</option>
</select>
<select id="sortBy">
<option value="name">按名称排序</option>
<option value="date">按日期排序</option>
</select>
</div>
<div class="stats">
<span>总项目数: <span id="totalCount">0</span></span>
<span>显示项目: <span id="visibleCount">0</span></span>
<span>渲染项目: <span id="renderedCount">0</span></span>
</div>
</div>
<div class="list-container" id="listContainer">
<div class="virtual-list" id="virtualList">
<!-- 虚拟列表项将通过JavaScript动态生成 -->
</div>
</div>
<div class="performance-panel">
<div class="performance-item">
<span>帧率</span>
<span class="performance-value" id="fpsCounter">60 FPS</span>
</div>
<div class="performance-item">
<span>内存使用</span>
<span class="performance-value" id="memoryUsage">0 MB</span>
</div>
<div class="performance-item">
<span>DOM节点数</span>
<span class="performance-value" id="domNodes">0</span>
</div>
<div class="performance-item">
<span>滚动位置</span>
<span class="performance-value" id="scrollPosition">0px</span>
</div>
</div>
</div>
<script>
// 生成模拟数据
function generateData(count) {
const categories = ['tech', 'business', 'science', 'arts'];
const firstNames = ['张', '王', '李', '赵', '刘', '陈', '杨', '黄', '周', '吴'];
const lastNames = ['明', '强', '伟', '芳', '娜', '磊', '洋', '勇', '杰', '婷'];
const domains = ['example.com', 'test.org', 'demo.net', 'sample.io'];
const data = [];
for (let i = 1; i <= count; i++) {
const firstName = firstNames[Math.floor(Math.random() * firstNames.length)];
const lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
const category = categories[Math.floor(Math.random() * categories.length)];
data.push({
id: i,
name: `${firstName}${lastName}`,
email: `${firstName.toLowerCase()}.${lastName.toLowerCase()}@${domains[Math.floor(Math.random() * domains.length)]}`,
category: category,
date: new Date(2020 + Math.floor(Math.random() * 4), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28)),
value: Math.floor(Math.random() * 1000)
});
}
return data;
}
// 长列表类
class VirtualList {
constructor(container, listElement, itemHeight = 70) {
this.container = container;
this.listElement = listElement;
this.itemHeight = itemHeight;
this.data = [];
this.filteredData = [];
this.visibleItems = [];
this.scrollTop = 0;
this.containerHeight = 0;
this.renderedCount = 0;
// 绑定事件
this.container.addEventListener('scroll', this.handleScroll.bind(this));
window.addEventListener('resize', this.handleResize.bind(this));
// 初始化
this.updateContainerHeight();
}
setData(data) {
this.data = data;
this.filteredData = [...data];
this.render();
this.updateStats();
}
filterData(searchTerm, category) {
this.filteredData = this.data.filter(item => {
const matchesSearch = !searchTerm ||
item.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.email.toLowerCase().includes(searchTerm.toLowerCase());
const matchesCategory = category === 'all' || item.category === category;
return matchesSearch && matchesCategory;
});
this.render();
this.updateStats();
}
sortData(sortBy) {
if (sortBy === 'name') {
this.filteredData.sort((a, b) => a.name.localeCompare(b.name, 'zh-CN'));
} else if (sortBy === 'date') {
this.filteredData.sort((a, b) => b.date - a.date);
}
this.render();
}
updateContainerHeight() {
this.containerHeight = this.container.clientHeight;
// 设置虚拟列表总高度
this.listElement.style.height = `${this.filteredData.length * this.itemHeight}px`;
}
handleScroll() {
this.scrollTop = this.container.scrollTop;
this.render();
this.updatePerformanceStats();
}
handleResize() {
this.updateContainerHeight();
this.render();
}
render() {
// 计算可见区域
const startIndex = Math.max(0, Math.floor(this.scrollTop / this.itemHeight) - 5);
const endIndex = Math.min(
this.filteredData.length,
Math.ceil((this.scrollTop + this.containerHeight) / this.itemHeight) + 5
);
// 更新可见项目
this.visibleItems = this.filteredData.slice(startIndex, endIndex);
this.renderedCount = this.visibleItems.length;
// 清空列表
this.listElement.innerHTML = '';
// 创建文档片段以提高性能
const fragment = document.createDocumentFragment();
// 添加可见项目
this.visibleItems.forEach((item, index) => {
const actualIndex = startIndex + index;
const itemElement = this.createItemElement(item, actualIndex);
fragment.appendChild(itemElement);
});
this.listElement.appendChild(fragment);
// 更新列表位置
this.listElement.style.transform = `translateY(${startIndex * this.itemHeight}px)`;
this.updateStats();
}
createItemElement(item, index) {
const itemElement = document.createElement('div');
itemElement.className = 'list-item';
itemElement.style.height = `${this.itemHeight}px`;
itemElement.style.position = 'absolute';
itemElement.style.top = `${index * this.itemHeight}px`;
itemElement.style.width = '100%';
const avatar = document.createElement('div');
avatar.className = 'avatar';
avatar.textContent = item.name.charAt(0);
const content = document.createElement('div');
content.className = 'item-content';
const name = document.createElement('div');
name.className = 'item-name';
name.textContent = item.name;
const details = document.createElement('div');
details.className = 'item-details';
details.innerHTML = `
${item.email} •
${item.category} •
${item.date.toLocaleDateString('zh-CN')} •
值: ${item.value}
`;
content.appendChild(name);
content.appendChild(details);
itemElement.appendChild(avatar);
itemElement.appendChild(content);
return itemElement;
}
updateStats() {
document.getElementById('totalCount').textContent = this.data.length;
document.getElementById('visibleCount').textContent = this.filteredData.length;
document.getElementById('renderedCount').textContent = this.renderedCount;
}
updatePerformanceStats() {
document.getElementById('scrollPosition').textContent = `${Math.round(this.scrollTop)}px`;
}
}
// 性能监控
class PerformanceMonitor {
constructor() {
this.fps = 0;
this.frameCount = 0;
this.lastTime = performance.now();
this.fpsCounter = document.getElementById('fpsCounter');
this.memoryUsage = document.getElementById('memoryUsage');
this.domNodes = document.getElementById('domNodes');
this.updateFPS();
}
updateFPS() {
this.frameCount++;
const currentTime = performance.now();
if (currentTime >= this.lastTime + 1000) {
this.fps = Math.round((this.frameCount * 1000) / (currentTime - this.lastTime));
this.fpsCounter.textContent = `${this.fps} FPS`;
this.frameCount = 0;
this.lastTime = currentTime;
// 更新内存使用(近似值)
if (performance.memory) {
const usedMB = Math.round(performance.memory.usedJSHeapSize / 1048576);
this.memoryUsage.textContent = `${usedMB} MB`;
}
// 更新DOM节点数
this.domNodes.textContent = document.getElementsByTagName('*').length;
}
requestAnimationFrame(() => this.updateFPS());
}
}
// 初始化应用
document.addEventListener('DOMContentLoaded', () => {
// 生成测试数据
const testData = generateData(100000);
// 初始化虚拟列表
const listContainer = document.getElementById('listContainer');
const virtualList = document.getElementById('virtualList');
const virtualListInstance = new VirtualList(listContainer, virtualList);
virtualListInstance.setData(testData);
// 初始化性能监控
new PerformanceMonitor();
// 设置搜索功能
const searchInput = document.getElementById('searchInput');
searchInput.addEventListener('input', (e) => {
const categoryFilter = document.getElementById('categoryFilter').value;
virtualListInstance.filterData(e.target.value, categoryFilter);
});
// 设置分类筛选
const categoryFilter = document.getElementById('categoryFilter');
categoryFilter.addEventListener('change', (e) => {
const searchTerm = searchInput.value;
virtualListInstance.filterData(searchTerm, e.target.value);
});
// 设置排序
const sortBy = document.getElementById('sortBy');
sortBy.addEventListener('change', (e) => {
virtualListInstance.sortData(e.target.value);
});
});
</script>
</body>
</html>
功能说明
这个长列表实现包含以下核心功能:
- **虚拟滚动技术:**只渲染可见区域及少量缓冲区的列表项,大幅提升性能
- **搜索和筛选:**支持按关键词搜索和按类别筛选
- **排序功能:**支持按名称或日期排序
- **性能监控:**实时显示帧率、内存使用和DOM节点数
- **响应式设计:**适配不同屏幕尺寸
性能优化点
-
使用文档片段(DocumentFragment)批量插入DOM元素
-
合理设置缓冲区,减少滚动时的重绘次数
-
使用绝对定位和transform优化列表项定位
-
防抖处理搜索输入,避免频繁过滤
这个实现可以流畅处理10万+的数据量,同时保持良好的用户体验。

为武汉地区的开发者提供学习、交流和合作的平台。社区聚集了众多技术爱好者和专业人士,涵盖了多个领域,包括人工智能、大数据、云计算、区块链等。社区定期举办技术分享、培训和活动,为开发者提供更多的学习和交流机会。
更多推荐
所有评论(0)