Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【bigo】长列表在Likee年度盛典的应用 #86

Open
DarkYeahs opened this issue Dec 1, 2021 · 0 comments
Open

【bigo】长列表在Likee年度盛典的应用 #86

DarkYeahs opened this issue Dec 1, 2021 · 0 comments

Comments

@DarkYeahs
Copy link

【bigo】长列表在Likee年度盛典的应用

列表是页面常用的一种数据表现形式,在页面中常用写法如下

<template>
  <div class="list">
    <div
      v-for="(item, index) in list"
      :key="index"
      class="item">
      <!-- 业务代码 -->
    </div>
  </div>
</template>

但在likee2021年度盛典的这次活动中,产品给出了一个难题:在一个页面中显示一个数据量达上万的长列表,要求支持滑动至任一位置。

一个上万数据的简单列表在页面显示需要至少上万个元素,如果列表中存在交互事件,那浏览器需要花费多少时间去渲染呢?

用一个简单列表来显示,在chrome浏览器中需要800ms左右,使用的内存为95.7m,而活动中列表显示的内容会比演示的代码更多,交互更加复杂,webview渲染的时间及占用的内存也会更多,那如何去优化呢?

加载时间及内存

列表可视化渲染

用户查看页面的时候,只能看到屏幕窗口显示的界面,那再窗口外的内容我们可以选择不进行全部渲染。如下图所示,我们将长列表截取一部分数据再页面上进行渲染,用户看到的数据是在内容显示区域,当用户上滑或者下滑的时候,使用处于上滑待显示区域或者下滑待显示区域的数据替代内容显示区域的数据,再从长列表中更新上滑待显示区域或者下滑待显示区域的数据,从而实现列表的可视化渲染。

实现方案

时序图

当用户打开页面查看长列表的时候,页面会向后端请求接口,获取长列表的初始数据以及列表长度,根据列表长度以及初始化子元素的高度来初始化容器的滑动高度。

设定容器的滑动高度后,将当前显示容器设定为三个区域,滑动待显示区域以及可视显示区域,计算当前的滑动高度以及可视显示区域的高度,计算当前渲染的列表长度。

const { total, list } = await this.fetchData();

if (this.cachedHeight.length === 0) {
  this.cachedHeight = new Array(total).fill(ESTIMATED_HEIGHT);
}

this.total = total;
this.containerHeight = total * ESTIMATED_HEIGHT;
// 给每个 item 打上序号标记
for (let i = 0; i < list.length; i += 1) {
  const item = list[i];
  this.listData[item.rank - 1 - this.offset] = {
    ...item,
    index: item.rank - 1 - this.offset
  };
}
if (!this.isInit) {
  this.$nextTick(() => {
    // 计算当前可视区域可显示数据量
    VISIBLE_COUNT = Math.ceil(this.scrollerRef.offsetHeight / (ESTIMATED_HEIGHT));
  });
}
this.isInit = true;

由于列表内子元素高度是不确定的,所以当列表内容在页面上渲染完成后,重新计算子元素的高度,抛出一个事件给显示容器进行更新。

显示容器获取到子元素的渲染完成事件后会重新计算当前容器的滑动高度,缓存子元素的高度,以便子元素下次滑动时的滑动操作。

// 如果子元素是固定高度,则不进行更新
if (this.fixedHeight) return;
const ro = new ResizeObserver(() => {
  // 高度发生变化时,将 'size-change' 事件 emit 到父组件
  this.$nextTick(() => {
    this.$emit('size-change', this.index);
  });
});
ro.observe(this.$refs.item);
this.$once('hook:beforeDestroy', ro.disconnect.bind(ro));

用户操作列表进行滑动时,获取滑动距离,计算当前显示的列表内容,更新显示列表。列表更新完成后判断当前显示列表是否存在未拉取的列表元素,如果存在,则拉取服务端数据进行更新,在服务端未返回前,使用本地初始化数据初始化待拉取的内容。

function updateVisibleData() {
  this.firstAttachedItem = Math.max(0, this.anchorItem.index - BUFFER_SIZE);
  this.lastAttachedItem = this.firstAttachedItem + VISIBLE_COUNT + BUFFER_SIZE * 2;
  const start = this.firstAttachedItem;
  const end = Math.min(this.lastAttachedItem, this.total);
  const list = [];
  let needRequest = false;
  for (let i = start; i < end; i += 1) {
    const item = this.listData[i];
    if (item) {
      list.push(item);
    } else {
      list.push({ index: i, rank: i + 1 + this.offset });
      needRequest = true;
    }
  }
  if (needRequest) {
    this.fetchData();
  }
  this.visibleData = list;
}

最终效果

最终效果

不管长列表有多少数据,最终在浏览器中渲染的条数都是由容器高度进行控制,并且整体活动页面的内存也始终在一个安全的范围进行波动。在交互上也完美的达到了产品的要求,完成需求上的交互。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant