-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f72f07f
commit a03dd9d
Showing
7 changed files
with
607 additions
and
250 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" | ||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> | ||
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | ||
<title>渲染一百万条数据</title> | ||
<style> | ||
* { | ||
padding: 0; | ||
margin: 0; | ||
border-style: none; | ||
} | ||
html, body { | ||
display: block; | ||
width: 100%; | ||
height: 100%; | ||
font-size: 16px; | ||
} | ||
.desc { | ||
height: 60px; | ||
line-height: 60px; | ||
text-align: center; | ||
} | ||
.list { | ||
display: block; | ||
height: calc(100% - 60px); | ||
overflow-x: hidden; | ||
overflow-y: auto; | ||
} | ||
.list .items { | ||
display: block; | ||
box-sizing: border-box; | ||
padding-top: 0; | ||
} | ||
.list .block { | ||
display: block; | ||
} | ||
.list .row { | ||
display: block; | ||
height: 30px; | ||
line-height: 30px; | ||
background-color: #ffffff; | ||
} | ||
.list .block.odd .row:nth-of-type(2n) { | ||
background-color: skyblue; | ||
} | ||
.list .block.even .row:nth-of-type(2n +1) { | ||
background-color: skyblue; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div class="desc">现在有一百万条数据,如果直接渲染一百万条数据会非常卡顿,下面是一种纯前端解决方案。</div> | ||
<div class="list"> | ||
<ul class="items"></ul> | ||
</div> | ||
<script> | ||
const elemContainer = document.querySelector('.list') | ||
const containerHeight = elemContainer.clientHeight | ||
const singleLineHeight = 30 | ||
const numOfItemsInSingleScreen = Math.ceil(containerHeight / singleLineHeight) | ||
|
||
// 生成一百万条数据 | ||
const max = 1000000 | ||
|
||
const elemList = elemContainer.querySelector('.items') | ||
elemList.style.height = `${Math.ceil(max * singleLineHeight)}px` | ||
elemList.style.paddingTop = '0px' | ||
elemList.style.paddingTop = '0px' | ||
|
||
function fillList (opts) { | ||
const { | ||
oldStartIdx, | ||
oldEndIdx, | ||
startIdx, | ||
endIdx, | ||
} = opts | ||
|
||
// 分页索引没变化的话不需要做任何处理 | ||
if (oldStartIdx === startIdx && oldEndIdx === endIdx) { | ||
return | ||
} | ||
|
||
// 直接下拉/上拉到新区域,之前已渲染的部分可以直接丢弃,所以可以通过 innerHTML 直接替换 | ||
if (startIdx > oldEndIdx || endIdx < oldStartIdx) { | ||
// 注意下设置一个合理的 margin-top | ||
let html = '' | ||
elemList.style.paddingTop = `${startIdx * singleLineHeight * numOfItemsInSingleScreen}px` | ||
for (let pageIdx = startIdx; pageIdx < endIdx; pageIdx++) { | ||
html += `<div class="block ${pageIdx % 2 ? 'even' : 'odd'} block-${pageIdx}">` | ||
const rowStartIdx = pageIdx * numOfItemsInSingleScreen | ||
const rowEndIdx = Math.min( | ||
(pageIdx + 1) * numOfItemsInSingleScreen, | ||
max + 1 | ||
) | ||
for (let rowIdx = rowStartIdx; rowIdx < rowEndIdx; rowIdx++) { | ||
html += `<div class="row">${ rowIdx }</div>` | ||
} | ||
html += '</div>' | ||
} | ||
elemList.innerHTML = html | ||
return | ||
} | ||
|
||
// 处理尾部新增页内容 | ||
if (endIdx > oldEndIdx) { | ||
const fragment = document.createDocumentFragment() | ||
for (let pageIdx = oldEndIdx; pageIdx < endIdx; pageIdx++) { | ||
const block = document.createElement('div') | ||
block.classList.add('block', pageIdx % 2 ? 'even' : 'odd', `block-${pageIdx}`) | ||
const rowStartIdx = pageIdx * numOfItemsInSingleScreen | ||
const rowEndIdx = Math.min( | ||
(pageIdx + 1) * numOfItemsInSingleScreen, | ||
max + 1 | ||
) | ||
for (let rowIdx = rowStartIdx; rowIdx < rowEndIdx; rowIdx++) { | ||
const div = document.createElement('div') | ||
div.classList.add('row') | ||
div.textContent = `${rowIdx}` | ||
block.appendChild(div) | ||
} | ||
fragment.appendChild(block) | ||
} | ||
elemList.appendChild(fragment) | ||
} | ||
|
||
// 处理尾部删减页内容 | ||
if (endIdx < oldEndIdx) { | ||
for (let pageIdx = endIdx; pageIdx < oldEndIdx; pageIdx++) { | ||
const block = document.querySelector(`.block-${pageIdx}`) | ||
block.parentNode.removeChild(block) | ||
} | ||
} | ||
|
||
// 处理头部新增页内容 | ||
if (startIdx < oldStartIdx) { | ||
const fragment = document.createDocumentFragment() | ||
elemList.style.paddingTop = `${startIdx * numOfItemsInSingleScreen * singleLineHeight}px` | ||
for (let pageIdx = startIdx; pageIdx < oldStartIdx; pageIdx++) { | ||
const block = document.createElement('div') | ||
block.classList.add('block', pageIdx % 2 ? 'even' : 'odd', `block-${pageIdx}`) | ||
const rowStartIdx = pageIdx * numOfItemsInSingleScreen | ||
const rowEndIdx = Math.min( | ||
(pageIdx + 1) * numOfItemsInSingleScreen, | ||
max + 1 | ||
) | ||
for (let rowIdx = rowStartIdx; rowIdx < rowEndIdx; rowIdx++) { | ||
const div = document.createElement('div') | ||
div.classList.add('row') | ||
div.textContent = `${rowIdx}` | ||
block.appendChild(div) | ||
} | ||
fragment.appendChild(block) | ||
} | ||
elemList.insertBefore(fragment, elemList.firstChild) | ||
} | ||
|
||
// 处理头部删减页内容 | ||
if (startIdx > oldStartIdx) { | ||
for (let pageIdx = oldStartIdx; pageIdx < startIdx; pageIdx++) { | ||
const block = document.querySelector(`.block-${pageIdx}`) | ||
block.parentNode.removeChild(block) | ||
} | ||
elemList.style.paddingTop = `${startIdx * numOfItemsInSingleScreen * singleLineHeight}px` | ||
} | ||
} | ||
|
||
// 渲染出来的页面索引 | ||
let startIdx = -1 | ||
let endIdx = -1 | ||
|
||
// 最大页数 | ||
const maxPageIdx = Math.ceil(max / numOfItemsInSingleScreen) | ||
console.log('maxPageIdx', maxPageIdx) | ||
|
||
const onScroll = (() => { | ||
let handlerId = 0 | ||
|
||
return () => { | ||
if (handlerId) { | ||
cancelAnimationFrame(handlerId) | ||
handlerId = 0 | ||
} | ||
handlerId = requestAnimationFrame(() => { | ||
const scrollTop = elemContainer.scrollTop | ||
|
||
// 根据滚动距离,推算出当前应该渲染的页索引 | ||
const idx = Math.floor(scrollTop / singleLineHeight / numOfItemsInSingleScreen) | ||
|
||
const oldStartIdx = startIdx | ||
const oldEndIdx = endIdx | ||
|
||
// 当前页往前 1 页 | ||
startIdx = Math.max(0, idx - 1) | ||
// 当前页往后 1 页(endIdx 是不渲染的界限,所以加了 2,只会渲染到 idx + 1,即 < idx + 2) | ||
endIdx = idx + 2 | ||
|
||
/** | ||
* 滚动到最大页就不让继续滚了 | ||
* 这里原本正常写法是判断滚动距离加已渲染内容的总长如果达到容器高度就不继续加载后续页面 | ||
* 但是判断已渲染内容高度需要频繁读取 DOM 属性,不如直接判断分页数是否达到最大页性能好 | ||
*/ | ||
if (endIdx > maxPageIdx) { | ||
endIdx = maxPageIdx | ||
startIdx = Math.max(0, endIdx - 3) | ||
} | ||
|
||
fillList({ | ||
oldStartIdx, | ||
oldEndIdx, | ||
startIdx, | ||
endIdx, | ||
}) | ||
handlerId = 0 | ||
}) | ||
} | ||
})() | ||
|
||
/** | ||
* 优化方案 | ||
*/ | ||
function goodScheme () { | ||
onScroll() | ||
} | ||
|
||
elemContainer.addEventListener('scroll', onScroll, { | ||
capture: false, | ||
passive: true, | ||
}) | ||
|
||
goodScheme() | ||
</script> | ||
</body> | ||
</html> |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.