You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

大屏环境下On Scroll Pagination失效问题求助

解决大屏滚动分页无法触发的问题

你的核心问题是:大屏设备上首次加载的12条内容刚好填满视口,页面无滚动条,导致基于scroll事件的分页逻辑无法触发,剩余6条数据无法加载。以下是具体修复方案:

1. 初始化后主动检查是否需要加载更多

在首次数据加载完成后,判断内容高度是否小于视口高度,同时还有剩余数据未加载,直接触发一次加载逻辑。

2. 修复offset更新的逻辑错误

原代码中更新myOffset和offset时存在变量混用问题,比如My Activity标签下错误使用了offset变量,需要修正。

3. (可选)添加兜底的"加载更多"按钮

为极端场景(比如内容始终无法填满视口)提供手动触发入口。

修改后的完整代码

'use client';

import React, { useCallback, useEffect, useState, useRef } from 'react';
import { message, Skeleton, Button } from 'antd';
import Image from 'next/image';
import { useRouter } from 'next/navigation';

import NoDreamIcon from '@/assests/svg/NoDreamIcon';
import { useAppSelector } from '@/hooks/reduxHooks';
import { selectpagename } from '@/redux/slice/pageTitleSlice';
import { fetchAllMyActivityDreams } from '@/service/shared-dreams/my-activity';
import { fetchAllSharedDreams } from '@/service/shared-dreams/shared-dreams';

import { IDreamDetails } from '../../../@types/common';
import testImage from '../../../public/shared-dream.png';

const SharedDreams = () => {
  const router = useRouter();
  const { activeTab } = useAppSelector(selectpagename);
  const [sharedDreamsList, setSharedDreamsList] = useState<IDreamDetails[]>([]);
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const limit = 12;
  const [offset, setOffset] = useState(0);
  const [hasMore, setHasMore] = useState<boolean>(false);
  const [myOffset, setMyOffset] = useState(0);
  const [isLoadingMore, setIsLoadingMore] = useState<boolean>(false);
  const contentRef = useRef<HTMLDivElement>(null); // 监听内容容器高度

  const getAllDreams = async () => {
    setIsLoading(true);
    const response =
      activeTab === 'My Activity'
        ? await fetchAllMyActivityDreams(limit, myOffset)
        : await fetchAllSharedDreams(limit, offset, '');
    if (response?.status === 1) {
      const newData = response?.data ?? [];
      // 修正offset更新逻辑,使用函数式更新避免闭包问题
      if (activeTab === 'My Activity') {
        setMyOffset(prev => prev + limit);
      } else {
        setOffset(prev => prev + limit);
      }
      setSharedDreamsList((prevData) => [...prevData, ...newData]);
      // 正确计算剩余数据量
      const totalLoaded = (sharedDreamsList?.length || 0) + (newData?.length || 0);
      setHasMore(response?.total > totalLoaded);
    } else {
      message.error(response?.message ?? 'Something went wrong');
    }
    setIsLoading(false);
  };

  useEffect(() => {
    getAllDreams();
  }, []);

  // 新增:检查内容高度是否不足视口,主动触发加载
  useEffect(() => {
    if (!isLoading && hasMore && contentRef.current) {
      const contentHeight = contentRef.current.offsetHeight;
      const viewportHeight = window.innerHeight;
      // 内容高度小于视口高度,且还有数据未加载,直接加载更多
      if (contentHeight < viewportHeight) {
        setIsLoadingMore(true);
        getAllDreams().finally(() => setIsLoadingMore(false));
      }
    }
  }, [isLoading, hasMore]);

  const handleScroll = useCallback(() => {
    const scrollPosition = window.scrollY + window.innerHeight;
    const docElement = document.documentElement;
    const { scrollHeight } = docElement ?? {};

    if (
      scrollHeight &&
      scrollPosition >= scrollHeight - 1 &&
      !isLoadingMore &&
      hasMore
    ) {
      setIsLoadingMore(true);
      getAllDreams().finally(() => setIsLoadingMore(false));
    }
  }, [hasMore, isLoadingMore]);

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [handleScroll]);

  const handleClick = (id: string) => {
    router.push(`/shared-dreams/${id}`);
  };

  return (
    <div className='flex h-full w-full flex-col gap-4 text-white'>
      <h1 className='text-base font-bold'>
        {activeTab === 'My Activity'
          ? 'Shared Dreams I’m in'
          : 'All Shared Dreams'}
      </h1>
      {sharedDreamsList?.length > 0 || isLoading ? (
        <>
          <div 
            ref={contentRef} // 绑定内容容器ref
            className='grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'
          >
            {sharedDreamsList?.map((dream: IDreamDetails, index: number) => {
              const {
                thumbnail,
                title,
                total_member: totalMember,
                is_accessible: isAccessible = true
              } = dream || {};
              return (
                <div
                  className={`flex flex-col gap-2 ${!isAccessible && 'cursor-not-allowed'}`}
                  onClick={() => isAccessible && handleClick(dream?.id)}
                  role='button'
                  tabIndex={0}
                  key={dream.id || index} // 用dream.id作为key更合理
                >
                  <Image
                    src={
                      thumbnail
                        ? `${thumbnail?.base_url}${thumbnail?.internal_path}${thumbnail?.image}`
                        : testImage
                    }
                    alt={`Dream ${index + 1}`}
                    className={`aspect-[4/3] h-[180px] w-full rounded-lg object-cover ${!isAccessible && 'opacity-20'}`}
                    height={122}
                    width={260}
                  />
                  <div className='flex flex-col gap-1'>
                    <p className='truncate text-base font-semibold'>{title}</p>
                    <p>
                      <span className='font-bold'>{totalMember}</span> {' '}
                      <span className='text-neutrals-300'>Members</span>
                    </p>
                  </div>
                </div>
              );
            })}
            {(isLoading || isLoadingMore) &&
              Array.from({ length: 8 }).map((_, index) => (
                <div className='flex flex-col gap-2' key={index}>
                  <Skeleton.Image
                    active
                    rootClassName='skeleton-img skeleton-wrapper'
                  />
                  <Skeleton
                    active
                    paragraph={{ rows: 1 }}
                    rootClassName='skeleton-wrapper skeleton-card'
                  />
                </div>
              ))}
          </div>
          {/* 新增:加载更多按钮作为兜底 */}
          {hasMore && !isLoading && !isLoadingMore && (
            <div className='flex justify-center mt-4'>
              <Button 
                onClick={() => {
                  setIsLoadingMore(true);
                  getAllDreams().finally(() => setIsLoadingMore(false));
                }}
                loading={isLoadingMore}
              >
                加载更多
              </Button>
            </div>
          )}
        </>
      ) : (
        !isLoading &&
        sharedDreamsList?.length === 0 && (
          <div className='flex h-full flex-col items-center justify-center self-stretch'>
            <div className='flex flex-col items-center justify-center gap-3 self-stretch text-center max-sm:h-[77vh]'>
              <NoDreamIcon />
              <div className='inline-flex flex-col items-center justify-center gap-1'>
                <p className='text-lg font-bold leading-[27px] text-[#f6f6fd]'>
                  No Dreams Joined Yet
                </p>
                <p className='self-stretch text-center text-sm font-medium leading-[21px] text-[#9090af]'>
                  {`It looks like you haven't joined any community dreams yet.`}
                </p>
              </div>
            </div>
          </div>
        )
      )}
    </div>
  );
};

export default SharedDreams;

关键修复点说明

  • 主动检查内容高度:通过useRef获取内容容器高度,在数据加载完成后对比视口高度,若内容未填满且有剩余数据,直接触发加载。
  • 修正offset更新:使用函数式更新setMyOffset(prev => prev + limit)避免闭包问题,同时确保My Activity标签下使用正确的myOffset变量。
  • 添加加载更多按钮:作为scroll事件的兜底,确保极端场景下用户仍能加载剩余数据。
  • 优化key值:用dream.id替代索引作为key,提升列表渲染性能。

内容的提问来源于stack exchange,提问作者Brijal Kansara

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 15:59:54