前端-使用虚拟滚动和Web Workers加载大量list数据的方案
1. 应用场景
可尝试用于数据量大时,列表渲染慢且卡顿的页面
另外是否可考虑预加载列表逻辑?
2. 代码
2.1 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>加载大量list数据-虚拟滚动和Web Workers的示例</title>
<style>
.div1 {
height: 100px;
width: 100%;
background-color: yellow;
margin: 5px;
/* div中的文字居中方式1 */
display: flex;
justify-content: center;
align-items: center;
/* div中的文字居中方式2 */
/* display: grid;
place-items: center; */
}
.search {
width: 100%;
margin: 5px;
}
.div2 {
width: 100%;
background-color: orange;
margin: 5px;
word-break: break-all;
}
.list {
height: 300px;
overflow-y: scroll;
border: 1px solid black;
margin: 5px;
}
#progress {
margin: 5px;
height: 30px;
}
.row {
height: 40px;
line-height: 40px;
border-bottom: 1px solid #ccc;
}
</style>
</head>
<body>
<div class="div1">加载大量list数据-虚拟滚动和Web Workers的示例
</div>
<div class="search">
<input placeholder="请输入" id="input" />
<button id="search">搜索</button>
</div>
<div id="progress">当前进度:</div> <!-- 共多少条,当前正在加载弟多少条 -->
<div class="list" id="list"></div>
<pre class="div2">
Web Workers 兼容性
【如果实现担心兼容性的问题,就直接改造成js模拟分页吧,关键词 "js模拟分页和筛选"】
Web Workers 是 HTML5 引入的一项技术,它允许在浏览器后台独立于主线程运行脚本,
避免了长时间运行的脚本导致的页面冻结。Web Workers 的兼容性在现代浏览器中是非
常广泛的,但仍然存在一些差异和限制。以下是主要浏览器对 Web Workers 的支持情况:
Google Chrome: 自 Chrome 7(2008年12月)起支持 Web Workers。
Mozilla Firefox: 自 Firefox 3.5(2009年6月)起支持 Web Workers。
Apple Safari: 自 Safari 4(2009年6月)起支持 Web Workers。
Microsoft Edge: 原始的 Edge 浏览器(基于 EdgeHTML)自 Edge 12(2015年7月)
起支持 Web Workers。新版 Edge(基于 Chromium)自发布之初就支持 Web Workers。
Internet Explorer: Internet Explorer 不支持 Web Workers。即使是最新版本的
IE11 也不支持这项技术。
Opera: 自 Opera 10.5(2010年3月)起支持 Web Workers。
Android Webview: 自 Android 4.4(2013年10月)起支持 Web Workers。
iOS Safari: 自 iOS 3.2(2010年4月)起支持 Web Workers。
兼容性注意事项:
服务工作线程(Service Workers):这是一种特殊的 Web Worker,用于实现离线缓存
和推送通知等功能。它的兼容性与普通 Web Workers 类似,但IE和部分旧版浏览器不支持。
Shared Workers:允许多个脚本共享一个 Worker 实例,以节省资源。它的兼容性与普通
Web Workers 类似,但IE和其他一些较旧的浏览器不支持。
Worker 全局作用域:self 关键字在 Worker 内部引用全局作用域,这与主线程中的
window 相似。不同之处在于 Worker 不提供 window 对象。
Blob 和 File 对象:在 Web Workers 中,Blob 和 File 对象的兼容性可能受限,
因为它们的实现可能与主线程不同。
XMLHttpRequest 和 Fetch API:虽然 Web Workers 支持使用 XMLHttpRequest 和
Fetch API 进行网络请求,但它们的使用有一些限制,例如 CORS 需要正确的设置。
兼容性检测:
在代码中,你可以使用以下方式检测 Web Worker 是否可用:
if (typeof Worker !== 'undefined') {
// Web Worker 支持
} else {
// Web Worker 不支持
}
总的来说,Web Workers 在现代浏览器中是广泛支持的,但在开发时仍然需要考虑不支持
Web Workers 的场景,比如为 IE 用户提供回退方案。在生产环境中,使用类似
Can I Use 或 MDN 的资源来检查兼容性是一个好习惯。2</pre>
<script>
let isShowAllOnce = true; //异步加载所有 【支持修改】
let data = []; // Your data array here
const filterCriteria = {}; // Your filter criteria object here
const worker = new Worker('worker.js');
const listElement = document.getElementById('list');
const search = document.getElementById('search');
const input = document.getElementById('input');
const progress = document.getElementById('progress');
const pageSize = 150;
let currentStartRow = 0;
let currentEndRow = pageSize; // Initial number of rows to load
const rowHeight = 1; //40 有问题
let isLoadingMore = false; //保持顺序和防止重复点击
let lastClickSearchTimestamp = null; //保持顺序和防止重复点击
for (var i = 0; i < 5000; i++) {
const item = {
"name": "第" + (i + 1) + "人",
"orderNum": 1 + i
};
data.push(item);
}
console.log("data size =" + data.length)
// Initialize the list
loadMoreRows();
// Add a scroll event listener to load more rows as needed
listElement.addEventListener('scroll', function() {
if (isScrolledToBottom()) {
loadMoreRows();
console.log("isScrolledToBottom true")
} else {
console.log("isScrolledToBottom false")
}
});
// Function to check if the user has scrolled to the bottom
function isScrolledToBottom() {
const {
scrollTop,
scrollHeight,
clientHeight
} = listElement;
let abs = Math.abs((scrollTop + clientHeight) - scrollHeight);
// console.log("abs=" + abs)
return abs < 10;
}
// Function to load more rows using the Web Worker
function loadMoreRows() {
// console.log("loadMoreRows")
if (isLoadingMore) {
return;
}
isLoadingMore = true;
worker.postMessage({
//key value相同写一个
data,
filterCriteria,
startRow: currentStartRow,
endRow: currentEndRow,
lastClickSearchTimestamp
});
}
// Listen for messages from the Web Worker
worker.onmessage = function(event) {
const dataObj = event.data;
const newData = dataObj.slicedData;
let lastClickSearchTimestampFromPost = dataObj.lastClickSearchTimestamp;
if (!newData || newData.length == 0) {
isLoadingMore = false;
return;
}
if (lastClickSearchTimestampFromPost < lastClickSearchTimestamp) {
console.log("快速切换搜索内容的情况下,不是最近的搜索数据,防止数据混乱,直接return ");
isLoadingMore = false;
return;
}
renderRows(newData);
currentStartRow = currentEndRow;
currentEndRow += pageSize; // Load 10 more rows each time
// console.log("worker.onmessage currentStartRow =" + currentStartRow + "data.length=" + data.length);
let pgs = "当前进度:";
if (!!isShowAllOnce) {
if (currentStartRow < data.length) {
console.log("isShowAllOnce =", isShowAllOnce, "自动分页加载全部方式,当前pageSize:", pageSize, "currentStartRow:",
currentStartRow,
"总数:",
data.length);
isLoadingMore = false;
loadMoreRows();
pgs += "自动分页加载全部方式[支持改成 滚动加载方式],当前pageSize:";
} else {
console.log("isShowAllOnce =" + isShowAllOnce, currentStartRow, data.length, "自动分页加载全部方式,已加载全部");
pgs += "自动分页加载全部方式[支持改成 滚动加载方式],已加载全部,当前pageSize:";
}
} else {
console.log("isShowAllOnce =" + isShowAllOnce, currentStartRow, data.length, "滚动加载方式");
pgs += "滚动加载方式[支持改成 自动分页加载全部方式],当前pageSize:";
}
pgs += pageSize;
pgs += ",";
pgs += "currentStartRow:";
pgs += currentStartRow;
pgs += ",";
pgs += "总数:";
pgs += data.length;
isLoadingMore = false;
progress.innerText = pgs;
};
// Render the rows to the DOM
function renderRows(dataSlice) {
if (!dataSlice || dataSlice.length == 0) {
return;
}
dataSlice.forEach(item => {
const row = document.createElement('div');
row.className = 'row';
row.style.transform = `translateY(${(currentStartRow++) * rowHeight}px)`;
row.textContent = `${item.name} - ${item.orderNum}`;
listElement.appendChild(row);
});
}
search.addEventListener("click", function(e) {
const inputValue = input.value.trim();
filterCriteria.name = inputValue;
console.log("inputValue =" + inputValue)
currentStartRow = 0;
currentEndRow = 10;
listElement.innerHTML = '';
//这里应该记录下本次的时间戳,作为全局对象,
//worker.onmessage中只接收这个时间错之后的数据,防止不停的切换搜索关键词,会带出来非本次搜索的结果
lastClickSearchTimestamp = new Date().getTime();
loadMoreRows();
})
</script>
</body>
</html>
2.2 worker.js
// worker.js
self.addEventListener('message', function(event) {
//从event.data解析出data、filterCriteria、startRow、endRow、lastClickSearchTimestamp
const {
data,
filterCriteria,
startRow,
endRow,
lastClickSearchTimestamp
} = event.data;
// console.log("filterCriteria =" + JSON.stringify(filterCriteria))
// console.log("data =" + JSON.stringify(data))
// console.log("data size =" + data.length)
const filteredData = data.filter(item => {
return Object.keys(filterCriteria).every(key => {
if (filterCriteria[key]) {
return item[key].toString().toLowerCase().includes(filterCriteria[key]
.toString().toLowerCase());
}
return true;
});
});
// Slice the data for the requested range
const slicedData = filteredData.slice(startRow, endRow);
//console.log("slicedData=" + startRow + ",endRow=" + endRow + ",filteredData" + JSON.stringify(slicedData))
// Send the slice back to the main thread
//key value相同写一个
let slicedDataWithTime = {
slicedData,
lastClickSearchTimestamp
};
self.postMessage(slicedDataWithTime);
});
3. 在线演示
正文到此结束
- 本文标签: JavaScript 前端
- 本文链接: https://code.jiangjiesheng.cn/article/349
- 版权声明: 本文由小江同学原创发布,转载请先联系本站长,谢谢。