原生JavaScript实现无限滚动加载效果

 更新时间:2026年04月22日 08:41:25   作者:叫我一声阿雷吧  
无限滚动加载(Infinite Scroll)是现代 Web 应用最主流的列表加载方式,替代传统分页,大幅提升用户浏览体验,下面小编就和大家详细介绍一下如何使用原生JavaScript实现无限滚动加载效果吧

一、功能背景与应用场景

无限滚动加载(Infinite Scroll)是现代 Web 应用最主流的列表加载方式,替代传统分页,大幅提升用户浏览体验,常见场景:

  • 商品列表页、推荐流
  • 评论区、消息列表
  • 文章列表、动态广场
  • 后台数据表格、日志列表

基础版本容易出现:重复请求、加载闪烁、滚动抖动、结束状态不显示、性能差等问题。本文实现企业级稳定版,解决所有常见坑点。

二、核心实现效果

滚动到底部自动加载数据

防重复请求(加锁机制)

加载中状态提示(Loading)

无数据 / 全部加载完毕状态提示

滚动监听节流优化

列表渲染无闪烁、无抖动

支持异步接口模拟(可直接对接后端)

响应式适配,移动端 / PC 端通用

代码模块化、可配置、易扩展

三、完整可运行源码(直接复制发布)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>原生JS实现无限滚动加载(下拉加载更多)</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="external nofollow" >
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: "Microsoft YaHei", sans-serif;
        }
        body {
            background-color: #f5f7fa;
            padding: 20px;
        }
        .container {
            max-width: 800px;
            margin: 0 auto;
        }
        .title {
            font-size: 24px;
            color: #2c3e50;
            margin-bottom: 20px;
            text-align: center;
        }
        /* 列表容器 */
        .list-container {
            display: flex;
            flex-direction: column;
            gap: 15px;
            margin-bottom: 30px;
            min-height: 300px;
        }
        /* 列表项 */
        .list-item {
            background: #fff;
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
            transition: transform 0.2s;
        }
        .list-item:hover {
            transform: translateY(-2px);
        }
        .list-item h3 {
            font-size: 18px;
            color: #2f3542;
            margin-bottom: 8px;
        }
        .list-item p {
            font-size: 14px;
            color: #57606f;
            line-height: 1.6;
        }
        /* 加载状态 */
        .load-status {
            text-align: center;
            padding: 15px 0;
            font-size: 14px;
            color: #666;
            display: none;
        }
        .load-status.active {
            display: block;
        }
        .loading-icon {
            display: inline-block;
            animation: rotate 1s linear infinite;
            margin-right: 6px;
        }
        @keyframes rotate {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
        /* 无更多数据 */
        .no-more {
            color: #999;
            padding: 10px 0;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1 class="title">无限滚动加载演示</h1>
        <div class="list-container" id="listContainer"></div>
        <div class="load-status" id="loadStatus">
            <i class="fas fa-spinner loading-icon"></i>
            <span>正在加载更多数据...</span>
        </div>
        <div class="load-status no-more" id="noMore">已加载全部数据</div>
    </div>
    <script>
        // ====================== 配置项 ======================
        const CONFIG = {
            pageSize: 8,          // 每页条数
            threshold: 150,       // 距离底部多少像素触发加载
            throttleDelay: 150,   // 滚动节流时间
            totalData: 40         // 模拟总数据量
        };
        // ====================== 状态管理 ======================
        let currentPage = 1;
        let isLoading = false;    // 防重复请求锁
        let isEnd = false;        // 是否全部加载完毕
        // DOM
        const listContainer = document.getElementById('listContainer');
        const loadStatus = document.getElementById('loadStatus');
        const noMore = document.getElementById('noMore');
        // ====================== 初始化 ======================
        function init() {
            loadData();
            window.addEventListener('scroll', throttle(handleScroll, CONFIG.throttleDelay));
        }
        // ====================== 滚动监听(触底判断) ======================
        function handleScroll() {
            if (isLoading || isEnd) return;
            const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            const clientHeight = document.documentElement.clientHeight;
            const scrollHeight = document.documentElement.scrollHeight;
            // 触底判断
            if (scrollTop + clientHeight + CONFIG.threshold >= scrollHeight) {
                currentPage++;
                loadData();
            }
        }
        // ====================== 加载数据(模拟接口) ======================
        function loadData() {
            isLoading = true;
            loadStatus.classList.add('active');
            // 模拟请求延迟
            setTimeout(() => {
                const start = (currentPage - 1) * CONFIG.pageSize;
                const end = currentPage * CONFIG.pageSize;
                // 构造数据
                const list = [];
                for (let i = start; i < end && i < CONFIG.totalData; i++) {
                    list.push({
                        id: i + 1,
                        title: `这是第 ${i + 1} 条内容`,
                        content: '原生JS实现无限滚动加载,支持防重复请求、滚动节流、状态管理,企业级稳定方案。'
                    });
                }
                // 渲染
                renderList(list);
                // 判断是否加载完毕
                if (end >= CONFIG.totalData) {
                    isEnd = true;
                    loadStatus.classList.remove('active');
                    noMore.classList.add('active');
                } else {
                    isLoading = false;
                    loadStatus.classList.remove('active');
                }
            }, 800);
        }
        // ====================== 渲染列表 ======================
        function renderList(data) {
            data.forEach(item => {
                const div = document.createElement('div');
                div.className = 'list-item';
                div.innerHTML = `
                    <h3>${item.title}</h3>
                    <p>${item.content}</p>
                `;
                listContainer.appendChild(div);
            });
        }
        // ====================== 节流函数 ======================
        function throttle(fn, delay) {
            let lastTime = 0;
            return function (...args) {
                const now = Date.now();
                if (now - lastTime >= delay) {
                    lastTime = now;
                    fn.apply(this, args);
                }
            };
        }
        // 启动
        init();
    </script>
</body>
</html>

四、核心技术讲解

1. 触底判断公式(最关键)

scrollTop + clientHeight + threshold >= scrollHeight
  • scrollTop:滚动条滚动距离
  • clientHeight:可视窗口高度
  • threshold:距离底部提前触发距离
  • scrollHeight:页面总高度

只要满足公式,就判定即将触底,开始加载下一页。

2. 防重复请求(企业必备)

使用isLoading 锁

  • 开始请求 → isLoading = true
  • 结束请求 → isLoading = false
  • 滚动时判断锁状态,避免多次触发

3. 性能优化:滚动节流

滚动事件触发频率极高,必须使用节流

  • 每 150ms 只执行一次
  • 大幅降低浏览器性能消耗
  • 避免页面卡顿

4. 状态管理规范

  • 加载中 → 显示 Loading
  • 无数据 → 显示 “已加载全部”
  • 结束后不再监听滚动
  • 全程无闪烁、无抖动

5. 扩展性极强

可直接对接真实接口:

  • 替换 setTimeoutfetch/axios
  • 后端返回总条数即可判断是否结束

五、可扩展高级功能(文章加分项)

  1. 错误重试:加载失败显示重试按钮
  2. 下拉刷新:配合 touch 事件实现下拉刷新
  3. 缓存机制:使用 localStorage 缓存列表
  4. 虚拟列表:支持 10w+ 数据不卡顿(超高分亮点)
  5. 淡入动画:加载新项目时添加动画
  6. 定位滚动:返回后保持上次位置

六、适用场景

  • 商品列表
  • 评论列表
  • 消息流
  • 后台管理表格
  • 文章 / 动态列表

所有长列表业务都能直接使用

七、方法补充

原生 JavaScript 实现无限滚动加载是一种常见的列表分页技术,核心是监听滚动事件,当用户滚动到页面底部(或接近底部)时,自动加载下一页数据并追加到列表中。以下是一个完整的实现方案,包含核心原理、代码示例及优化技巧。

核心原理

  • 监听滚动事件:使用 window 或特定容器的 scroll 事件。
  • 计算滚动位置:判断 scrollTop + clientHeight >= scrollHeight - threshold(threshold 为阈值,如 200px)。
  • 触发加载:满足条件时,调用加载函数,请求新数据。
  • 追加内容:将新数据渲染到页面列表末尾。
  • 状态控制:使用 isLoading 标志防止重复请求,使用 hasMore 标志表示是否还有更多数据。

实现代码

HTML 结构

<div id="app">
  <ul id="list" class="list"></ul>
  <div id="loading" class="loading hidden">加载中...</div>
  <div id="no-more" class="no-more hidden">没有更多数据了</div>
</div>

CSS 样式(简单示例)

.list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.list li {
  padding: 12px;
  border-bottom: 1px solid #eee;
}
.loading, .no-more {
  text-align: center;
  padding: 10px;
  color: #999;
}
.hidden {
  display: none;
}

JavaScript 实现

// 模拟异步数据请求
function fetchData(page, pageSize = 10) {
  return new Promise((resolve) => {
    setTimeout(() => {
      const start = (page - 1) * pageSize;
      const items = Array.from({ length: pageSize }, (_, i) => ({
        id: start + i + 1,
        content: `条目 ${start + i + 1}`
      }));
      // 模拟总共 50 条数据
      const hasMore = start + pageSize < 50;
      resolve({ items, hasMore });
    }, 500);
  });
}
class InfiniteScroll {
  constructor(options) {
    this.container = options.container;      // 滚动容器,默认为 window
    this.listEl = options.listEl;            // 列表容器元素
    this.loadingEl = options.loadingEl;      // 加载提示元素
    this.noMoreEl = options.noMoreEl;        // 无更多数据提示元素
    this.fetchData = options.fetchData;      // 获取数据的函数
    this.page = 1;
    this.pageSize = options.pageSize || 10;
    this.isLoading = false;
    this.hasMore = true;
    this.threshold = options.threshold || 200; // 距离底部多少像素时触发
    this.init();
  }
  init() {
    this.loadMore = this.loadMore.bind(this);
    // 绑定滚动事件(使用节流优化)
    this.scrollHandler = this.throttle(this.checkScroll.bind(this), 200);
    this.container.addEventListener('scroll', this.scrollHandler);
    if (this.container === window) {
      window.addEventListener('scroll', this.scrollHandler);
    }
    // 初始加载第一页
    this.loadMore();
  }
  // 检查是否需要加载
  checkScroll() {
    if (!this.hasMore || this.isLoading) return;
    let scrollTop, clientHeight, scrollHeight;
    if (this.container === window) {
      scrollTop = window.pageYOffset || document.documentElement.scrollTop;
      clientHeight = document.documentElement.clientHeight;
      scrollHeight = document.documentElement.scrollHeight;
    } else {
      scrollTop = this.container.scrollTop;
      clientHeight = this.container.clientHeight;
      scrollHeight = this.container.scrollHeight;
    }
    if (scrollTop + clientHeight >= scrollHeight - this.threshold) {
      this.loadMore();
    }
  }
  // 加载更多数据
  async loadMore() {
    if (!this.hasMore || this.isLoading) return;
    this.isLoading = true;
    this.showLoading(true);
    try {
      const { items, hasMore } = await this.fetchData(this.page, this.pageSize);
      this.renderItems(items);
      this.hasMore = hasMore;
      this.page++;
      if (!this.hasMore) {
        this.showNoMore(true);
      }
    } catch (error) {
      console.error('加载失败', error);
      // 可在此处显示错误提示,并提供重试按钮
    } finally {
      this.isLoading = false;
      this.showLoading(false);
    }
  }
  // 渲染列表项
  renderItems(items) {
    const fragment = document.createDocumentFragment();
    items.forEach(item => {
      const li = document.createElement('li');
      li.textContent = item.content;
      fragment.appendChild(li);
    });
    this.listEl.appendChild(fragment);
  }
  // 显示/隐藏加载提示
  showLoading(show) {
    if (this.loadingEl) {
      this.loadingEl.classList.toggle('hidden', !show);
    }
  }
  // 显示/隐藏无更多数据提示
  showNoMore(show) {
    if (this.noMoreEl) {
      this.noMoreEl.classList.toggle('hidden', !show);
    }
  }
  // 节流函数
  throttle(fn, delay) {
    let last = 0;
    return function(...args) {
      const now = Date.now();
      if (now - last > delay) {
        last = now;
        fn.apply(this, args);
      }
    };
  }
  // 销毁,移除事件监听
  destroy() {
    if (this.container === window) {
      window.removeEventListener('scroll', this.scrollHandler);
    } else {
      this.container.removeEventListener('scroll', this.scrollHandler);
    }
  }
}
// 使用示例
const infiniteScroll = new InfiniteScroll({
  container: window,                  // 滚动容器
  listEl: document.getElementById('list'),
  loadingEl: document.getElementById('loading'),
  noMoreEl: document.getElementById('no-more'),
  fetchData: fetchData,              // 自定义数据获取函数
  pageSize: 10,
  threshold: 200
});

八、总结

本文实现的无限滚动加载组件是前端高频面试题 + 企业必备功能。特点:原生 JS、无依赖、防重复请求、性能高、状态完整、无闪烁

以上就是原生JavaScript实现无限滚动加载效果的详细内容,更多关于JavaScript无限滚动加载的资料请关注脚本之家其它相关文章!

相关文章

  • 一种Javascript解释ajax返回的json的好方法(推荐)

    一种Javascript解释ajax返回的json的好方法(推荐)

    下面小编就为大家带来一篇一种Javascript解释ajax返回的json的好方法(推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-06-06
  • js/jquery获取文本框输入焦点的方法

    js/jquery获取文本框输入焦点的方法

    本篇文章主要是对js/jquery获取文本框输入焦点的方法进行了介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2014-03-03
  • 关于JS前端实现水印的代码操作

    关于JS前端实现水印的代码操作

    这篇文章主要介绍了关于JS前端实现水印的代码操作,文中给出了详细的实现思路和代码示例供大家参考,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-06-06
  • javascript数组元素删除方法delete和splice解析

    javascript数组元素删除方法delete和splice解析

    这篇文章主要介绍了javascaipt数组元素删除方法delete和splice解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • JavaScript数组塌陷实例解析

    JavaScript数组塌陷实例解析

    这篇文章主要为大家介绍了JavaScript数组塌陷实例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • 微信小程序实现图片上传

    微信小程序实现图片上传

    这篇文章主要为大家详细介绍了微信小程序实现图片上传,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-05-05
  • js 完美图片新闻轮转效果,腾讯大粤网首页图片轮转改造而来

    js 完美图片新闻轮转效果,腾讯大粤网首页图片轮转改造而来

    找过很多图片轮询效果,和我的要求总是有些出入,不能十全十美。不是功能不全,就是太多花哨。本想自己开发一个,鉴于琐事拖延,迟迟未能开始
    2011-11-11
  • 如何利用 JS 脚本实现网页全自动秒杀抢购功能

    如何利用 JS 脚本实现网页全自动秒杀抢购功能

    这篇文章主要介绍了如何利用 JS 脚本实现网页全自动秒杀抢购功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • JavaScript严格模式详解

    JavaScript严格模式详解

    在 JavaScript 的严格模式下,对 JavaScript 的写法做了一些限制。如果在严格模式下违反了这些限制,代码就会报错,对javascript严格模式相关知识感兴趣的朋友一起学习吧
    2015-11-11
  • 详解web如何改变主题配色方法示例

    详解web如何改变主题配色方法示例

    这篇文章主要为大家介绍了web如何改变主题配色方法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03

最新评论