First, the overall idea
1. Source of ideas
Recently, I haven’t written an article for a long time because OF my busy work. I don’t know how to write it, so let’s talk about why I want to develop the component of this article first. Firm has a positioning system, basically a positioning unit for a minute or less will have an anchor point, this will have a lot of anchor point of the day, if the table wanted to take a day or even at three days of data, then the data will be particularly large amount of data (may reach 50000 or so), if we show columns and more, Then the form will be obvious. Our company’s web side selection UI framework is iview, the truth of the iview other components are ok, but forms seem to be very weak in front of a large amount of data, but I used to use easyui old framework such as the form on the performance and functionality are good, after all, they have gone through a lot of optimization, the form of the component development sex is very big, It’s very difficult to get both performance and functionality right.
Easyui is a framework that keeps up with The Times. One time I checked its official website and found that it already has UI components based on popular vue, React and Angular components. So I chose to look at its vUE based table this time, and I saw the component attached to the link www.jeasyui.net/demo_vue/68… . I found that it solved the problem of the large amount of data stuck through the method of paging lazy loading, which I could basically understand, but after watching it I had some questions. First of all, if it only rendered part of the data and loaded the data while the scrollbar was scrolling, then why was the scrollbar always so long? Resourcefully, I opened developer mode and looked at the HTML code in the table section
At first GLANCE, I understand that the bottom and top parts of the table in the figure are the reason why the height of the scroll bar remains unchanged, while the middle part always loads only 40 pieces of data according to the scrolling of the scroll bar, so the problem of the table with large data volume is solved
2. Confirm the idea
So we basically have the idea. Let’s get this straight.
- First we can think of the table as having three sections [table top (top), table scroll area (screen), table bottom (bottom)].
- The problem of the top and bottom part is the calculation of the height. Basically, it can determine the position of the scroll bar when it should scroll again, and then calculate the height of the top and bottom according to the total amount of data and the amount of data in the Screen part. A couple of words that come to mind when I think about this (calculated properties) are probably the most appropriate words to use here.
- screenPart of the implementation’s initial idea was to calculate the data that should be loaded based on scroll height. I was a little confused about how to make it more fluid with excessive data, so I continued to look at its HTML. For clarification, front End Da Vinci opens his drawing app and begins a project: ‘◡’●
If the peepeggers rolled the scroll bar higher than it should have been, we deleted all 40 of the data in a new way, counting 20 of the data above and 20 of the data above the current position based on the scroll position. There may be a whitening of the table in this process, but I think it can be handled with a mask layer.
So with the basic idea behind it, one item for a smooth one (●’◡’●).
Second, the implementation process
1. Table structure
The table is composed of two table tags, the first is the head of the table and the second is the data content, which is convenient for later expansion. Instead of separating the header and internal contents and tr into a single component to make the code more readable, we can optimize it later.
2. Logical implementation
Let’s get right to the main logic, so let’s look at the props and data sections first
props: {
loadNum: {
// The number of lines to load by default
type: [Number.String].default() {
return 20; }},tdHeight: {
// Table row height
type: [Number.String].default() {
return 40; }},tableHeight: {
// Table height
type: [Number.String].default() {
return "200"; }},tableList: {
// All table data
type: Array.default() {
return[]; }},columns: {
// All table matching rules
type: Array.default() {
return[]; }},showHeader: {
type: Boolean.default: true
}
},
data() {
return {
isScroll: 17.showLoad: false.columnsBottom: [].// Actual render table rules
showTableList: [], // The actual table data to display
loadedNum: 0.// The amount of data actually rendered
dataTotal: 0.// Total number of data items
dataTop: 0.// Render the height of the top of the data
scrollTop: 0.// Scroll up and down the distance
interval: null.// The timer that determines whether scrolling has stopped
scrollHeight: 0.// The height of the data scroll
selectTr: - 1 // Select a row
};
},Copy the code
And then let’s look at what the scroll event should do first in the code, okay
BottomScroll = document.getelementById ("bottomDiv"); let topScroll = document.getElementById("topDiv"); If (bottomScroll. ScrollTop > this.scrollTop) {// Record the last scroll down position this.scrollTop = bottomScroll. // Scroll down this.handlescrollbottom (); } else if (bottomScroll. ScrollTop < this.scrollTop) {this.scrollTop = bottomScroll. // Scroll up this.handlescrollTop (); } else {// scroll this. HandleScrollLeft (topScroll, bottomScroll); }}Copy the code
First, we use the variable scrollTop to record the position of the vertical scroll bar every time we enter a scroll event. If this value does not change, the scroll is left and right, if the value increases, it is down, and if it decreases, it is up. Scroll left and right when we need to do is to make the table head with the content of the move together, so that you can achieve left and right move the table head move up and down fixed effect.
// Scroll left and right
handleScrollLeft(topScroll, bottomScroll) {
// The top header follows the bottom scroll
topScroll.scrollTo(bottomScroll.scrollLeft, topScroll.pageYOffset);
},Copy the code
If it’s moving up we’re going to do something that we’re improving in our thinking by looking at the code
// Scroll up
handleScrollTop() {
// If the amount of data loaded is smaller than the default amount of data loaded
if (this.dataTotal > this.loadNum) {
let computeHeight = this.dataTop; // The height at which the data needs to be processed
if (
this.scrollTop < computeHeight &&
this.scrollTop >= computeHeight - this.loadNum * this.tdHeight
) {
this.showLoad = true;
// If the scrolling height reaches the top of the data display
if (this.dataTotal > this.loadedNum) {
// If the total amount of data is greater than the already rendered data
if (this.dataTotal - this.loadedNum >= this.loadNum) {
// If the total number of data minus rendered data is greater than or equal to loadNum
this.dataProcessing(
this.loadNum,
this.loadedNum - this.loadNum,
"top"
);
} else {
this.dataProcessing(
this.dataTotal - this.loadedNum,
this.dataTotal - this.loadedNum,
"top"); }}}else if (
this.scrollTop <
computeHeight - this.loadNum * this.tdHeight
) {
this.showLoad = true;
let scrollNum = parseInt(this.scrollTop / this.tdHeight); // Where is the scroll position
if (scrollNum - this.loadNum >= 0) {
this.dataProcessing(this.loadNum * 2, scrollNum, "topAll");
} else {
this.dataProcessing(scrollNum + this.loadNum, scrollNum, "topAll"); }}}},Copy the code
- First, we determine whether the amount of data loaded is less than the default amount of data loaded. If so, there is no need to do any logic, because all the data has been loaded.
- Determine whether the scrolling height has exceeded the top position of the current part of Screen data and is smaller than the top position of the current part of Screen data minus the height of the default load data, that is, the first case we mentioned before, The second case is the height greater than the top of the current screen minus the default load.
- If this. ShowLoad is set to true, the mask layer will be turned on to prevent the table from turning white and affecting the user’s experience.
- In the first case, if the top of the data is less than the default loaded data, we only load the remaining height of the data. If it is larger, we load the default loaded this.loadNum amount of data
- LoadNum *2 = this.loadNum*2 = this.loadNum*2 = this.loadNum*2 = this.loadNum*2 = this.loadNum*2
// Scroll down
handleScrollBottom() {
let computeHeight =
this.dataTop +
this.loadedNum * this.tdHeight -
(this.tableHeight - this.tdHeight - 3); // The height at which the data needs to be processed
if (
this.scrollTop > computeHeight &&
this.scrollTop <= computeHeight + this.tdHeight * this.loadNum
) {
this.showLoad = true;
// If the scrolling height reaches the bottom of the data display
if (this.dataTotal > this.loadedNum) {
// If the total amount of data is greater than the already rendered data
if (this.dataTotal - this.loadedNum >= this.loadNum) {
// If the total number of data minus rendered data is greater than or equal to 20
this.dataProcessing(
this.loadedNum - this.loadNum,
this.loadNum,
"bottom"
);
} else {
this.dataProcessing(
this.dataTotal - this.loadedNum,
this.dataTotal - this.loadedNum,
"bottom"); }}}else if (
this.scrollTop >
computeHeight + this.tdHeight * this.loadNum
) {
this.showLoad = true;
let scrollNum = parseInt(this.scrollTop / this.tdHeight); // Where is the scroll position
if (scrollNum + this.loadNum <= this.dataTotal) {
this.dataProcessing(scrollNum, this.loadNum * 2."bottomAll");
} else {
this.dataProcessing(
scrollNum,
this.dataTotal - scrollNum + this.loadNum,
"bottomAll"); }}},Copy the code
It has been calculated that there are 4 cases, and the corresponding amount of data to be deleted and added is calculated. Let’s look at what the dataProcessing function does.
// Data processing when scrolling up and down
dataProcessing(topNum, bottomNum, type) {
let topPosition = parseInt(this.dataTop / this.tdHeight);
if (type === "top") {
this.showTableList.splice(this.loadedNum - bottomNum, bottomNum); // Subtract the bottom data
for (var i = 1; i <= topNum; i++) {
// Add the top data
let indexNum = topPosition - i;
this.tableList[indexNum].index = indexNum + 1;
this.showTableList.unshift(this.tableList[indexNum]);
}
this.loadedNum = this.loadedNum + topNum - bottomNum; // Recalculate the actual number of renders
this.dataTop = this.dataTop - topNum * this.tdHeight; // Recalculate the height of the rendered data
document.getElementById("bottomDiv").scrollTop =
document.getElementById("bottomDiv").scrollTop +
bottomNum * this.tdHeight;
this.scrollTop = document.getElementById("bottomDiv").scrollTop;
} else if (type == "bottom") {
this.showTableList.splice(0, topNum); // Subtract the top data
for (var i = 0; i < bottomNum; i++) {
// Add the bottom data
let indexNum = topPosition + this.loadedNum + i;
this.tableList[indexNum].index = indexNum + 1;
this.showTableList.push(this.tableList[indexNum]);
}
this.loadedNum = this.loadedNum - topNum + bottomNum; // Recalculate the actual number of renders
this.dataTop = this.dataTop + topNum * this.tdHeight; // Recalculate the height of the rendered data
document.getElementById("bottomDiv").scrollTop =
document.getElementById("bottomDiv").scrollTop -
topNum * this.tdHeight;
this.scrollTop = document.getElementById("bottomDiv").scrollTop;
} else if (type == "bottomAll") {
this.showTableList = []; // Subtract the top data
let scrollNum = topNum;
for (var i = 0; i < bottomNum; i++) {
// Add the bottom data
let indexNum = scrollNum - this.loadNum + i;
this.tableList[indexNum].index = indexNum + 1;
this.showTableList.push(this.tableList[indexNum]);
}
this.loadedNum = bottomNum; // Recalculate the actual number of renders
this.dataTop = (scrollNum - this.loadNum) * this.tdHeight; // Recalculate the height of the rendered data
this.scrollTop = document.getElementById("bottomDiv").scrollTop;
} else if (type == "topAll") {
this.showTableList = []; // Subtract the top data
let scrollNum = bottomNum;
for (var i = 0; i < topNum; i++) {
// Add the bottom data
let indexNum = scrollNum - topNum + this.loadNum + i;
this.tableList[indexNum].index = indexNum + 1;
this.showTableList.push(this.tableList[indexNum]);
}
this.loadedNum = topNum; // Recalculate the actual number of renders
this.dataTop = (scrollNum - topNum + this.loadNum) * this.tdHeight; // Recalculate the height of the rendered data
this.scrollTop = document.getElementById("bottomDiv").scrollTop;
}
this.showLoad = false;
},Copy the code
- So first we delete the data that we calculated to delete we delete it using splice, and then we go through a simple for loop, and if we scroll up we add the data to the top using unshift, if we scroll down we add the data to the bottom using push.
- Once we have processed the data we also need to recalculate the actual number of renders and change the value of loadedNum to the number of renders now displayed
-
Recalculate the height of the rendered data and calculate the height of the top of the data that dataTop now displays
- Because changing top and bottom causes the table scrollTop to change, we need to dynamically move the scroll bar to the correct position
And then finally, let’s talk about the top and bottom that we were thinking about before, and we knew from the beginning that we should do it with computed properties, and it turns out that’s true, so let’s look at the code
computed: {
tableOtherTop() {
// Top height of remaining data in table
return this.dataTop;
},
tableOtherBottom() {
// The bottom height of the rest of the table
return (
this.dataTotal * this.tdHeight -
this.dataTop -
this.loadedNum * this.tdHeight ); }},Copy the code
This ensures that changes in the heights of the top and bottom trigger changes to the table.
The height of top should be dataTop.
The height of bottom should be the total height of the data – the height of the displayed data (this.loadednum * this.tdheight) – the height of the top.
Finally, let’s look at the renderings
The 2020-3-26 update
- Through function anti – shake to solve the dragging process flicker, scroll bar jumping situation. At the same time, a friendly judgment is made each time the scroll is triggered to determine whether the current scroll is beyond the data range and beyond the display loading animation.
/** * @typedef {Object} Options - Configuration item * @property {Boolean} leading - Start whether additional trigger * @property {this} context - context **/ // use Proxy (func, time, options = {leading:true, context: null } ) { let timer; let _this = this; letHandler = {apply(target, _, args) {// Proxy function calllet bottomScroll = document.getElementById("bottomDiv"); let topScroll = document.getElementById("topDiv"); if(bottomScroll. ScrollTop == _this.scrollTop) {bottomScroll.return; } // Same core logic as the closure implementationif(! options.leading) {if (timer) return; timer = setTimeout(() => { timer = null; Reflect.apply(func, options.context, args); }, time); } else { if (timer) { _this.needLoad(bottomScroll); clearTimeout(timer); } timer = setTimeout(() => { Reflect.apply(func, options.context, args); }, time); }}};returnnew Proxy(func, handler); }, // Whether to display needLoad(bottomScroll) {if ( Math.abs(bottomScroll.scrollTop - this.scrollTop) > this.tdHeight * this.loadNum ) { this.showLoad = true; This.scrolltop = bottomScroll. ScrollTop; }}Copy the code
- Fixed bug where part of the table is not rendered due to height changes
- The form component has been posted to NPM and you are welcome to use it and ask questions
conclusion
The hardest part of developing this component is figuring out what’s going on, and then writing down the calculations to make sure they don’t go wrong. In the process of development, I also have an idea that I want to develop a simple table component, but I feel that my personal level is limited, but I also foxed in many places, and then have time to expand this component, come on ~~~// (^v^)\\\~~~.
Here is my github address github.com/github30789… I have uploaded the project. One item: A start one item if you like it. Thanks (●’ plus-one ‘●).