原生 JS 实现无限滚动加载(下拉加载更多 + 无闪烁 + 性能优化),长列表必备组件

一、功能背景与应用场景

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

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

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

二、核心实现效果

✅ 滚动到底部自动加载数据✅ 防重复请求(加锁机制)✅ 加载中状态提示(Loading)✅ 无数据 / 全部加载完毕状态提示✅ 滚动监听节流优化 ✅ 列表渲染无闪烁、无抖动✅ 支持异步接口模拟(可直接对接后端)✅ 响应式适配,移动端 / PC 端通用✅ 代码模块化、可配置、易扩展

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

html

预览

html 复制代码
<!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">
    <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>

四、核心技术讲解(CSDN 高分必备)

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

js

javascript 复制代码
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. 定位滚动:返回后保持上次位置

六、适用场景

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

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


七、总结

本文实现的无限滚动加载组件 是前端高频面试题 + 企业必备功能。特点:原生 JS、无依赖、防重复请求、性能高、状态完整、无闪烁 。代码可直接用于生产环境,也可作为前端实战案例发布到 CSDN,属于高阅读、高收藏、高互动的爆款技术文章。

相关推荐
叫我一声阿雷吧13 天前
原生 JS 实现图片预览上传组件:多图上传 + 拖拽上传 + 裁剪预览 + 进度显示(附完整源码)
图片上传·canvas·文件上传·响应式布局·拖拽上传·原生javascript·filereader api
叫我一声阿雷吧17 天前
JS 入门通关手册(38):防抖与节流 原理 + 手写 + 实战场景(面试必考)
javascript·性能优化·前端面试·防抖·节流·js手写题
じ星不离月か1 个月前
【记录】 跑马灯无限滚动
前端·css·跑马灯·无限滚动
Pu_Nine_97 个月前
深入理解节流(Throttle):原理、实现与应用场景
javascript·性能优化·es6·节流·lodash 库
前端与小赵1 年前
什么是防抖与节流
前端·防抖·节流
是萝卜干呀1 年前
Frontend - 防止多次请求,避免重复请求
javascript·ajax·jquery·防抖·节流·disabled属性
SchneeDuan1 年前
iOS--利用UITableViewDataSourcePrefetching实现平滑如丝的无限滚动
ios·swift·用户体验·无限滚动
零凌林2 年前
如何避免接口重复请求(axios推荐使用AbortController)
前端·javascript·axios·防抖·节流·请求库·alova.js
追涨杀跌的小韭菜2 年前
小程序上拉触底节流处理
小程序·节流