Vue 虚拟列表,纵享丝滑【实践篇】

简介: 现如今,我们总是在无止境的刷。刷微博、刷抖音、刷沸点......一次次丝滑下拉体验的背后却是前端攻城狮的用心。本篇讨论基于 Vue.js 的列表无限下拉实践。

image.png

序言



现如今,我们总是在无止境的刷。刷微博、刷抖音、刷沸点......一次次丝滑下拉体验的背后却是前端攻城狮的用心。


本篇讨论基于 Vue.js 的列表无限下拉实践。


我们的目标就是:让列表下拉纵享丝滑,而不是像以往的下拉就 loading 等待的体验。


设计



咱还是用 Vue CLI 来快速构建项目。

这是主页面:


// EndlessList.vue


<template>
  <div class="endless-scrolling-list">
    <!-- 搜索框 -->
    <div class="search-box">
      <input type="text" v-model="searchQuery"/>
    </div>
    <p class="center" v-if="results.length == 0 && !loading">
      Start typing to search something.
    </p>
    <!-- 虚拟列表 -->
    <virtual-list
      :data-key="'pageid'"
      :data-sources="results"
      :data-component="itemComponent"
      :page-mode="true"
      />
    <!-- loading -->
    <loader v-if="loading" />
  </div>
</template>


其中核心当然是virtual-list组件啦~

这里的虚拟列表,我们用到一个三方库 Vue Virtual Scroll List,它在 Github 上又 2.5k+ 的 stars。类比于 react 的 react-virtualized 库。


image.png

大量的 DOM 元素会使得我们的网页非常“重”。当 DOM 元素超过 1500 至 2000 个的时候,页面就开始又延迟,尤其是在小型的、性能差的设备上尤为明显。

想象一下,有一个无线滚动的页面,你不断的下拉,它实际上可能形成了上万个 DOM 元素,每个元素还包含子节点,这样将消耗巨大的性能。


Virtual scrollers 正是来解决这个问题的。


如上图,已经表示的很清楚了。列表分为可见区域和缓冲区域,超出这个范围的列表 DOM 都将被删除。


好啦,准备工作已就绪,Let`s get it!


实现



// imports.js(EndlessList.vue)


import axios from 'axios';
import lodash from 'lodash';
import VirtualList from 'vue-virtual-scroll-list';
import SearchResult from './SearchResult';
import Loader from './Loader';
export default {
  name: 'EndlessList',
  components: {
    VirtualList,
    Loader
  },
  data() {
    return {
      searchQuery: '',
      currentPage: 0,
      results: [],
      itemComponent: SearchResult,
      loading: false
    }
  },
};


我们引入第三方库 axios 和 loadsh,以便后续使用。

其中,itemComponent 是 virtual-list 的属性,为此我们需要新建一个 SearchResult 子组件,作为搜索结果单元。


代码如下:

// SearchResult.vue


<template>
  <div class="list-item">
    <h3>
      {{ source.title }}
    </h3>
    <div v-html="source.snippet"></div>
  </div>
</template>
<script>
export default {
  props: {
    index: {
      // index of current item
      type: Number,
    },
    source: {
      type: Object,
      default() {
        return {};
      },
    },
  },
};
</script>
<style scoped>
.list-item {
  padding: 0 10px 20px 10px;
}
.list-item h3 {
  margin: 0;
  padding-bottom: 10px;
}
</style>


我们可以通过搜索标题或描述来得到结果,请求数据来源于维基百科。


// search.js

search(query, page) {
  // We prepare the data that the Wikipedia API expects.
  const data = {
    action: "query",
    format: "json",
    list: "search",
    continue: "-||",
    utf8: 1,
    srsearch: query,
    sroffset: page * 10,
    origin: "*",
  };
  // And then we convert these params TO GET params in the format
  // action=query&format=json ...
  const params = Object.keys(data)
    .map(function(k) {
      return data[k] == ""
        ? ""
        : encodeURIComponent(k) + "=" + encodeURIComponent(data[k]);
    })
    .join("&");
  // We prepare the url with the params string
  const searchUrl = `https://en.wikipedia.org/w/api.php?${params}`;
  // we set loading to true so that we can display the loader 
  this.loading = true;
  // Then we execute the request and concatenate the results
  axios.get(searchUrl).then((response) => {
    this.results = this.results.concat(response.data.query.search);
    // And of course set loading to false to hide the loader.
    this.loading = false;
  });
}


搜索的方法已经写好,接着就是调用

  1. 当用户键入内容的搜索时候会调用。
  2. 当下拉的时候会调用。


// EndlessList.vue

<script>
export default {
  // data() and methods skipped for brevity
  watch: {
    searchQuery: {
      immediate: true,
      handler: lodash.debounce(function (newVal) {
        if (newVal == "") {
          return;
        }
        this.results = [];
        this.currentPage = 0;
        this.search(newVal, this.currentPage);
        this.search(newVal, this.currentPage + 1);
        this.currentPage = 2;
      }, 200),
    },
  },
  mounted() {
    const vm = this;
    window.onscroll = lodash.debounce(function () {
      var distanceFromBottom =
        document.body.scrollHeight - window.innerHeight - window.scrollY;
      if (distanceFromBottom < 400 && vm.searchQuery !== "") {
        vm.search(vm.searchQuery, vm.currentPage);
        vm.currentPage++;
      }
    }, 100, {leading: true});
  },
}
</script>


显而易见,当 searchQuery 变化的时候,我们会得到新的搜索结果。当然,这里的输入框也用到了防抖函数。


另一个需要注意的是,我们第一次搜索加载了两页的结果,用户就会有一定的滚动空间,这样就可以保持顺畅的感觉。


我们在滚动的事件中也加了防抖函数。这里设一个疑问:为什么要在 window.onscroll 事件下设置 leadingtrue


然后我们运行程序看效果:

image.png


npm run dev


image.png

如何?只要你不是疯狂下拉,基本上感受不到 loading 的过程~


小结



用户不会希望每下拉十条结果就要等待新的十条结果加载出来!所以我们需要有缓冲区,还未下拉到底的时候就预判它到底然后提前加载。这便是丝滑体验的内核。

当然不在视图区和缓冲区的 DOM 都将被删除,这也是页面不形成大量 DOM 元素的精髓。


这样动态的处理列表的确是编程人员的一种智慧和用心。

你可以把 项目 克隆到本地再体会一下。以上便是本次分享~

撰文不易,点赞鼓励!(●'◡'●) 我是掘金安东尼,关注公众号【掘金安东尼】,有更多精彩分享~


相关文章
|
4天前
|
JavaScript
vue消息订阅与发布
vue消息订阅与发布
|
3天前
|
JavaScript
vue尚品汇商城项目-day07【vue插件-50.(了解)表单校验插件】
vue尚品汇商城项目-day07【vue插件-50.(了解)表单校验插件】
12 4
|
3天前
|
JavaScript
vue尚品汇商城项目-day07【51.路由懒加载】
vue尚品汇商城项目-day07【51.路由懒加载】
13 4
|
4天前
|
JavaScript 前端开发
Vue学习笔记8:解决Vue学习笔记7中用v-for指令渲染列表遇到两个问题
Vue学习笔记8:解决Vue学习笔记7中用v-for指令渲染列表遇到两个问题
|
3天前
|
JavaScript
vue尚品汇商城项目-day07【vue插件-54.(了解)生成二维码插件】
vue尚品汇商城项目-day07【vue插件-54.(了解)生成二维码插件】
9 2
|
4天前
|
JavaScript 前端开发 API
Vue学习笔记7:使用v-for指令渲染列表
Vue学习笔记7:使用v-for指令渲染列表
|
5天前
|
JavaScript
vue组件中的插槽
本文介绍了Vue中组件的插槽使用,包括单个插槽和多个具名插槽的定义及在父组件中的使用方法,展示了如何通过插槽将父组件的内容插入到子组件的指定位置。
|
21小时前
|
JavaScript
理解 Vue 的 setup 应用程序钩子
【10月更文挑战第3天】`setup` 函数是 Vue 3 中的新组件选项,在组件创建前调用,作为初始化逻辑的入口。它接收 `props` 和 `context` 两个参数,内部定义的变量和函数需通过 `return` 暴露给模板。`props` 包含父组件传入的属性,`context` 包含组件上下文信息。`setup` 可替代 `beforeCreate` 和 `created` 钩子,并提供类似 `data`、`computed` 和 `methods` 的功能,支持逻辑复用和 TypeScript 类型定义。
19 11
|
4天前
|
JavaScript 前端开发 IDE
Vue学习笔记5:用Vue的事件监听 实现数据更新的实时视图显示
Vue学习笔记5:用Vue的事件监听 实现数据更新的实时视图显示
|
4天前
|
JavaScript 前端开发 API
Vue学习笔记4:用reactive() 实现数据更新的实时视图显示
Vue学习笔记4:用reactive() 实现数据更新的实时视图显示
下一篇
无影云桌面