如何在JavaScript中实现无限滚动?

来源:IPIPP.com作者:头衔:全栈工程师
导读:本期聚焦于小伙伴创作的《如何在JavaScript中实现无限滚动?》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《如何在JavaScript中实现无限滚动?》有用,将其分享出去将是对创作者最好的鼓励。

无限滚动是提升内容浏览体验的常用功能,核心是通过JavaScript监听页面滚动行为,在用户接近底部时自动触发数据加载和渲染,不需要手动操作翻页。下面详细介绍完整的实现步骤和注意事项。

如何在JavaScript中实现无限滚动?

实现核心逻辑

1. 监听滚动事件

首先需要监听窗口的scroll事件,获取当前滚动位置和页面总高度,判断是否已经滚动到接近底部的位置。

// 监听窗口滚动事件
window.addEventListener('scroll', handleScroll);

function handleScroll() {
  // 获取当前滚动高度:已滚动的内容高度
  const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  // 获取可视区域高度:当前窗口可以看到的内容高度
  const clientHeight = document.documentElement.clientHeight || window.innerHeight;
  // 获取页面总高度:整个文档的内容高度
  const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
  
  // 设置加载阈值,滚动到距离底部200px时触发加载
  const threshold = 200;
  // 判断是否到达加载位置
  if (scrollTop + clientHeight >= scrollHeight - threshold) {
    console.log('触发加载更多');
  }
}

2. 添加加载状态控制

为了避免滚动时多次触发请求,需要添加加载状态标记,正在请求数据时禁止再次触发请求。

// 标记是否正在加载
let isLoading = false;
// 标记是否还有更多数据
let hasMore = true;
// 当前加载的页码
let currentPage = 1;
// 每页加载的数据条数
const pageSize = 10;

function handleScroll() {
  if (isLoading || !hasMore) return;
  
  const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  const clientHeight = document.documentElement.clientHeight || window.innerHeight;
  const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
  const threshold = 200;
  
  if (scrollTop + clientHeight >= scrollHeight - threshold) {
    loadMoreData();
  }
}

3. 实现数据加载和渲染

触发加载后调用接口获取新数据,渲染到页面中,同时更新加载状态和页码。

// 获取内容容器
const contentContainer = document.getElementById('content-container');

async function loadMoreData() {
  isLoading = true;
  // 显示加载提示
  showLoadingTip();
  
  try {
    // 模拟异步请求,实际开发中替换为真实接口地址,如http://ipipp.com/api/list
    const res = await mockFetchData(currentPage, pageSize);
    const dataList = res.data;
    
    if (dataList.length === 0) {
      // 没有更多数据
      hasMore = false;
      showNoMoreTip();
    } else {
      // 渲染新数据
      renderData(dataList);
      currentPage++;
    }
  } catch (error) {
    console.error('加载数据失败', error);
    showErrorTip();
  } finally {
    isLoading = false;
    hideLoadingTip();
  }
}

// 模拟数据请求
function mockFetchData(page, size) {
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = [];
      // 模拟生成数据,最多加载5页
      if (page <= 5) {
        for (let i = 0; i < size; i++) {
          data.push({
            id: (page - 1) * size + i + 1,
            title: `列表项 ${(page - 1) * size + i + 1}`,
            content: `这是第${page}页的第${i + 1}条内容`
          });
        }
      }
      resolve({ data });
    }, 1000);
  });
}

// 渲染数据到页面
function renderData(list) {
  const fragment = document.createDocumentFragment();
  list.forEach(item => {
    const div = document.createElement('div');
    div.className = 'list-item';
    div.innerHTML = `

${item.title}

${item.content}

`; fragment.appendChild(div); }); contentContainer.appendChild(fragment); }

4. 性能优化处理

滚动事件触发频率很高,直接使用会导致性能问题,可以通过节流函数优化。

// 节流函数,控制滚动事件触发频率
function throttle(fn, delay = 200) {
  let timer = null;
  return function(...args) {
    if (timer) return;
    timer = setTimeout(() => {
      fn.apply(this, args);
      timer = null;
    }, delay);
  };
}

// 使用节流后的滚动处理函数
window.addEventListener('scroll', throttle(handleScroll, 200));

完整HTML示例

下面是包含样式和结构的完整示例,可以直接运行查看效果。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JavaScript无限滚动示例</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    body {
      font-family: Arial, sans-serif;
      padding: 20px;
    }
    .list-item {
      padding: 15px;
      margin-bottom: 10px;
      border: 1px solid #eee;
      border-radius: 4px;
    }
    .list-item h3 {
      margin-bottom: 8px;
      color: #333;
    }
    .list-item p {
      color: #666;
      line-height: 1.5;
    }
    .tip {
      text-align: center;
      padding: 15px;
      color: #999;
      font-size: 14px;
    }
    .loading {
      color: #1890ff;
    }
    .error {
      color: #ff4d4f;
    }
  </style>
</head>
<body>
  <h1>无限滚动示例</h1>
  <div id="content-container"></div>
  <div id="tip" class="tip">向下滚动加载更多</div>

  <script>
    const contentContainer = document.getElementById('content-container');
    const tipElement = document.getElementById('tip');
    let isLoading = false;
    let hasMore = true;
    let currentPage = 1;
    const pageSize = 10;

    // 节流函数
    function throttle(fn, delay = 200) {
      let timer = null;
      return function(...args) {
        if (timer) return;
        timer = setTimeout(() => {
          fn.apply(this, args);
          timer = null;
        }, delay);
      };
    }

    // 滚动处理函数
    function handleScroll() {
      if (isLoading || !hasMore) return;
      
      const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
      const clientHeight = document.documentElement.clientHeight || window.innerHeight;
      const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
      const threshold = 200;
      
      if (scrollTop + clientHeight >= scrollHeight - threshold) {
        loadMoreData();
      }
    }

    // 加载数据
    async function loadMoreData() {
      isLoading = true;
      tipElement.textContent = '加载中...';
      tipElement.className = 'tip loading';
      
      try {
        const res = await mockFetchData(currentPage, pageSize);
        const dataList = res.data;
        
        if (dataList.length === 0) {
          hasMore = false;
          tipElement.textContent = '没有更多内容了';
        } else {
          renderData(dataList);
          currentPage++;
          tipElement.textContent = '向下滚动加载更多';
        }
      } catch (error) {
        console.error('加载失败', error);
        tipElement.textContent = '加载失败,请稍后重试';
        tipElement.className = 'tip error';
      } finally {
        isLoading = false;
      }
    }

    // 模拟请求
    function mockFetchData(page, size) {
      return new Promise((resolve) => {
        setTimeout(() => {
          const data = [];
          if (page <= 5) {
            for (let i = 0; i < size; i++) {
              data.push({
                id: (page - 1) * size + i + 1,
                title: `列表项 ${(page - 1) * size + i + 1}`,
                content: `这是第${page}页的第${i + 1}条内容,用于演示无限滚动效果`
              });
            }
          }
          resolve({ data });
        }, 1000);
      });
    }

    // 渲染数据
    function renderData(list) {
      const fragment = document.createDocumentFragment();
      list.forEach(item => {
        const div = document.createElement('div');
        div.className = 'list-item';
        div.innerHTML = `
          <h3>${item.title}</h3>
          <p>${item.content}</p>
        `;
        fragment.appendChild(div);
      });
      contentContainer.appendChild(fragment);
    }

    // 绑定节流后的滚动事件
    window.addEventListener('scroll', throttle(handleScroll, 200));
    // 初始加载第一页数据
    loadMoreData();
  </script>
</body>
</html>

注意事项

  • 加载阈值可以根据页面实际布局调整,避免触发过早或过晚
  • 真实项目中需要替换模拟请求为真实接口,注意处理接口鉴权、参数传递等问题
  • 如果页面内容动态变化,需要重新计算scrollHeight的值,避免判断出现偏差
  • 可以在组件销毁时移除滚动事件监听,避免内存泄漏

JavaScript无限滚动事件监听异步请求DOM操作修改时间:2026-06-03 00:26:28

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。