reactjs - React useEffect 钩子中的平滑滚动错误仅在 Chrome / Chromium 上
问题描述
我有一个错误,其中useEffect
钩子阻止了scrollIntoView
仅在 chromium 浏览器上完成的呼叫。我想有些事情我不明白useEffect
。任何帮助表示赞赏
如何重现
- 用chrome打开这个
- 导航到部分:1(单击导航栏)
- 导航到第 5 节(在导航栏中单击)
- 该应用程序将开始向section:5滚动,但在section:2上被抓住——“平滑滚动”由于某种原因被“取消”
笔记
- 这只发生在“平滑”滚动行为中
- 只发生在铬(铬,边缘等) - Firefox 和 saf 很好
这是一个在不同浏览器中打开它的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;
}
解决方案
用 setTimeout 包装 scrollIntoView 调用对我有用。
例子:
setTimeout(() => {
element.scrollIntoView({
behavior: "smooth",
block: "center",
inline: "center"
});
}, 0);
推荐阅读
- java - GOOGLE MAPS API - 如何在没有 map.setMyLocationEnabled(boolean) 的情况下获取我的位置?
- java - NetBeans:JUnit没有执行测试
- javascript - 打字稿中来自apis的打字稿对象的良好做法是什么?
- python - 在使用 Keras 的 Tensorflow 2.0 中无需训练即可保存模型
- ios - Cocoapods - 具有内部静态库依赖的 Swift 框架
- variables - Lazarus 中的 var 声明
- ios - 需要在为 iOS 搜索(如 youtube)时显示预览视频缩略图
- angular - MatSort 未对数据进行排序,但已填充数据
- parsing - 避免使用 `sepBy` 解析最后一个分隔符
- javascript - JS文件被阻塞怎么办