侧边栏壁纸
  • 累计撰写 54 篇文章
  • 累计创建 4 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

性能优化:滚动加载更多的实现方案

  1. 监听滚动事件 + 计算滚动位置(传统方案)

    通过监听容器的滚动事件,计算是否已滚动到底部附近。

    计算公式:滚动容器.scrollTop + 滚动容器.clientHeight >= 滚动容器.scrollHeight - 阈值

    scrollTop:已滚动的距离

    clientHeight:容器可视区域的高度

    scrollHeight:整个内容的总高度

    阈值:提前触发的距离

const container = document.getElementById('scroll-container'); // 或 window
let isLoading = false;
let currentPage = 1;
// 使用节流函数优化性能
container.addEventListener('scroll', throttle(() => {
  // 计算是否滚动到底部(距离底部50px内触发)
  const scrollBottom = container.scrollTop + container.clientHeight;
  const totalHeight = container.scrollHeight;
  const threshold = 50;

  if (totalHeight - scrollBottom <= threshold && !isLoading && hasMoreData) {
    loadMoreData();
  }
}, 200)); // 200ms节流

async function loadMoreData() {
  isLoading = true;
  // 显示加载提示...
  try {
    const newData = await fetchData(++currentPage);
    // 将新数据渲染到列表末尾
    renderList(newData);
    // 判断是否还有更多数据
    if (newData.length < pageSize) hasMoreData = false;
  } catch (error) {
    // 错误处理
    currentPage--;
  } finally {
    isLoading = false;
  }
}
  1. 使用 Intersection Observer API(推荐)

    现代浏览器提供的原生 API,用于异步监听目标元素与视窗的交叉状态。

React:

import React, { useEffect, useRef, useState } from 'react';

function InfiniteList() {
  const [list, setList] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);
  const observerTarget = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      entries => {
        if (entries[0].isIntersecting && !loading && hasMore) {
          loadMore();
        }
      },
      { threshold: 1.0 }
    );

    if (observerTarget.current) {
      observer.observe(observerTarget.current);
    }

    return () => {
      if (observerTarget.current) {
        observer.unobserve(observerTarget.current);
      }
    };
  }, [loading, hasMore]);

  const loadMore = async () => {
    setLoading(true);
    const newData = await fetchData(page + 1);
    setList(prev => [...prev, ...newData]);
    setPage(prev => prev + 1);
    if (newData.length === 0) setHasMore(false);
    setLoading(false);
  };

  return (
    <div>
      {list.map(item => (
        <div key={item.id}>{item.content}</div>
      ))}
      {/* 哨兵元素 */}
      <div ref={observerTarget}>
        {loading && <div>加载中...</div>}
        {!hasMore && <div>没有更多了</div>}
      </div>
    </div>
  );
}

Vue:

<template>
  <div class="container" ref="containerRef">
    <!-- 列表内容 -->
    <div v-for="item in list" :key="item.id" class="list-item">
      {{ item.content }}
    </div>
    
    <!-- 底部加载状态 -->
    <div ref="sentinelRef" class="sentinel">
      <div v-if="loading" class="loading">加载中...</div>
      <div v-if="!hasMore" class="no-more">没有更多了</div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'

// 响应式数据
const list = ref([])
const loading = ref(false)
const page = ref(1)
const hasMore = ref(true)
const containerRef = ref(null)
const sentinelRef = ref(null)

// 观察器实例
let observer = null

// 初始化观察器
const initObserver = () => {
  // 清理旧的观察器
  if (observer) {
    observer.disconnect()
  }
  
  observer = new IntersectionObserver(
    (entries) => {
      const entry = entries[0]
      if (entry.isIntersecting && !loading.value && hasMore.value) {
        loadMore()
      }
    },
    {
      root: containerRef.value, // 可滚动的容器
      rootMargin: '100px', // 提前100px触发
      threshold: 0.1, // 10%可见时触发
    }
  )
  
  if (sentinelRef.value) {
    observer.observe(sentinelRef.value)
  }
}

// 加载更多数据
const loadMore = async () => {
  if (loading.value || !hasMore.value) return
  
  loading.value = true
  try {
    // 模拟API请求
    const newData = await fetchData(page.value)
    
    if (newData.length > 0) {
      list.value.push(...newData)
      page.value++
      
      // 重新观察哨兵元素(位置已变)
      nextTick(() => {
        if (observer && sentinelRef.value) {
          observer.unobserve(sentinelRef.value)
          observer.observe(sentinelRef.value)
        }
      })
    } else {
      hasMore.value = false
    }
  } catch (error) {
    console.error('加载失败:', error)
  } finally {
    loading.value = false
  }
}

// 模拟API请求
const fetchData = (pageNum) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      const pageSize = 10
      const newItems = Array.from({ length: pageSize }, (_, i) => ({
        id: (pageNum - 1) * pageSize + i + 1,
        content: `项目 ${(pageNum - 1) * pageSize + i + 1}`,
      }))
      resolve(newItems)
    }, 800)
  })
}

// 生命周期
onMounted(() => {
  // 首次加载
  loadMore()
  // 初始化观察器
  initObserver()
})

onUnmounted(() => {
  if (observer) {
    observer.disconnect()
  }
})
</script>
0

评论区