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

(First of all, I would like to say that the table I implemented is not considered so comprehensive, the original intention of development is only to solve the problem of broken, table sorting multiple selection and other functions, and then expand)

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

  1. 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.
  2. 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.
  3. 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.
  4. 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
  5. 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

  1. 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.
  2. 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
  3. Recalculate the height of the rendered data and calculate the height of the top of the data that dataTop now displays

  4. 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 ‘●).