preface

The code is followed by the demo address

In daily development, beyond the use of off-the-shelf plug-ins, there are many do-it-yourself issues. When a drop-down list reaches thousands or even tens of thousands of entries, the browser will already be severely stuttered. Take a look at the following example

As you can see, it took about 5 seconds to load the drop-down list with 2W simple test data (nothing else on the page). The occurrence of this kind of situation in the heart is really very complicated, this is not playing me?

solution

This is the same performance problem as tabular data, which is solved by using pagers to reduce the amount of data carried on pages. So how do you solve drop-down lists? Usually we load all the data in the drop down at once, and for the current problem, the idea is the same, using paging to solve the page performance problem. Again, the pager is clickable, and the drop-down list is not clickable, so the only way to do that is to listen for scroll events.

First outline:

  1. Monitor the scroll
  2. Load data backwards as you scroll down
  3. Load data forward when scrolling up
  4. Data goes in and data goes out

Show begins

1. Listen for scrolling

    <el-select class="remoteSelect" v-scroll v-model="value">
      <el-option :value="item.id" v-for="item in list" :key="item.id">{{item.name}}</el-option>
    </el-select>
Copy the code

Here is the listening scroll based on the EL-Select implementation in Vue and Element-UI. Here is the use of custom instructions in the way of listening to scroll

// cache directory index.js file import Vue from'vue'
export default () => {
  Vue.directive('scroll', {
    bind(el, binding) {// Get the scrolling page DOMlet SCROLL_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap')
      SCROLL_DOM.addEventListener('scroll'.function () {
        console.log('scrll')})}})}Copy the code

Register it in main.js with the global method vue.use ()

import Directives from './directives'
Vue.use(Directives)
Copy the code

At this time, the scroll page can see the control of the printed log, on behalf of monitoring has taken effect, then roll up the sleeves to do

2. Load data backwards when scrolling down

The first thing you have to do is figure out whether you’re scrolling up or scrolling down

  1. Records the last scroll position
  2. The current position is compared to the last scroll position

The global position is recorded through a public variable, and the current scrollPosition is obtained through the scrollTop method and recorded in the public variable scrollPosition

    bind(el, binding) {// Get the scrolling page DOMlet SCROLL_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap')
      let scrollPosition = 0
      SCROLL_DOM.addEventListener('scroll'.function() {// Current scroll position minus last scroll position // iftrueIt means scroll up,falseScroll downletFlagToDirection = this.scrollTop - scrollPosition > 0 // Record the current scrolling position scrollPosition = this.scrollTop console.log(flagToDirection ?'Rolling direction: Down' : 'Rolling direction: Up')})}Copy the code

Now that we know the direction of scrolling, we will do the corresponding processing according to the direction of scrolling. Tell the component about the scrolling behavior

. Omitted // record current scrollPosition scrollPosition = this.scrollTop // tell component binding.value(flagToDirection)Copy the code

Event accept In the V-scroll instruction v-scroll=”handleScroll”, in this method handleScroll handles the scrolling behavior. All you need to do next is request data for scrolling down in this event

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Fn: handleScroll * * Intro: rolling processing behavior * * @ params: paramtrue** @params: param isfalseOn behalf of the scroll up * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / handleScroll (param) {if(param) {// Request data for the next page this.list.push(... this.ajaxData(++this.pageIndex)) } },Copy the code

At this point, scroll loading is implemented. But the load is too frequent, if the fast scrolling will send multiple requests for background data at the same time, in some dense browser Ajax will be developed and queued, which is not ideal. So how do you control it? Instead, trigger handleScroll when the scroll position is a certain height from the bottom of the page, such as 100px from the bottom of the page

  1. scrollHeightGet roll height
  2. At 100px from the bottom
ScrollPosition = this.scrollTop const LIMIT_BOTTOM = 100 // record the position of the scrollPosition from the bottomletScrollBottom = this.scrollHeight - (this.scrollTop + this.clientheight) < LIMIT_BOTTOM // Trigger if the specified position is reachedif(scrollBottom) {// Tell the component binding.value(flagToDirection)}Copy the code

Through the change of data length, it can be known that the triggering events have been significantly harmonized. This effect is similar to the lazy loading of mobile phones, and the data will be continuously superimposed.

Tip: There is a bug that ajax is asynchronous, and if the Ajax request takes 1s to return data while rolling down, multiple request events will be triggered. How can this be avoided? The answer is to add a flag bit that is set to false before the request and true after the request. Determine the flag bit before each request. If false, the event is blocked.

The midfield

Let’s look at our outline again

  1. Monitor the scroll
  2. Load data backwards as you scroll down
  3. Load data forward when scrolling up
  4. Data goes in and data goes out

So far we have only done steps ① and ②. If you’ve met your needs, you can finish reading. If it helps you a little bit, give it a “like” before you leave.

This is just the basics, not the highlights. What about no performance pressure?

  1. Code address: Github
  2. Current version Demo: Demo

Let’s go home from work for dinner. Finish writing over the weekend


Gorgeous division


Just like the weekend, everything will happen. – the 2018-11-10 08:30

3. Load data forward when scrolling up

We know the scrolling behavior by judging the parameter param in handleScroll, but previously we only limited the timing of scrolling down, now we improve the timing of scrolling up. Again, use the trigger 100px from the top.

The handleScroll event is triggered whenever the current scroll position scrollTop is less than 100px

// If you scroll down and are only 100px away from the bottomif(flagToDirection && scrollBottom) {// Tell the component binding.value(flagToDirection)} // If the scroll is up and only 100px away from the topif(! flagToDirection && this.scrollTop < LIMIT_BOTTOM) { binding.value(flagToDirection) }Copy the code

Scrolling up was already detected in the handleScroll event, and the timing was as expected.

4. Data in and data out

4. Data in and data out

What about no performance pressure? That’s the key point. Look at the picture at a glance (it is not easy to find this effect) :

  1. Scroll down (each click in the figure represents a trigger to scroll to load data)

list

Why don’t we see how to do that

How do I maintain the length of this array? It’s easy to say in and out, but it’s not easy to implement.

Suppose our current array container holds a maximum of four pages of data, 100 items per page. Use the pageLimit parameter to limit the length of the array we need to maintain, which is set to 4.

How do we know which page to load when scrolling down or up?

So we need a record table, pageMap, to record the page numbers, which correspond to the current data entity list. The correspondence is as follows.

PageLimit: 4 pageMap: [1, 2, 3, 4]'First page of data'.'Page 2 data'.'Data on Page 3'.'Data on Page 4']

Copy the code

Effect drawing (currently rolling is not scientific, the steps are correct, there is optimization of rolling behind) :

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Fn: handleScroll * * Intro: rolling processing behavior * * @ params: paramtrue** @params: param isfalseOn behalf of the scroll up * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / handleScroll (param) {if (param) {
        if (this.pageMap.length >= this.pageLimit) {
          // 当长度相等的时候, 绝对不能超出长度  则有进必有出
          // 删除 pageMap 列表的第一个元素
          this.pageMap.shift()
          // 对应删除list中一页的数据量
          this.list.splice(0, this.pageSize)
        }
        ++this.pageIndex
        this.pageMap.push(this.pageIndex)
        // 请求下一页的数据
        this.list.push(...this.ajaxData(this.pageIndex))
        // 同步记录页码
      } else{// Continue loading if you haven't reached the first page while scrolling up. Stop loading if it doesif(this.pagemap [0] > 1) {// Scroll up, This.pageindex = this.pagemap [0] - 1 = this.pagemap [0] - 1 = this.pagemap [0] - 1 = this.pagemap ( This.pagemap = [this.pagemap,... this.pagemap] // This.list = [...this.ajaxData(this.pageIndex),...this.list]}else return false}}Copy the code

I’ll stop here, it’s time for lunch again. 2018-11.10 12:01

Optimization of rolling

When the data reaches the specified length, the total amount of data will not change, so the overall scrolling height is also fixed, which means that although the data is in and out, the scrollTop will no longer affect the scrolling position. As shown in the dynamic diagram above, the effect will be rolled to the bottom. This is not the end of pagination, but the user thinks it is. This is a serious problem

Optimization method:

  1. Returns the current scroll position to the middle of the total scroll height after each load. At this point we need to scrolldomAnd the height of the middle position through custom instructionsv-scrollCast out and scroll to the middle position while adding data to the head or tail.

Throws the middle of the DOM and scroll

// The index.js file in the cache directory // if you scroll down and it is only 100px from the bottomif(flagToDirection && scrollBottom) {// Tell the component binding.value(flagToDirection, SCROLL_DOM, This.scrollheight / 2)} // If the scroll is up and only 100px from the topif(! flagToDirection && this.scrollTop < LIMIT_BOTTOM) { binding.value(flagToDirection, SCROLL_DOM, this.scrollHeight / 2) }Copy the code

When the pageMap (corresponding to the list length) reaches the pageLimit length, the DOM scroll position is reset when adding and deleting data in and out

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Fn: handleScroll * * Intro: rolling processing behavior * * @ params: paramtrueRepresents scrolling down asfalseScroll up ** @params: el scroll DOM ** @params: MiddlePosition scrolling list the middle position of * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / handleScroll (param, el, middlePosition) {if (param) {
        if(this.pageMap.length >= this.pageLimit) { .... Omit the code this.list.splice(0, this.pagesize) // roll back to the middle el.scrollTop = middlePosition}.... Omit code}else{// Continue loading if you haven't reached the first page while scrolling up. Stop loading if it doesif(this.pageMap[0] > 1) { .... This.list = [...this.ajaxData(this.pageIndex),...this.list] // Roll back to middle el.scrollTop = middlePosition}else return false}},Copy the code

Ah ah
Have to panic

The overall height
LIMIT_BOTTOM
const LIMIT_BOTTOM = 100

Conditions of comb

  1. Every time I go back to1/2 scrollHeightThe location of the
  2. The position of each data change is(1 / pageLimit) * scrollHeightWhat I’m showing here is1/4 * scrollHeight
  3. Set an unknown X as the critical point of the jump
  4. The tipping point is the position the user sees before jumping
  5. 1/2 scrollHeightIs the position of the user after the jump

X + (1/4 * scrollHeight) = (1/2 scrollHeight)

Const LIMIT_BOTTOM = this. ScrollHeight /4

Const LIMIT_BOTTOM = this.scrollheight / 4.2

conclusion

This is where the story ends. Give it a thumbs up

Create a repository on Github to upload code:

  1. Demo View: Demo
  2. Github code to view: Portal
  3. Vue custom directive implements input limits to positive integers
  4. Why encapsulate AJAX again?
  • Copyright: this article was first published in nuggets, if you need to reprint, please indicate the source.

The best time to plant a tree was ten years ago, followed by now. It doesn’t matter who says it.