发布于 2026-01-06 1 阅读
0

使用 React 和 Framer Motion 实现平滑滚动

使用 React 和 Framer Motion 实现平滑滚动

使用带凹槽的鼠标滚轮滚动浏览网页时,页面通常会跳动,难以操作。

平滑滚动或弹簧滚动为传统的鼠标滚动增添了动画效果。

如果您从未体验过流畅滚动,请尝试在线演示

概念

考虑一下视口窗口。当一堆 HTML 元素组合起来的高度超过窗口高度时,就会发生溢出,向下滚动时就可以访问到这些溢出的内容。

带有内容的窗口

要覆盖默认滚动条,我们需要将内容包裹在一个我们可以控制的固定元素中。

受控内容包装器

最后,我们将创建一个不可见的间隔元素(div),其高度等于内容的滚动高度。这将触发浏览器的默认滚动条。

隐形高度垫片

设置

在Replit上创建一个React Typescript Repl即可开始。

安装Framer Motionnpm install framer-motion

组件

<SmoothScroll/>组件将包裹所有我们想要融入平滑滚动效果的 HTML 元素。

export default function SmoothScroll({
  children
}: {
  children: React.ReactNode;
}) {
  return <></>;
}
Enter fullscreen mode Exit fullscreen mode

滚动和弹簧值

从以下位置导入钩子framer-motion

import { useScroll, useSpring, useTransform } from 'framer-motion';
Enter fullscreen mode Exit fullscreen mode

在组件中,从钩子中SmoothScroll解构scrollYProgressuseScroll

const { scrollYProgress } = useScroll();
Enter fullscreen mode Exit fullscreen mode

接下来,使用useSpring钩子函数将平滑效果应用于该scrollYProgress值。

const smoothProgress = useSpring(scrollYProgress, { mass: 0.1 })
Enter fullscreen mode Exit fullscreen mode

内容和间隔

将该组件添加motion到现有的导入中framer-motion

- import { useScroll, useSpring } from 'framer-motion';
+ import { motion, useScroll, useSpring } from 'framer-motion';
Enter fullscreen mode Exit fullscreen mode

跳至组件的return语句部分。返回一个空<div>元素和一个将该属性<motion.div>渲染children为子元素的元素。

return <>
  <div style={{ height: contentHeight }} />

  <motion.div
    className="scrollBody"
  >
    {children}
  </motion.div>
</>
Enter fullscreen mode Exit fullscreen mode

通过hook 和state创建一个contentRefReact 引用useRefcontentHeightuseState

import { useState, useRef } from 'react';

...

const contentRef = useRef<HTMLDivElement>(null);
const [contentHeight, setContentHeight] = useState(0);
Enter fullscreen mode Exit fullscreen mode

将内容包装器分配给contentRef.

<motion.div
  className="scrollBody"
  ref={contentRef}
>
  {children}
</motion.div>
Enter fullscreen mode Exit fullscreen mode

使用style道具使垫片的高度为contentHeight

<div style={{ height: contentHeight }} />
Enter fullscreen mode Exit fullscreen mode

调整大小处理程序

当窗口大小改变时,内容的高度很可能会发生变化。由于该contentHeight值是一个状态,因此我们需要在每次窗口大小改变以及contentRef引用更新时更新它。

useEffect首先从以下位置导入钩子react

- import { useState, useRef } from 'react';
+ import { useEffect, useState, useRef } from 'react';
Enter fullscreen mode Exit fullscreen mode

在 useEffect 钩子中,创建并调用一个处理程序来设置contentHeightcontentRef.current.scrollHeight

添加contentRef到依赖项数组中。

useEffect(() => {
  const handleResize = () => {
    if (contentRef.current) {
      setContentHeight(contentRef.current.scrollHeight)
    }
  }

  handleResize();
}, [contentRef]);
Enter fullscreen mode Exit fullscreen mode

最后,在 useEffect 钩子中添加resize事件监听器,并返回一个处置函数。window

useEffect(() => {
  ...

  window.addEventListener("resize", handleResize);

  return () => {
    window.removeEventListener("resize", handleResize);
  }
}, [contentRef, children]);
Enter fullscreen mode Exit fullscreen mode

把所有内容整合起来

useTransform从以下位置导入钩子framer-motion

- import { motion, useScroll, useSpring } from 'framer-motion';
+ import { useTransform, motion, useScroll, useSpring } from 'framer-motion';
Enter fullscreen mode Exit fullscreen mode

创建一个常量y,并将其设置为useTransform钩子的smoothProgress初始值。

const y = useTransform(smoothProgress, value => {
  return value;
});
Enter fullscreen mode Exit fullscreen mode

为了可视化如何计算滚动,我们将从内容容器的滚动高度中减去视口高度,然后乘以 -1,最后乘以value

滚动可视化

const y = useTransform(smoothProgress, value => {
  return value * -(contentHeight - window.innerHeight);
});
Enter fullscreen mode Exit fullscreen mode

y在内容容器中使用转换后的值。

<motion.div
  className="scrollBody"
  style={{ y }}
  ref={contentRef}
>
  {children}
</motion.div>
Enter fullscreen mode Exit fullscreen mode

款式

我已经帮你搞定了 CSS,所以你不用再费心了。你只需要复制粘贴最基本的 CSS 代码,滚动组件就能正常运行了。

.scrollBody {
  width: 100vw;
  position: fixed;
  top: 0;

  display: flex;
  flex-direction: column;
}
Enter fullscreen mode Exit fullscreen mode

完成🎉

就是这样!你只需要在组件中添加一些HTML元素即可<SmoothScroll />

<h1>Lorem Ipsum</h1>代码库中没有什么比五十个 s 更好了。


感谢阅读。如果您对本文有任何反馈或建议,欢迎在评论区留言。

让我们联系吧🤝

文章来源:https://dev.to/ironcladdev/smooth-scrolling-with-react-framer-motion-dih