React无限滚动插件react-infinite-scroll-component的配置+优化+避坑指南

1. 前言

react-infinite-scroll-component 是 React 生态中一款轻量、易用的无限滚动插件,核心目标是帮助开发者快速实现"滚动到底部自动加载更多"的交互效果。它无需手动监听滚动事件、计算滚动位置,而是通过封装好的组件化 API,简化无限滚动的实现逻辑,同时支持加载状态显示、无更多数据提示、自定义触发距离等实用功能。

相比原生实现或其他同类插件,其核心优势如下:

  • 零冗余代码 :无需手动处理 scroll 事件监听、滚动高度计算等底层逻辑;
  • 高度可定制:支持自定义加载中组件、无更多数据提示、触发加载的距离阈值;
  • 兼容性强 :适配 PC 端与移动端,支持滚动容器为 window 或自定义 DOM 元素;
  • 轻量无依赖:体积小巧(gzip 压缩后仅 ~3KB),无额外第三方依赖,不增加项目负担。

2. 快速上手:安装与基础使用

2.1 安装依赖

通过 npm 或 yarn 安装插件(最新版本可参考 npm 官网):

bash 复制代码
# npm 安装
npm install react-infinite-scroll-component --save

# yarn 安装
yarn add react-infinite-scroll-component

2.2 基础示例:实现列表无限滚动

以下是最基础的使用场景------滚动 window 窗口到底部时,自动加载更多列表数据:

jsx 复制代码
import React, { useState, useEffect } from 'react';
import InfiniteScroll from 'react-infinite-scroll-component';

const BasicInfiniteScroll = () => {
  // 1. 状态管理:列表数据、是否有更多数据
  const [list, setList] = useState<number[]>([]);
  const [hasMore, setHasMore] = useState<boolean>(true);
  const [page, setPage] = useState<number>(1); // 分页参数

  // 2. 初始化加载第一页数据
  useEffect(() => {
    fetchData(page);
  }, [page]);

  // 3. 模拟接口请求:获取列表数据
  const fetchData = async (currentPage: number) => {
    try {
      // 模拟接口延迟(实际项目替换为真实接口请求)
      const response = await new Promise<number[]>((resolve) => {
        setTimeout(() => {
          // 生成 10 条模拟数据
          const newData = Array.from({ length: 10 }, (_, i) => (currentPage - 1) * 10 + i + 1);
          resolve(newData);
        }, 800);
      });

      // 更新列表数据
      setList(prev => [...prev, ...response]);

      // 控制是否还有更多数据(示例:最多加载 5 页)
      if (currentPage >= 5) {
        setHasMore(false);
      }
    } catch (err) {
      console.error('数据加载失败:', err);
    }
  };

  // 4. 加载更多回调:触发下一页数据请求
  const fetchMoreData = () => {
    setPage(prev => prev + 1); // 页码 +1,触发 useEffect 重新请求
  };

  return (
    <div style={{ padding: '20px' }}>
      <h2>基础无限滚动示例</h2>
      {/* 5. 无限滚动组件 */}
      <InfiniteScroll
        dataLength={list.length} // 已加载数据的长度(用于判断是否触发加载)
        next={fetchMoreData} // 滚动到底部时触发的"加载更多"回调
        hasMore={hasMore} // 是否还有更多数据(false 时停止触发 next)
        loader={<h4 style={{ textAlign: 'center', padding: '20px' }}>加载中...</h4>} // 加载中提示组件
        endMessage={
          <p style={{ textAlign: 'center', padding: '20px', color: '#666' }}>
            <b>已加载全部数据</b>
          </p>
        } // 无更多数据时的提示
      >
        {/* 6. 列表内容 */}
        <ul style={{ listStyle: 'none', padding: 0 }}>
          {list.map((item) => (
            <li
              key={item}
              style={{
                padding: '15px',
                margin: '10px 0',
                border: '1px solid #eee',
                borderRadius: '4px',
              }}
            >
              列表项 {item}
            </li>
          ))}
        </ul>
      </InfiniteScroll>
    </div>
  );
};

export default BasicInfiniteScroll;

核心逻辑说明

  1. dataLength:插件通过对比"已加载数据长度"与"滚动容器高度",判断是否触发 next 回调;
  2. next:滚动到底部时执行,通常用于更新分页参数(如页码 +1),进而触发数据请求;
  3. hasMore:控制插件是否继续监听滚动------当 hasMore=false 时,不再触发 next,并显示 endMessage
  4. loader/endMessage:分别对应"加载中"和"无更多数据"的 UI 提示,支持自定义组件。

3. 核心配置项详解

react-infinite-scroll-component 提供了丰富的配置项,可满足不同场景的需求。以下是常用配置项的分类说明:

3.1 核心功能配置

配置项 类型 作用 默认值
dataLength number 已加载数据的长度(必传),插件通过此值判断是否需要触发加载 -
next () => void 滚动到底部时触发的"加载更多"回调(必传),用于请求下一页数据 -
hasMore boolean 是否还有更多数据------false 时停止触发 next,并显示 endMessage true
loader ReactNode 加载中的提示组件(如"加载中..."文字、Spinner 动画) <h4>Loading...</h4>
endMessage ReactNode 无更多数据时的提示组件(hasMore=false 时显示) -

3.2 滚动容器配置

默认情况下,插件监听 window 的滚动事件;若需监听自定义 DOM 容器的滚动(如带固定高度的列表),需配置以下参数:

配置项 类型 作用 默认值
scrollableTarget string 自定义滚动容器的 id(需给容器设置 id 和固定高度 + overflow: auto -
height string/number 滚动容器的高度(仅当未指定 scrollableTarget 时生效,如 500px -
style CSSProperties 滚动容器的自定义样式(如边框、内边距) {}

示例:自定义滚动容器

jsx 复制代码
// 自定义滚动容器(固定高度 500px,超出滚动)
<div id="customScrollContainer" style={{ height: '500px', overflow: 'auto', border: '1px solid #eee' }}>
  <InfiniteScroll
    scrollableTarget="customScrollContainer" // 绑定自定义容器的 id
    dataLength={list.length}
    next={fetchMoreData}
    hasMore={hasMore}
    loader={<div style={{ textAlign: 'center', padding: '20px' }}>加载中...</div>}
    endMessage={<div style={{ textAlign: 'center', padding: '20px' }}>已加载全部</div>}
  >
    {/* 列表内容 */}
    <ul>
      {list.map(item => (
        <li key={item} style={{ padding: '15px', borderBottom: '1px solid #eee' }}>
          自定义容器列表项 {item}
        </li>
      ))}
    </ul>
  </InfiniteScroll>
</div>

3.3 加载触发时机配置

默认情况下,滚动到"容器底部"时触发加载;若需提前触发(如滚动到距离底部 200px 时开始加载),可通过以下参数调整:

配置项 类型 作用 默认值
threshold number 触发加载的"提前距离"(单位:px)------距离底部小于该值时触发 next 100
disableScroll boolean 是否禁用滚动监听(如加载失败时,禁止继续触发加载) false

示例:提前触发加载

jsx 复制代码
<InfiniteScroll
  dataLength={list.length}
  next={fetchMoreData}
  hasMore={hasMore}
  threshold={300} // 距离底部 300px 时触发加载(适合大列表或慢网络)
  loader={<div>加载中...</div>}
>
  {/* 列表内容 */}
</InfiniteScroll>

3.4 其他实用配置

配置项 类型 作用 默认值
onScroll (e: UIEvent) => void 滚动时的回调(可用于自定义滚动逻辑,如记录滚动位置) -
initialScrollY number 初始滚动位置(单位:px)------组件挂载时自动滚动到指定位置 0
reverse boolean 是否反向滚动(从顶部加载更多,适合聊天记录等场景) false
pullDownToRefresh boolean 是否启用"下拉刷新"功能(需配合 pullDownToRefreshContentonPullDownRefresh false
pullDownToRefreshContent ReactNode 下拉刷新时的提示组件(如"下拉可刷新") <h3>Pull down to refresh</h3>
releaseToRefreshContent ReactNode 下拉到阈值后释放时的提示组件(如"释放即可刷新") <h3>Release to refresh</h3>
onPullDownRefresh () => void 下拉刷新触发的回调(需手动调用 setPullDownToRefresh(false) 结束刷新) -

4. 场景化进阶示例

4.1 反向滚动:聊天记录场景

聊天记录通常需要"从底部向上加载历史消息"(新消息在底部,滚动到顶部时加载更早的记录),可通过 reverse 配置实现:

jsx 复制代码
import React, { useState, useEffect } from 'react';
import InfiniteScroll from 'react-infinite-scroll-component';

const ChatInfiniteScroll = () => {
  const [messages, setMessages] = useState<string[]>([]);
  const [hasMore, setHasMore] = useState<boolean>(true);
  const [page, setPage] = useState<number>(1);

  // 初始化加载最新消息(第 1 页)
  useEffect(() => {
    fetchChatHistory(page);
  }, [page]);

  // 模拟加载聊天历史记录(页号越小,消息越新)
  const fetchChatHistory = async (currentPage: number) => {
    await new Promise<void>((resolve) => {
      setTimeout(() => {
        const newMessages = Array.from({ length: 5 }, (_, i) => 
          `历史消息 ${(currentPage - 1) * 5 + i + 1}(页 ${currentPage})`
        );
        setMessages(prev => [...newMessages, ...prev]); // 新消息添加到数组头部(反向)
        
        // 最多加载 4 页历史消息
        if (currentPage >= 4) {
          setHasMore(false);
        }
        resolve();
      }, 800);
    });
  };

  // 滚动到顶部时加载更多历史消息
  const fetchMoreHistory = () => {
    setPage(prev => prev + 1);
  };

  return (
    <div style={{ height: '600px', border: '1px solid #eee', borderRadius: '4px' }}>
      <h3 style={{ padding: '10px', borderBottom: '1px solid #eee' }}>聊天窗口</h3>
      {/* 反向滚动容器 */}
      <InfiniteScroll
        scrollableTarget="chatContainer" // 自定义滚动容器 id
        dataLength={messages.length}
        next={fetchMoreHistory}
        hasMore={hasMore}
        reverse={true} // 启用反向滚动(顶部加载更多)
        loader={<div style={{ textAlign: 'center', padding: '10px' }}>加载更早的消息...</div>}
        endMessage={<div style={{ textAlign: 'center', padding: '10px', color: '#666' }}>已加载全部历史消息</div>}
      >
        <div id="chatContainer" style={{ height: '540px', overflow: 'auto', padding: '10px' }}>
          {messages.map((msg, index) => (
            <div
              key={index}
              style={{
                margin: '8px 0',
                padding: '8px 12px',
                backgroundColor: '#f5f5f5',
                borderRadius: '8px',
                maxWidth: '80%',
              }}
            >
              {msg}
            </div>
          ))}
        </div>
      </InfiniteScroll>
    </div>
  );
};

export default ChatInfiniteScroll;

核心逻辑

  • reverse={true}:插件监听"滚动到顶部"事件,触发 next 加载历史数据;
  • 新加载的历史消息通过 [...newMessages, ...prev] 添加到数组头部,确保 UI 中"历史消息在上方,最新消息在下方"。

4.2 下拉刷新 + 无限滚动

结合"下拉刷新"(更新最新数据)和"无限滚动"(加载更多历史数据),满足列表的完整交互需求:

jsx 复制代码
const PullRefreshAndInfinite = () => {
  const [list, setList] = useState<number[]>([]);
  const [hasMore, setHasMore] = useState<boolean>(true);
  const [page, setPage] = useState<number>(1);
  const [pullDownToRefresh, setPullDownToRefresh] = useState<boolean>(false); // 控制下拉刷新状态

  // 初始化加载
  useEffect(() => {
    fetchData(page);
  }, [page]);

  // 加载列表数据
  const fetchData = async (currentPage: number) => {
    await new Promise<void>((resolve) => {
      setTimeout(() => {
        const newData = Array.from({ length: 10 }, (_, i) => (currentPage - 1) * 10 + i + 1);
        setList(prev => currentPage === 1 ? newData : [...prev, ...newData]); // 第一页覆盖,后续页追加
        setHasMore(currentPage < 5);
        resolve();
      }, 800);
    });
  };

  // 加载更多(滚动到底部)
  const fetchMoreData = () => {
    setPage(prev => prev + 1);
  };

  // 下拉刷新(更新最新数据)
  const handlePullDownRefresh = async () => {
    setPullDownToRefresh(true); // 显示"刷新中"状态
    await fetchData(1); // 重新请求第一页数据(最新数据)
    setPullDownToRefresh(false); // 结束刷新
  };

  return (
    <InfiniteScroll
      dataLength={list.length}
      next={fetchMoreData}
      hasMore={hasMore}
      loader={<div style={{ textAlign: 'center', padding: '20px' }}>加载中...</div>}
      endMessage={<div style={{ textAlign: 'center', padding: '20px' }}>已加载全部</div>}
      // 下拉刷新配置
      pullDownToRefresh={pullDownToRefresh}
      pullDownToRefreshContent={<div style={{ textAlign: 'center', padding: '20px' }}>下拉可刷新</div>}
      releaseToRefreshContent={<div style={{ textAlign: 'center', padding: '20px' }}>释放即可刷新</div>}
      onPullDownRefresh={handlePullDownRefresh}
    >
      <ul style={{ listStyle: 'none', padding: '0 20px' }}>
        {list.map(item => (
          <li
            key={item}
            style={{ padding: '15px', margin: '10px 0', borderBottom: '1px solid #eee' }}
          >
            带下拉刷新的列表项 {item}
          </li>
        ))}
      </ul>
    </InfiniteScroll>
  );
};

export default PullRefreshAndInfinite;

核心逻辑

  • pullDownToRefresh:控制下拉刷新状态(true 时显示刷新中 UI,禁止重复触发);
  • onPullDownRefresh:下拉到阈值并释放后触发,通常用于重新请求第一页数据(获取最新内容);
  • 数据更新时需将 pullDownToRefresh 设为 false,否则刷新状态会一直保持。

4.3 加载失败重试

实际项目中可能出现接口请求失败的情况,需提供"重试加载"功能,可通过状态管理控制加载状态与重试逻辑:

jsx 复制代码
const RetryOnFail = () => {
  const [list, setList] = useState<number[]>([]);
  const [hasMore, setHasMore] = useState<boolean>(true);
  const [page, setPage] = useState<number>(1);
  const [isLoading, setIsLoading] = useState<boolean>(false); // 加载中状态(防止重复请求)
  const [loadError, setLoadError] = useState<boolean>(false); // 加载失败状态

  // 初始化加载
  useEffect(() => {
    fetchData(page);
  }, [page]);

  // 数据请求逻辑(含失败处理)
  const fetchData = async (currentPage: number) => {
    setIsLoading(true);
    setLoadError(false); // 重置失败状态
    try {
      const response = await new Promise<number[]>((resolve, reject) => {
        setTimeout(() => {
          // 模拟 30% 概率请求失败
          if (Math.random() < 0.3) {
            reject(new Error('接口请求失败'));
          } else {
            const newData = Array.from({ length: 10 }, (_, i) => (currentPage - 1) * 10 + i + 1);
            resolve(newData);
          }
        }, 800);
      });

      setList(prev => [...prev, ...response]);
      setHasMore(currentPage < 5);
    } catch (err) {
      console.error('加载失败:', err);
      setLoadError(true); // 标记加载失败
    } finally {
      setIsLoading(false); // 结束加载状态
    }
  };

  // 加载更多(仅当非加载中、非失败时触发)
  const fetchMoreData = () => {
    if (!isLoading && !loadError) {
      setPage(prev => prev + 1);
    }
  };

  // 重试加载当前页
  const retryLoad = () => {
    fetchData(page);
  };

  return (
    <InfiniteScroll
      dataLength={list.length}
      next={fetchMoreData}
      hasMore={hasMore && !loadError} // 失败时停止触发自动加载
      disableScroll={isLoading || loadError} // 加载中/失败时禁用滚动触发
      // 加载中/失败 UI 切换
      loader={isLoading ? <div style={{ textAlign: 'center', padding: '20px' }}>加载中...</div> : null}
      endMessage={
        hasMore ? null : (
          <div style={{ textAlign: 'center', padding: '20px', color: '#666' }}>已加载全部数据</div>
        )
      }
    >
      <ul style={{ listStyle: 'none', padding: '0 20px' }}>
        {list.map(item => (
          <li
            key={item}
            style={{ padding: '15px', margin: '10px 0', border: '1px solid #eee', borderRadius: '4px' }}
          >
            支持重试的列表项 {item}
          </li>
        ))}
        {/* 加载失败提示与重试按钮 */}
        {loadError && (
          <div style={{ textAlign: 'center', padding: '20px' }}>
            <p style={{ color: 'red' }}>加载失败,请重试</p>
            <button
              onClick={retryLoad}
              style={{
                padding: '8px 16px',
                margin: '10px 0',
                border: 'none',
                backgroundColor: '#007bff',
                color: 'white',
                borderRadius: '4px',
                cursor: 'pointer',
              }}
            >
              重试加载
            </button>
          </div>
        )}
      </ul>
    </InfiniteScroll>
  );
};

export default RetryOnFail;

核心逻辑

  • isLoading:防止滚动时重复触发请求(加载中时禁用 next);
  • loadError:标记请求失败状态,显示重试按钮,同时停止自动加载;
  • retryLoad:重试时重新请求当前页数据,而非直接请求下一页,确保数据连续性。

5. 性能优化建议

无限滚动场景若处理不当,可能导致列表渲染性能下降(如 DOM 元素过多、重复渲染),以下是关键优化点:

5.1 实现列表项虚拟滚动

当列表数据量极大(如超过 100 条)时,直接渲染所有 DOM 元素会占用大量内存,导致页面卡顿。建议结合 react-windowreact-virtualized 实现"虚拟滚动"(仅渲染可视区域内的列表项)。

示例:结合 react-window 优化

bash 复制代码
# 安装 react-window
npm install react-window --save
jsx 复制代码
import { FixedSizeList as List } from 'react-window';
import InfiniteScroll from 'react-infinite-scroll-component';

const VirtualizedInfiniteScroll = () => {
  const [list, setList] = useState<number[]>([]);
  const [hasMore, setHasMore] = useState<boolean>(true);
  const [page, setPage] = useState<number>(1);

  // 数据请求逻辑(同前)
  useEffect(() => {
    fetchData(page);
  }, [page]);

  const fetchData = async (currentPage: number) => {
    // 模拟数据请求...
    const newData = Array.from({ length: 20 }, (_, i) => (currentPage - 1) * 20 + i + 1);
    setList(prev => [...prev, ...newData]);
    setHasMore(currentPage < 10);
  };

  const fetchMoreData = () => {
    setPage(prev => prev + 1);
  };

  // 渲染单个列表项(react-window 要求)
  const renderListItem = ({ index, style }: { index: number; style: React.CSSProperties }) => {
    const item = list[index];
    return (
      <div style={{ ...style, padding: '15px', borderBottom: '1px solid #eee' }}>
        虚拟滚动列表项 {item}
      </div>
    );
  };

  return (
    <InfiniteScroll
      dataLength={list.length}
      next={fetchMoreData}
      hasMore={hasMore}
      loader={<div style={{ textAlign: 'center', padding: '20px' }}>加载中...</div>}
      endMessage={<div style={{ textAlign: 'center', padding: '20px' }}>已加载全部</div>}
    >
      {/* 虚拟滚动列表:仅渲染可视区域内的项(高度 500px,每项高度 60px) */}
      <List
        height={500}
        width="100%"
        itemCount={list.length}
        itemSize={60} // 每项固定高度
      >
        {renderListItem}
      </List>
    </InfiniteScroll>
  );
};

export default VirtualizedInfiniteScroll;

优化原理:react-window 通过计算可视区域范围,仅渲染"能看到的列表项",即使列表有 1000 条数据,DOM 元素数量也仅为"可视区域高度 / 每项高度"(通常 10 条左右),大幅降低渲染压力。

5.2 避免重复请求

  • isLoading 状态控制:请求发起时设为 true,请求结束(成功/失败)后设为 falsenext 回调中判断 !isLoading 才触发下一次请求;
  • 限制请求频率:避免快速滚动时频繁触发 next(插件内部已做防抖,但可结合 threshold 增大提前加载距离,减少请求次数)。

5.3 优化数据更新逻辑

  • 避免直接修改原数组:使用 setList(prev => [...prev, ...newData]) 而非 list.push(...newData),确保 React 能正确识别状态变化;

  • 分页数据去重:若接口可能返回重复数据(如分页参数异常),可在更新列表前通过 Setfilter 去重:

    jsx 复制代码
    setList(prev => {
      const uniqueData = [...new Set([...prev, ...newData])];
      return uniqueData;
    });

5.4 减少不必要的重渲染

  • 列表项用 memo 包裹:若列表项为自定义组件,且 props 不变时无需重渲染,可通过 React.memo 优化:

    jsx 复制代码
    const ListItem = React.memo(({ item }: { item: number }) => {
      return <div style={{ padding: '15px' }}>列表项 {item}</div>;
    });
  • 避免在渲染中定义函数:将 fetchMoreDataretryLoad 等函数用 useCallback 包裹,防止每次渲染生成新函数导致子组件重渲染:

    jsx 复制代码
    const fetchMoreData = useCallback(() => {
      if (!isLoading && !loadError) {
        setPage(prev => prev + 1);
      }
    }, [isLoading, loadError]);

6. 常见问题与解决方案

6.1 滚动不触发加载?

  • 检查 dataLength:必须正确传递"已加载数据的长度",插件通过 dataLength 判断"是否已滚动到可加载位置"(若 dataLength=0,可能因"无数据可滚动"无法触发);
  • 确认滚动容器:若用 scrollableTarget,需确保容器设置了 idoverflow: auto + 固定高度(无固定高度则容器会被内容撑满,无法滚动);
  • 检查 hasMore:若初始 hasMore=false,插件会直接显示 endMessage,不触发 next
  • 查看控制台报错:若存在 JS 错误(如 fetchData 未定义),会导致 next 回调执行失败,需优先修复错误。

6.2 加载后列表不滚动到底部?

  • 反向滚动场景(reverse=true):加载历史消息后,需手动滚动到"加载前的位置"(避免每次加载都跳到顶部),可通过 scrollableTarget 对应的 DOM 元素控制:

    jsx 复制代码
    const chatContainer = document.getElementById('chatContainer');
    if (chatContainer) {
      const scrollTop = chatContainer.scrollTop; // 记录加载前的滚动位置
      // 数据更新后恢复滚动位置
      setTimeout(() => {
        chatContainer.scrollTop = scrollTop;
      }, 0);
    }
  • 正常滚动场景:若需加载后自动滚动到底部,可在 setList 后调用 scrollTo

    jsx 复制代码
    setList(prev => [...prev, ...newData], () => {
      window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
    });

6.3 下拉刷新不生效?

  • 确保配置完整:需同时设置 pullDownToRefresh(控制状态)、onPullDownRefresh(回调)、pullDownToRefreshContent(提示 UI);
  • onPullDownRefresh 中必须重置状态:回调执行完后需将 pullDownToRefresh 设为 false,否则刷新状态会一直保持;
  • 检查滚动容器:下拉刷新仅支持 window 滚动或"高度固定且可滚动的容器",若容器无固定高度,可能无法触发下拉逻辑。

6.4 移动端滚动不流畅?

  • 关闭触摸事件阻止:若项目中存在 touchmove 事件阻止默认行为(如 e.preventDefault()),可能影响移动端滚动,需在滚动容器内放行触摸事件;

  • 优化列表项样式:避免使用复杂 CSS(如 box-shadowgradient)或大量图片,可通过 will-change: transform 提示浏览器提前优化渲染:

    css 复制代码
    .list-item {
      will-change: transform;
      /* 其他样式 */
    }

7. 总结

react-infinite-scroll-component 是一款"开箱即用"的无限滚动插件,其核心价值在于简化底层滚动逻辑,让开发者专注于数据处理与 UI 设计。通过本文的讲解,可掌握:

  1. 基础用法 :快速实现"滚动加载更多",配置 dataLengthnexthasMore 核心参数;
  2. 场景进阶:处理反向滚动(聊天记录)、下拉刷新、加载失败重试等实际需求;
  3. 性能优化:结合虚拟滚动、避免重复请求、减少重渲染,确保大列表流畅运行;
  4. 问题排查:解决滚动不触发、加载异常等常见问题。

适用场景包括:商品列表、文章列表、聊天记录、数据报表等"需要批量加载数据且滚动查看"的界面。在实际项目中,建议根据数据量大小选择是否结合虚拟滚动,并始终关注"用户体验"(如加载状态提示、失败重试、避免卡顿),让无限滚动既实用又流畅。


本次分享就到这儿啦,我是鹏多多,深耕前端的技术创作者,如果您看了觉得有帮助,欢迎评论,关注,点赞,转发,我们下次见~

PS:在本页按F12,在console中输入document.getElementsByClassName('panel-btn')[0].click();有惊喜哦~

往期文章

相关推荐
不会算法的小灰4 小时前
HTML盒子模型详解
前端·html
lifejump4 小时前
文章管理系统CMS的XSS注入渗透测试(白盒)
前端·web安全·xss·安全性测试
华仔啊4 小时前
Vue3 登录页还能这么丝滑?这个 hover 效果太惊艳了
前端·css·vue.js
IT_陈寒4 小时前
JavaScript引擎优化:5个90%开发者都不知道的V8隐藏性能技巧
前端·人工智能·后端
云和数据.ChenGuang4 小时前
Component template requires a root element, rather than just错误
前端·javascript·vue.js
mCell4 小时前
为博客添加 RSS 订阅
前端·vitepress·rss
艾小码5 小时前
告别JS初学者噩梦:这样写流程控制和函数才叫优雅
前端·javascript
阿喵派我来抓鱼6 小时前
深入理解 AI 流式接口:从请求到响应的完整解析
react.js·ai·前端框架·vue
sdgsdgdsgc7 小时前
Next.js企业级应用开发:SSR、ISR与性能监控方案
开发语言·前端·javascript