preface
When a page needs to display a long list, there are two ways to do this:
The first is lazy loading, which is called infinite scrolling, adding new data and rendering the DOM each time you scroll to the bottom. This way is generally more commonly used, but also can solve most problems.
The second is visual region loading, known as virtual list loading, which only loads the currently visible portion of the DOM at a time, updating the DOM data as you scroll down the page in a way that feels normal. Is to solve the first way when loading a lot of data when too many DOM nodes may cause some problems.
This time try to implement a virtual list to see its basic logic.
Train of thought
Think about the following things.
- The original container. – I don’t need to explain this, it’s the original box.
- The container for sliding – because it only loads a certain amount of DOM itself, it doesn’t lift the height, so you need to set a total height to simulate the effect of sliding and scrollbars, no content, just height.
- The container used to display the content – that is, to load the data that is really used to display, it needs to be noted that it is not only loaded visible, but must be reserved, otherwise there will be a discontinuous phenomenon when scrolling, which can be expressed as the following figure. It’s a little abstract but if it’s smart you’ll understand it.
| | - > the reserved area | | | -- -- -- -- - | | | | | -- - > display area | | | -- -- -- -- - | | | | |Copy the code
The main need is actually the above three pieces of content, and then it is time to think about the specific implementation logic.
implementation
The realization is divided into the following steps, we here with code step by step implementation.
Initialize the
There are many states required to implement a virtual list, so we create a class to store these states.
class VritualList {
constructor(container, options = {}) {
// The original container
this.container = container
// List data NODE NODE array
this.data = options.data
// Maximum number of loads
this.maxCount = options.maxCount
// The height of the most data
this.itemHeight = options.itemHeight
this.init()
}
// ...
Copy the code
We passed some state that needed to be stored initially through parameters. We did this for a basic demonstration, so the data was directly generated by the Node, and each piece of content was guaranteed to be of equal height.
And then initialize everything else that needs to be evaluated, which is more in init.
// ...
init() {
// Container height
this.containerHeight = this.container.clientHeight
According to the total / /
this.total = this.data.length
// The first index loaded
this.start = 0
// The last index loaded
this.end = 0
// The current scroll bar position
this.scrollTop = 0
// Last scroll bar position
this.oscrollTop = 0
// Reserved quantity
this.reserveCount = this.getReserveCount()
// use div to wrap
this.wrapperNode = this.createWrapper()
// Use div to scroll
this.scrollBarNode = this.createScrollBar()
// Display the list using div
this.scrollListNode = this.createScrollList()
this.wrapperNode.onscroll = this.handleScroll.bind(this)
this.wrapperNode.append(this.scrollBarNode, this.scrollListNode)
this.container.append(this.wrapperNode)
this.end = this.start + this.maxCount
this.scrollListNode.append(... this.data.slice(this.start, this.end))
}
// ...
Copy the code
In general, in addition to the necessary height, the rest are some variables that need to be calculated. For example, we set the reservation number here, which records the maximum distance between the currently seen element and the loaded element, and the algorithm is very simple.
// ...
// The number of reservations is equal to the maximum number of current-start
getReserveCount() {
const oneScreenShow = Math.floor(this.containerHeight / this.itemHeight)
// (Total number of displays - maximum number of displays on a screen)
return Math.floor((this.maxCount - oneScreenShow) / 2)}// ...
Copy the code
The current visible element number can be obtained by dividing the top distance by the height.
// ...
get current() {
return Math.floor(this.scrollTop / this.itemHeight)
}
// ...
Copy the code
We then created three divs to use, at the following level.
- container
- wrapper
- scroll-bar
- scroll-list
- wrapper
The Wrapper fills the entire container, setting relative positioning beyond partial scrolling.
// ...
createWrapper() {
const node = document.createElement('div')
node.style.width = '100%'
node.style.height = '100%'
node.style.position = 'relative'
node.style.overflow = 'auto'
return node
}
// ...
Copy the code
Scroll bar To set the overall height.
// ...
createScrollBar() {
const node = document.createElement('div')
node.style.height = this.total * this.itemHeight + 'px'
return node
}
// ...
Copy the code
The scroll list is absolutely positioned at the top left corner, filling the landscape area.
// ...
createScrollList() {
const node = document.createElement('div')
node.style.position = 'absolute'
node.style.left = 0
node.style.top = 0
node.style.width = '100%'
return node
}
// ...
Copy the code
So once you initialize this, you just populate it with the first batch of data.
// ...
this.end = this.start + this.maxCount
this.scrollListNode.append(... this.data.slice(this.start, this.end))
// ...
Copy the code
Scroll event handling
The next step is to handle the scrolling event, which is relatively complicated.
First, scroll events can be divided into scroll down and scroll up, so you can set a last scroll position to compare and distinguish the scrolling direction.
Then for rolling down and rolling up have their own ways to deal with, sort out the ideas can probably be described as follows.
- Roll down – If the currently visible element is more than the reserved number from the first loaded element, continually remove the extra element from the head of the list while adding the same number of elements to the tail.
- Roll up – If the first loaded element in the list is not the first, and the distance from the current visible element is less than the reserved number, the elements are continuously added to the head of the list. If the whole loaded element is more than the maximum number of loadable elements, the extra elements are removed from the tail.
The code can be expressed as follows
// ...
scrollNext() {
const listNode = this.scrollListNode
while (this.current - this.start > this.reserveCount) {
this.start++
listNode.firstChild.remove()
if (this.end < this.total) {
listNode.append(this.data[this.end++])
}
}
}
scrollPrev() {
const listNode = this.scrollListNode
while (this.start && this.current - this.start < this.reserveCount) {
listNode.prepend(this.data[--this.start])
if (this.end - this.start >= this.maxCount) {
this.end--
listNode.lastChild.remove()
}
}
}
// ...
Copy the code
Then after the operation on the elements in the list, the height of the list remains the same, but the scroll bar has been rolled further, so the positioning of the list should be handled, the specific processing rules are as follows:
Multiply the height of the element by the ordinal number of the start rendering position offset downward from the list
This looks like normal scrolling relative to the outermost container, as follows:
// ...
handleScroll() {
this.scrollTop = this.wrapperNode.scrollTop
if (this.scrollTop > this.oscrollTop) {
this.scrollNext()
} else {
this.scrollPrev()
}
this.oscrollTop = this.scrollTop
this.scrollListNode.style.transform = `translateY(The ${this.start * this.itemHeight}px)`
}
// ...
Copy the code
The introduction of
Ok, so now that we’re done implementing the VritualList class, now we’re going to introduce it externally, so let’s write a simple example to try it out.
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html.body {
height: 100%;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
#container {
width: 250px;
height: 500px;
background: WhiteSmoke;
}
.item {
margin-bottom: 15px;
height: 50px;
background: lightblue;
border: 1px solid gray;
}
</style>
</head>
<body>
<div id="container"></div>
</body>
<script src="virtual-list.js"></script>
<script>
function createItem(index) {
const node = document.createElement('div')
node.className = 'item'
node.textContent = 'I am' + index
return node
}
const TOTAL_COUNT = 1e4
const data = Array(TOTAL_COUNT)
for (let i = 0; i < TOTAL_COUNT; i++) {
data[i] = createItem(i)
}
new VritualList(
document.getElementById('container'),
{
data,
maxCount: 20.itemHeight: 65,})</script>
</html>
Copy the code
Let’s try to load 10,000 pieces of data, and then load up to 20 at a time to see what happens.
Congratulations on the successful implementation of virtual lists.
conclusion
This attempt to use native JS to achieve virtual lists, in the corresponding framework does not need to manipulate the DOM can just be data.
However, this kind of virtual list is needed only when the data volume is very, very large, and ordinary infinite scrolling is basically enough. In addition, for the variable height, you need to dynamically calculate the height in the setting section. You can find examples elsewhere, such as the lite-virtual-list library in reference.
In addition, if you want to throttle the scrolling event, you need to pay attention to the last scrolling operation must be triggered, otherwise the notification location may be incorrect.
Finally, I wish you good health.
reference
- Talk about long lists in front-end development
- lite-virtual-list
- The relevant code
- The original address