javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JavaScript无限滚动加载

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

作者:叫我一声阿雷吧

无限滚动加载(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

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

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

使用isLoading 锁

3. 性能优化:滚动节流

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

4. 状态管理规范

5. 扩展性极强

可直接对接真实接口:

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

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

六、适用场景

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

七、方法补充

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

核心原理

实现代码

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无限滚动加载的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文