首页 > 解决方案 > React useEffect 钩子中的平滑滚动错误仅在 Chrome / Chromium 上

问题描述

我有一个错误,其中useEffect钩子阻止了scrollIntoView仅在 chromium 浏览器上完成的呼叫。我想有些事情我不明白useEffect。任何帮助表示赞赏

如何重现

  1. 用chrome打开这个
  2. 导航到部分:1(单击导航栏)
  3. 导航到第 5 节(在导航栏中单击)
  4. 该应用程序将开始向section:5滚动,但在section:2上被抓住——“平滑滚动”由于某种原因被“取消”

笔记

这是一个在不同浏览器中打开它的codeandbox链接很好地显示了这个问题

预期行为(火狐

预期行为(火狐)

破坏行为(

破坏行为(铬)

样式随着用户滚动而变化

样式随着用户滚动而变化

以下是源文件以及堆栈溢出指南。试图使它成为一个如此片段,但它不想工作。

应用程序.js

import { useEffect, useRef, useState } from 'react';
import './styles.css';

export default function App() {
  const generateSections = amount => {
    return [...Array(amount).keys()]
       .map(number => number + 1)
       .map(number => {
          return {
            text: `section:${number}`,
            id: `#section-${number}`,
          };
    });
  };

  const sections = generateSections(10);

  const sectionRefs = useRef([]);
  const sectionLinkRefs = useRef([]);
  const navRef = useRef();

  const [activeSectionId, setActiveSectionId] = useState(sections[0].id);

  //  update the active section on scroll (active section is used for styling and     other logic)
  useEffect(() => {
    const changeActiveSection = () => {
      // a small buffer is a bit more intuitive
      const buffer = 50;
      const amountScrolled = window.scrollY + navRef.current.clientHeight + buffer;

      // check what section is scrolled to on the page
      const haveScrolledIntoSection = section => {
        const sectionTop = sectionRefs.current[section.id].offsetTop;
        const sectionBottom = sectionRefs.current[section.id].clientHeight + sectionTop;

        return amountScrolled >= sectionTop && amountScrolled <= sectionBottom;
      };

      // set the active section to be the section scrolled to on the page
      setActiveSectionId(activeSectionId => sections.find(haveScrolledIntoSection)?.id ?? activeSectionId);
    };

    window.addEventListener('scroll', changeActiveSection);
      return () => window.removeEventListener('scroll', changeActiveSection);
    });

    //  center the active section nav link if the active section changes
    useEffect(() => {
      const activeSectionLink = sectionLinkRefs.current[activeSectionId];

      const remainingNavWidth = navRef.current.clientWidth - activeSectionLink.clientWidth;

      navRef.current.scrollLeft = activeSectionLink.offsetLeft - remainingNavWidth / 2;
    }, [activeSectionId]);

    const scrollToSection = sectionId => {
      //  here is where the bug is! 
      const scrollBehavior = 'smooth';
      // const scrollBehavior = 'auto';

      sectionRefs.current[sectionId].scrollIntoView({ behavior: scrollBehavior });
    };

    return (
      <div>
        <nav ref={navRef}>
          {sections.map(section => {
            const addSectionLinkRef = (ref, sectionId) => {
              if (sectionLinkRefs.current[sectionId] === undefined) sectionLinkRefs.current[sectionId] = ref;
            };

            return (
              <h1
                ref={ref => addSectionLinkRef(ref, section.id)}
                onClick={() => scrollToSection(section.id)}
                className={section.id === activeSectionId ? 'active' : ''}
                key={section.id}
              >
                {section.text}
              </h1>
            );
          })}
        </nav>
        <main>
          {sections.map(section => {
            const addSectionRef = (ref, sectionId) => {
              if (sectionRefs.current[sectionId] === undefined) sectionRefs.current[sectionId] = ref;
            };

          return (
            <section
              ref={ref => addSectionRef(ref, section.id)}
              className={section.id === activeSectionId ? 'active' : ''}
              key={section.id}
            >    
              {section.text}
            </section>
          );
        })}
      </main>
    </div>
  );
}

index.js

import { StrictMode } from "react";
import ReactDOM from "react-dom";

import App from "./App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <StrictMode>
    <App />
  </StrictMode>,
  rootElement
);

styles.css —基本上不相关

* {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
}

:root {
    --nav__height: 12.5vh;
    --section__height: calc(100vh - var(--nav__height) - (var(--padding) * 2));
    --padding: 10px;

    --color-primary--normal: #004d40;
    --color-primary--dark: #00251a;
    --color-primary--light: #39796b;
    --color-secondary--normal: #37474f;
    --color-secondary--dark: #102027;
    --color-secondary--light: #62727b;
}

nav {
    height: var(--nav__height);
    width: 100vw;
    background-color: var(--color-primary--normal);
    padding: var(--padding);
    overflow-x: scroll;
    scrollbar-width: none;
    -ms-overflow-style: none;

    display: flex;
    position: fixed;
    gap: var(--padding);
}

nav::-webkit-scrollbar {
    display: none;
}

h1 {
    display: grid;
    height: 100%;
    width: max-content;
    background-color: var(--color-primary--dark);
    place-content: center;
    padding: var(--padding);
    color: var(--color-secondary--normal);
}

.active {
    text-decoration: underline;
    color: white;
}

main {
    padding: var(--padding);
    padding-top: calc(var(--nav__height) + var(--padding));
    background-color: var(--color-secondary--normal);
    display: flex;
    gap: var(--padding);
    flex-direction: column;
}

section {
    height: var(--section__height);
    display: grid;
    place-content: center;
    background-color: var(--color-secondary--light);
    font-size: 2rem;
}

标签: reactjsgoogle-chromechromiumuse-effectsmooth-scrolling

解决方案


用 setTimeout 包装 scrollIntoView 调用对我有用。

例子:

setTimeout(() => {
    element.scrollIntoView({
        behavior: "smooth",
        block: "center",
        inline: "center"
    });
}, 0);

推荐阅读