searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

渲染大型列表优化

2024-08-22 10:06:16
3
0
 

前端渲染大型列表变得很慢因为浏览器处理大量DOM节点真实场景我们不需要立刻渲染全部列表只需要渲染一小部分用户鼠标滚动加载数据通过列表虚拟化提升性能

一、列表虚拟化

实际使用场景大型列表每个数据项可能复杂数据项,可能包含图片视频等等前端渲染需要分类处理一下

当数据项中包含图片时,做一个简单demo实现

 

基本思路

1.列表项数据结构:

每个列表项包含一个图片 URL。

 

2.懒加载图片:

只有当列表项进入视口时,才加载相应的图片。

 

3.结合虚拟化列表:

使用虚拟化列表技术,只渲染当前可视区域内的列表项,减少 DOM 操作和资源占用。

 

4.滚动监听:

监听滚动事件,动态计算哪些列表项进入了可视区域,以及何时加载它们的图片。

<template>
  <div class="virtual-list-container" ref="container" @scroll="handleScroll">
    <div :style="{ height: totalHeight + 'px' }">
      <div v-for="(item, index) in visibleItems" :key="item.id"
           :style="{ transform: 'translateY(' + itemPositions[index] + 'px)' }"
           class="virtual-list-item">
        <img v-if="item.isVisible" :src="item.imageUrl" @load="handleImageLoad(index)"
             class="lazy-load-image" />
        <div v-else class="placeholder" style="height: 200px; background-color: #f0f0f0;"></div>
        <p>{{ item.text }}</p>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [], // 数据源
      visibleItems: [], // 当前可见的列表项
      itemPositions: [], // 可见列表项的位置
      itemHeight: 200, // 每个列表项的高度
      totalHeight: 0, // 虚拟列表容器的总高度
      startIndex: 0, // 可视区域内的起始索引
      endIndex: 0, // 可视区域内的结束索引
      bufferSize: 5, // 缓冲区大小,可根据实际情况调整
    };
  },
  mounted() {
    this.initializeList();
  },
  methods: {
    initializeList() {
      // 模拟生成大量数据
      this.items = Array.from({ length: 1000 }, (_, index) => ({
        id: index,
        text: `Item ${index + 1}`,
        imageUrl: `via.placeholder.com/300?text=Image${index + 1}`, // 示例图片 URL
        isVisible: false, // 标记是否可见
      }));
      
      // 计算虚拟列表容器的总高度
      this.totalHeight = this.items.length * this.itemHeight;
      
      // 初始化可见列表项和位置
      this.updateVisibleItems();
    },
    updateVisibleItems() {
      const container = this.$refs.container;
      const scrollTop = container.scrollTop;
      const offset = Math.floor(scrollTop / this.itemHeight) - this.bufferSize;
      this.startIndex = Math.max(offset, 0);
      this.endIndex = Math.min(this.startIndex + this.visibleCount + 2 * this.bufferSize, this.items.length);
      this.visibleItems = this.items.slice(this.startIndex, this.endIndex);
      this.itemPositions = this.visibleItems.map((item, index) => (this.startIndex + index) * this.itemHeight);
      
      // 标记可见项为可见,用于懒加载图片
      this.visibleItems.forEach(item => {
        if (!item.isVisible) {
          item.isVisible = true;
        }
      });
    },
    handleScroll() {
      this.updateVisibleItems();
    },
    handleImageLoad(index) {
      // 图片加载完成时的处理逻辑,可根据需求自定义
      console.log(`Image ${index + 1} loaded.`);
    }
  },
  computed: {
    visibleCount() {
      return Math.ceil(this.$refs.container.clientHeight / this.itemHeight);
    }
  }
};
</script>

<style>
.virtual-list-container {
  height: 400px;
  overflow-y: scroll;
  border: 1px solid #ccc;
}

.virtual-list-item {
  height: 200px; /* 每个列表项的固定高度 */
  border-bottom: 1px solid #eee;
  padding: 10px;
  box-sizing: border-box;
}

.lazy-load-image {
  max-width: 100%;
  max-height: 100%;
  object-fit: cover;
}

.placeholder {
  width: 100%;
}
</style>
 

解释和注意事项

 

1.数据源和初始化:

items 数组中的每个对象包含了一个图片的 URL (imageUrl) 和一个标志 (isVisible),用来表示图片是否已经在可见区域内加载。

 

2.图片加载机制:

在 v-for 中,根据 isVisible 的值决定是渲染 <img> 元素还是一个占位符 <div>。只有当列表项进入可视区域后,才加载相应的图片。

 

3.滚动事件处理:

handleScroll 方法监听滚动事件,根据滚动位置动态更新可见区域内的列表项。

 

4.懒加载图片:

当图片加载时,可以在 handleImageLoad 方法中执行额外的逻辑,例如记录日志或更新 UI 状态。

 

5.样式:

使用 CSS 控制列表项和图片的样式,确保列表的外观和交互符合设计需求。

 

通过这种方式,可以有效地结合虚拟化和图片懒加载技术,优化大型数据列表的性能,并提升用户体验。

vue官方给出三个虚拟化的社区库供使用

● vue-virtual-scroller

● vue-virtual-scroll-grid

● vueuc/VVirtualList

 

二、浅层式API

除了列表虚拟化可以较好实现大型列表优化也可以结合浅层式API,对数据做浅度监听

Vue 的响应性系统默认是深度的。当在数据量巨大时,深度响应性也性能负担较大,因为每个属性访问都将触发代理的依赖追踪。

Vue 对上述问题提供了一种解决方案,通过使用 shallowRef() 和 shallowReactive() 来绕开深度响应。浅层式 API 创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理。这使得对深层级属性的访问变得更快,但代价是,必须将所有深层级对象视为不可变的,并且只能通过替换整个根状态来触发更新

vue官方给出的例子如下:

const shallowArray = shallowRef([
  /* 巨大的列表,里面包含深层的对象 */
])

// 这不会触发更新...
shallowArray.value.push(newObject)
// 这才会触发更新
shallowArray.value = [...shallowArray.value, newObject]

// 这不会触发更新...
shallowArray.value[0].foo = 1
// 这才会触发更新
shallowArray.value = [
  {
    ...shallowArray.value[0],
    foo: 1
  },
  ...shallowArray.value.slice(1)
]
 

通过上述方式一定程度降低了浏览器大型列表渲染负担并提升用户体验。

 

0条评论
0 / 1000
w****n
17文章数
1粉丝数
w****n
17 文章 | 1 粉丝
原创

渲染大型列表优化

2024-08-22 10:06:16
3
0
 

前端渲染大型列表变得很慢因为浏览器处理大量DOM节点真实场景我们不需要立刻渲染全部列表只需要渲染一小部分用户鼠标滚动加载数据通过列表虚拟化提升性能

一、列表虚拟化

实际使用场景大型列表每个数据项可能复杂数据项,可能包含图片视频等等前端渲染需要分类处理一下

当数据项中包含图片时,做一个简单demo实现

 

基本思路

1.列表项数据结构:

每个列表项包含一个图片 URL。

 

2.懒加载图片:

只有当列表项进入视口时,才加载相应的图片。

 

3.结合虚拟化列表:

使用虚拟化列表技术,只渲染当前可视区域内的列表项,减少 DOM 操作和资源占用。

 

4.滚动监听:

监听滚动事件,动态计算哪些列表项进入了可视区域,以及何时加载它们的图片。

<template>
  <div class="virtual-list-container" ref="container" @scroll="handleScroll">
    <div :style="{ height: totalHeight + 'px' }">
      <div v-for="(item, index) in visibleItems" :key="item.id"
           :style="{ transform: 'translateY(' + itemPositions[index] + 'px)' }"
           class="virtual-list-item">
        <img v-if="item.isVisible" :src="item.imageUrl" @load="handleImageLoad(index)"
             class="lazy-load-image" />
        <div v-else class="placeholder" style="height: 200px; background-color: #f0f0f0;"></div>
        <p>{{ item.text }}</p>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [], // 数据源
      visibleItems: [], // 当前可见的列表项
      itemPositions: [], // 可见列表项的位置
      itemHeight: 200, // 每个列表项的高度
      totalHeight: 0, // 虚拟列表容器的总高度
      startIndex: 0, // 可视区域内的起始索引
      endIndex: 0, // 可视区域内的结束索引
      bufferSize: 5, // 缓冲区大小,可根据实际情况调整
    };
  },
  mounted() {
    this.initializeList();
  },
  methods: {
    initializeList() {
      // 模拟生成大量数据
      this.items = Array.from({ length: 1000 }, (_, index) => ({
        id: index,
        text: `Item ${index + 1}`,
        imageUrl: `via.placeholder.com/300?text=Image${index + 1}`, // 示例图片 URL
        isVisible: false, // 标记是否可见
      }));
      
      // 计算虚拟列表容器的总高度
      this.totalHeight = this.items.length * this.itemHeight;
      
      // 初始化可见列表项和位置
      this.updateVisibleItems();
    },
    updateVisibleItems() {
      const container = this.$refs.container;
      const scrollTop = container.scrollTop;
      const offset = Math.floor(scrollTop / this.itemHeight) - this.bufferSize;
      this.startIndex = Math.max(offset, 0);
      this.endIndex = Math.min(this.startIndex + this.visibleCount + 2 * this.bufferSize, this.items.length);
      this.visibleItems = this.items.slice(this.startIndex, this.endIndex);
      this.itemPositions = this.visibleItems.map((item, index) => (this.startIndex + index) * this.itemHeight);
      
      // 标记可见项为可见,用于懒加载图片
      this.visibleItems.forEach(item => {
        if (!item.isVisible) {
          item.isVisible = true;
        }
      });
    },
    handleScroll() {
      this.updateVisibleItems();
    },
    handleImageLoad(index) {
      // 图片加载完成时的处理逻辑,可根据需求自定义
      console.log(`Image ${index + 1} loaded.`);
    }
  },
  computed: {
    visibleCount() {
      return Math.ceil(this.$refs.container.clientHeight / this.itemHeight);
    }
  }
};
</script>

<style>
.virtual-list-container {
  height: 400px;
  overflow-y: scroll;
  border: 1px solid #ccc;
}

.virtual-list-item {
  height: 200px; /* 每个列表项的固定高度 */
  border-bottom: 1px solid #eee;
  padding: 10px;
  box-sizing: border-box;
}

.lazy-load-image {
  max-width: 100%;
  max-height: 100%;
  object-fit: cover;
}

.placeholder {
  width: 100%;
}
</style>
 

解释和注意事项

 

1.数据源和初始化:

items 数组中的每个对象包含了一个图片的 URL (imageUrl) 和一个标志 (isVisible),用来表示图片是否已经在可见区域内加载。

 

2.图片加载机制:

在 v-for 中,根据 isVisible 的值决定是渲染 <img> 元素还是一个占位符 <div>。只有当列表项进入可视区域后,才加载相应的图片。

 

3.滚动事件处理:

handleScroll 方法监听滚动事件,根据滚动位置动态更新可见区域内的列表项。

 

4.懒加载图片:

当图片加载时,可以在 handleImageLoad 方法中执行额外的逻辑,例如记录日志或更新 UI 状态。

 

5.样式:

使用 CSS 控制列表项和图片的样式,确保列表的外观和交互符合设计需求。

 

通过这种方式,可以有效地结合虚拟化和图片懒加载技术,优化大型数据列表的性能,并提升用户体验。

vue官方给出三个虚拟化的社区库供使用

● vue-virtual-scroller

● vue-virtual-scroll-grid

● vueuc/VVirtualList

 

二、浅层式API

除了列表虚拟化可以较好实现大型列表优化也可以结合浅层式API,对数据做浅度监听

Vue 的响应性系统默认是深度的。当在数据量巨大时,深度响应性也性能负担较大,因为每个属性访问都将触发代理的依赖追踪。

Vue 对上述问题提供了一种解决方案,通过使用 shallowRef() 和 shallowReactive() 来绕开深度响应。浅层式 API 创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理。这使得对深层级属性的访问变得更快,但代价是,必须将所有深层级对象视为不可变的,并且只能通过替换整个根状态来触发更新

vue官方给出的例子如下:

const shallowArray = shallowRef([
  /* 巨大的列表,里面包含深层的对象 */
])

// 这不会触发更新...
shallowArray.value.push(newObject)
// 这才会触发更新
shallowArray.value = [...shallowArray.value, newObject]

// 这不会触发更新...
shallowArray.value[0].foo = 1
// 这才会触发更新
shallowArray.value = [
  {
    ...shallowArray.value[0],
    foo: 1
  },
  ...shallowArray.value.slice(1)
]
 

通过上述方式一定程度降低了浏览器大型列表渲染负担并提升用户体验。

 

文章来自个人专栏
Vue前端开发
17 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0