You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在scrollTo执行完成后执行函数,确保List组件滚动步骤有序?

确保滚动完成后执行后续操作的解决方案

针对你提出的两个问题——通用的scrollTo完成后执行函数,以及react-window <List>组件的两步滚动顺序问题,我整理了几个靠谱的方案:

一、通用:检测scrollTo执行完成的方法

浏览器原生的scrollTo(包括元素的scrollTo)本身是同步执行的,但如果用了behavior: 'smooth'开启平滑滚动,滚动动画是异步的。要判断滚动动画结束,可以通过监听scroll事件,结合定时器来检测滚动是否停止:

function waitForScrollEnd(element, callback) {
  let isScrollingTimer;
  element.addEventListener('scroll', () => {
    clearTimeout(isScrollingTimer);
    // 滚动停止后100ms执行回调(可根据滚动速度调整延迟)
    isScrollingTimer = setTimeout(() => {
      callback();
      // 执行完移除监听,避免重复触发
      element.removeEventListener('scroll', arguments.callee);
    }, 100);
  }, { passive: true });
}

// 使用示例:
window.scrollTo({ top: 1000, behavior: 'smooth' });
waitForScrollEnd(window, () => {
  console.log('滚动完成,执行后续操作');
});

如果是无平滑滚动的场景(behavior: 'auto'),scrollTo是同步执行的,直接在后续行写函数即可。

二、针对react-window <List>组件的两步滚动问题

你提到onRowsRendered不可靠,确实这个回调是列表渲染新行时触发,不一定和滚动完成完全同步。这里有两个更可靠的方案:

方案1:监听列表容器滚动事件,等待滚动稳定

react-window的List会把滚动事件绑定到它的容器元素上,你可以获取容器引用,用上面的滚动停止检测逻辑:

import { List } from 'react-window';
import { useRef } from 'react';

function MyComponent() {
  const listRef = useRef(null);
  const targetIndex = 10; // 示例目标行索引

  const handleTwoStepScroll = async () => {
    // 步骤1:滚动到目标行
    listRef.current.scrollToIndex({ index: targetIndex });

    // 获取列表的滚动容器
    const scrollContainer = listRef.current.container;

    // 等待滚动停止
    await new Promise(resolve => {
      let isScrollingTimer;
      const onScroll = () => {
        clearTimeout(isScrollingTimer);
        isScrollingTimer = setTimeout(() => {
          scrollContainer.removeEventListener('scroll', onScroll);
          resolve();
        }, 100);
      };
      scrollContainer.addEventListener('scroll', onScroll, { passive: true });
    });

    // 步骤2:滚动到行内的特定元素
    document.getElementById('myId').scrollIntoView({ behavior: 'smooth' });
  };

  return (
    <div>
      <button onClick={handleTwoStepScroll}>执行两步滚动</button>
      <List
        ref={listRef}
        height={400}
        itemCount={100}
        itemSize={50}
        rowRenderer={({ index, style }) => (
          <div style={style} key={index}>
            第{index}行
            {index === targetIndex && <div id="myId">需要定位的元素</div>}
          </div>
        )}
      />
    </div>
  );
}

方案2:用Intersection Observer检测目标行是否进入视口

如果你的核心需求是确保目标行已经进入视口再执行第二步,可以用Intersection Observer监听目标行:

import { List } from 'react-window';
import { useRef } from 'react';

function MyComponent() {
  const listRef = useRef(null);
  const targetRowRef = useRef(null);
  const targetIndex = 10;

  const handleTwoStepScroll = () => {
    // 步骤1:滚动到目标行
    listRef.current.scrollToIndex({ index: targetIndex });

    // 创建观察者,当目标行90%进入视口时触发
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        // 目标行已到位,执行第二步
        document.getElementById('myId').scrollIntoView({ behavior: 'smooth' });
        observer.disconnect(); // 停止监听
      }
    }, { threshold: 0.9 });

    // 监听目标行(需确保行已渲染)
    if (targetRowRef.current) {
      observer.observe(targetRowRef.current);
    }
  };

  return (
    <div>
      <button onClick={handleTwoStepScroll}>执行两步滚动</button>
      <List
        ref={listRef}
        height={400}
        itemCount={100}
        itemSize={50}
        rowRenderer={({ index, style }) => (
          <div
            style={style}
            key={index}
            ref={index === targetIndex ? targetRowRef : null}
          >
            第{index}行
            {index === targetIndex && <div id="myId">需要定位的元素</div>}
          </div>
        )}
      />
    </div>
  );
}

补充说明

  • 方案1中的100ms延迟是经验值,你可以根据列表滚动速度调整,比如50ms或150ms,确保滚动完全停止。
  • 如果scrollToIndex没有开启平滑滚动(默认behavior: 'auto'),理论上同步执行后可直接调用第二步,但考虑到列表渲染可能有延迟,还是建议用监听方法更稳妥。

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

火山引擎 最新活动