使用 Framer Motion 实现 React 滚动动画

如果动画执行得当,它们可以提供强大的用户体验。然而,尝试使用 CSS 创建出令人惊叹的动画可能会令人伤脑筋。许多动画库可以简化这一过程,但大多数动画库不够全面,无法构建复杂的动画。

在本文中,我们将演示如何使用 Framer Motion 创建滚动动画,这是一个动画库,无需成为 CSS 专家也可创作出精美的动画。

前置条件

若要充分理解本教程,应具备以下条件:

  • 熟悉 React 及其概念(包括 Hooks)
  • CSS 属性(如不透明度、过渡和缩放)相关知识

无需具备任何 Framer Motion 的先验知识。本文将介绍该库的基本概念。

让我们先了解一下 Framer Motion 和 Intersection Observer 功能的背景知识。

什么是 Framer Motion?

Framer Motion 是一个动画库,在 React 中可以声明性创建动画。它提供可供生产的动画和 API,以帮助简化将动画集成到应用程序中的过程。

一些 React 动画库,如 react-transition-group 和 transition-hook,使用手动配置的 CSS 对元素进行动画处理。Framer Motion 采用了一种不同的方法,即使用预配置的样式在引擎盖下对元素进行动画处理。

motionuseAnimation 是 Framer Motion 用于触发和控制这些样式的两个功能。该 motion 函数用于创建运动组件,这些组件是 Framer Motion 的构建块。

通过 motion 为 HTML 或 SVG 元素添加前缀,该元素会自动成为组件:

html 复制代码
<motion.h1>Motion Component</motion.h1>

组件可以访问多个属性,包括 animate 属性。 animate 接收具有要进行动画处理的组件的已定义属性的对象。当组件装载时,对象中定义的属性将进行动画处理。

在 React 中使用 Framer Motion 创建滚动动画

使用 Framer Motion 在 React 应用程序中创建滚动动画有两种主要方法:scroll-triggerescroll-linked 动画。

scroll-triggered 动画包括检测元素在视口中何时可见并激活与之关联的动画。我们可以使用 Intersection Observer API 或 Framer Motion 的 whileInView 钩子来实现这一点。

另一方面,scroll-linked 动画涉及跟踪页面的滚动位置并使用它来控制元素的动画进度。

在本教程中,我们将探讨如何使用这两种方法。让我们开始吧。

创建滚动触发的动画

在引入钩子之前 whileInView ,Framer Motion 没有在元素进入或离开视口时观察元素的功能。因此,元素在装载到 DOM 后会立即动画化。

为了解决这个问题,开发人员将使用原生 Javascript API,例如 Intersection Observer API,它可以防止元素在定义的边界内或视口内进行动画处理。

使用 Intersection Observer API

Intersection Observer API 是一个 JavaScript API,它提供了一种异步观察目标元素与顶级文档视口相交的变化的方法。它注册一个回调函数,每当我们想要监视的元素进入或退出另一个元素或视口时,就会执行该函数。

在本文中,我们将使用 react-intersection-observer 库,这是 Intersection Observer API 的 React 实现。此库旨在使用较少的样板代码处理上述功能。它提供了 Hooks 和渲染,可以轻松跟踪视口上元素的滚动位置。

react-intersection-observer 是一个相对较小的包,因此无需担心它可能会给您的项目增加开销:

现在,让我们设置一个简单的 React 项目并安装必要的依赖项。

设置我们的 React 项目

我们将通过安装 React 来开始我们的项目设置:

bash 复制代码
npm create vite@latest my-app -- --template react

然后,我们将安装 Framer Motion 和 react-intersection-observer

bash 复制代码
npm i react-intersection-observer framer-motion

接下来,我们将使用 Framer Motion 和 react-intersection-observer 库设置一个演示 React 应用程序。

创建演示 React 应用程序

首先,我们将创建一个 box 组件,它可以是卡片、模态或其他任何东西。我们将这个 box 组件导入到主组件 . App.js 当这个组件进入视口时,我们将对其进行动画处理:

js 复制代码
/*Box component*/
const Box = () => {
  return (
    <div className="box">
      <h1>Box</h1>
    </div>
  );
};


/*Main component*/
export default function App() {
  return (
    <div className="App">
      <Box /> /*imported Box component*/ /*imported Box component*/
    </div>
  );
}

接下来,我们将从之前安装的库中导入创建动画所需的所有其他内容:

js 复制代码
motion and useAnimation Hooks from Framer Motion
useEffect Hook from React
useInView Hook from react-intersection-observer
import { motion, useAnimation } from "framer-motion";

import { useInView } from "react-intersection-observer";

import { useEffect } from "react";

这些是我们需要对 box 组件进行动画处理的基本 Hooks。在本教程的后面部分,将了解每个 Hook 的工作原理。

在我们的组件内部是一个 div 元素 box ,其 className 为 。为了对 box 元素进行动画处理,我们需要使其成为运动组件。我们通过在元素前面添加 motion 前缀来做到这一点:

js 复制代码
const Box = () => {
  return (
    <motion.div className="box">
      <h1>Box</h1>
    </motion.div>
  );
};

我们可以通过向 motion 组件添加 initialanimate 属性。

js 复制代码
<motion.div 
animate={{ x: 100 }} 
initial={{x: 0}} 
className="box"
></motion.div>

对于更复杂的动画,Framer Motion 提供了变体。接下来让我们看看如何使用此功能。

使用变体制作动画

变体是一组预定义的对象,让我们以声明方式定义动画的外观。它们具有我们可以在动画组件中引用的标签来创建动画。

下面是 variant 对象的示例:

js 复制代码
const exampleVariant = {
  visible: { opacity: 1 },
  hidden: { opacity: 0 },
}

在这个变体对象中, exampleVariant 有两个属性: visiblehidden 。这两个属性都传递一个对象作为值。当元素 是 visible 时,我们希望 opacity 1 是 ;当它是 hidden 时,我们希望它是 0

上面的变体对象可以在动画组件中引用,如下所示:

js 复制代码
<motion.div variants={exampleVariant} />

接下来,我们将创建一个变体,并将其作为道具传递给我们的动画组件:

js 复制代码
const boxVariant = {
  visible: { opacity: 1, scale: 2 },
  hidden: { opacity: 0, scale: 0 },
}

为了在我们的动画组件中引用这个变体对象,我们将向动画组件添加一个 variants 配置:

js 复制代码
<motion.div
  variants={boxVariant}
  className="box"
/>

现在,我们的动画组件没有发生任何事情------它可以访问对象,但它不知道如何处理它。动画组件需要一种方法来知道何时开始和结束变体对象中定义的动画。

为此,我们将 initial and animate 配置传递给 motion 组件:

js 复制代码
<motion.div
  variants={boxVariant}
  className="box"
  initial="..."
  animate="..."
/>

在上面的代码中, initial 配置定义了 motion 组件在挂载之前的行为,而 animate 配置用于定义挂载时的行为。

现在,我们将通过将组件的 设置为装载 0 前和安装 1 时,为组件 opacity 添加淡入动画效果。该 transition 属性具有 duration 一个值,该值指示动画的持续时间:

js 复制代码
<motion.div
  className="box"
  initial={{ opacity: 0, transition:{duration: 1}}}
  animate={{opacity: 1}}
/>

我们可以通过引用我们之前创建的 variant 对象中的 hiddenvisible 属性来动态设置它们:

js 复制代码
const boxVariant = {
  visible: { opacity: 1, scale: 2 },
  hidden: { opacity: 0, scale: 0 },
}
...
<motion.div
  variants={boxVariant}
  initial="hidden"
  animate="visible"
  className="box"
/>

运动组件将继承变体对象 hiddenvisible 属性的值,并相应地进行动画处理:

现在我们已经有了组件的工作动画,下一步是使用 react-intersection-observer 库访问 Intersection Observer API 并在组件处于视图中时触发动画。

使用 useInViewuseAnimation Hooks 添加滚动显示动画

如前所述,Framer Motion 在元素加载时对元素进行动画处理,因此在元素处于视图中时,我们需要能够控制它们何时安装和卸载。

useAnimation Hook 提供了帮助程序方法,让我们可以控制动画发生的顺序。例如,我们可以使用control.startcontrol.stop 方法来手动启动和停止动画。

useInView 是一个 react-intersection-observer Hook,它允许我们跟踪组件在视口中何时可见。这个 Hook 让我们可以访问 一个 ref ,我们可以将其传递给我们想要观看的组件,它告诉我们组件是否在视口中。

例如,如果我们将组件作为参数传递给 ref 。当组件滚动到视口和离开视口,在控制台,将显示truefalse:

现在,我们将使用 useAnimation Hook 在运动组件进入视口时触发动画。

首先,我们将从 useInView Hook 中引用 ref and inView ,并分配给 useAnimation 一个变量:

js 复制代码
const control = useAnimation()
const [ref, inView] = useInView()

接下来,我们将 ref 添加到我们的 motion 组件中,并将变量 control 作为值传递给 animate

js 复制代码
<motion.div
  ref={ref}
  variants={boxVariant}
  initial="hidden"
  animate={control}
  className="box"
/>

最后,我们将创建一个 useEffect Hook,以便在我们看到的组件出现在视野中时调用该 control.start 方法。在这个 Hook 中,我们将 controlinView 变量作为依赖项传递:

js 复制代码
useEffect(() => {
    if (inView) {
      control.start("visible");
    } 
  }, [control, inView]);

useEffect 回调函数中,我们使用 if 语句执行条件检查,以检查运动组件是否在视图中。

如果条件是 trueuseEffect 将调用 control.start 带有 "visible" 值的方法。这将触发动画组件上的 animate 属性并启动动画。

现在,如果我们上下滚动视口,当框组件的滚动位置进入视口时,它们将进行动画处理:

请注意,框组件仅在第一次进入视口时才进行动画处理。

我们可以通过在 useEffect 回调函数中向 if 语句添加一个 else 块来使它们每次出现在视图中时出现动画。在这个 else 块中,我们将再次调用该 control.start 方法,但这次将一个 "hidden" 值传递给它:

js 复制代码
else {
      control.start("hidden");
    }

现在,如果我们上下滚动视口,则每次滚动位置进入视口时,盒子组件都会进行动画处理:

以下是在 React with Framer Motion 中创建滚动触发动画的最终代码:

js 复制代码
import { motion, useAnimation } from "framer-motion";
import { useInView } from "react-intersection-observer";
import { useEffect } from "react";

const boxVariant = {
  visible: { opacity: 1, scale: 1, transition: { duration: 0.5 } },
  hidden: { opacity: 0, scale: 0 }
};

const Box = ({ num }) => {

  const control = useAnimation();
  const [ref, inView] = useInView();

  useEffect(() => {
    if (inView) {
      control.start("visible");
    } else {
      control.start("hidden");
    }
  }, [control, inView]);

  return (
    <motion.div
      className="box"
      ref={ref}
      variants={boxVariant}
      initial="hidden"
      animate={control}
    >
      <h1>Box {num} </h1>
    </motion.div>
  );
};

export default function App() {
  return (
    <div className="App">
      <Box num={1} />
      <Box num={2} />
      <Box num={3} />
    </div>
  );
}

使用 Framer Motion whileInView prop

Intersection Observer API 专为检测复杂的交叉行为而定制,可让你对交叉变化进行精细控制。但是,假设你需要一种简化的、特定于 React 的方法来处理滚动触发的动画。

在这种情况下,Framer Motion 提供了一个为 motion 组件调用 whileInView 的属性,我们可以使用它来触发滚动触发的动画。该 whileInView 定义了一组属性或变体标签,这些属性或变体标签在元素处于视图中时对其进行动画处理:

js 复制代码
<motion.div whileInView={{ scale: 1 }} initial={{ scale: 0 }}>...<motion.div/>

在此示例中,组件的比例设置为在装载之前设置为0,加载完成以后设置成1

js 复制代码
  const variant = {
    visible: { scale: 1 },
    hidden: { scale: 0 },
  };

  <motion.div
  variant="variant"
  initial="hidden" 
  whileInView="visible"
  >...<motion.div/>

如果我们在用例中实现,例如在博客网站上对图像进行动画处理,结果将如下所示:

可以在 Code Sandbox 上找到此演示并与之交互。

正如你所看到的,该 whileInView 道具是一种更紧凑的方法,用于通过 Framer Motion 实现滚动触发功能。相比之下,交叉点观察器需要额外的步骤来设置。

whileInView 的关联属性提供了一定更简便的方法:

  • viewport :定义如何检测视口的对象。它接受一组属性,我们可以使用这些属性来定义 whileInView 道具被触发的次数,选择要跟踪的视口,并在检测元素的可见性时向视口添加边距
  • onViewportEnter :元素进入视口时触发的回调。它在后台使用 Intersection Observer API 的 intersectionObserverEntry 接口来处理此功能
  • onViewportLeave :与回调相反。 onViewportEnter 当元素离开视口时触发

可以在官方 Framer Motion 文档中了解有关这些属性的更多信息

带有 useScroll 钩子的滚动链接动画

滚动链接动画是 Web 上最常见的滚动动画类型。这些动画由用户在网页上的滚动行为触发。将动画与特定的滚动位置同步,可在用户滚动浏览 Web 应用的内容时创建引人入胜的用户体验。

滚动链接动画的一些常见用例包括视差效果、滚动进度指示器等。

Framer Motion 提供了一个 useScroll 钩子,可以轻松创建滚动链接的动画。 useScroll 钩子返回四个运动值,我们可以根据元素的滚动状态使用动画元素:

  • scrollXscrollY,表示页面或元素在 xy 轴上的绝对滚动位置
  • scrollXProgressscrollYProgress ,表示相对于定义的偏移量的滚动进度

偏移量是介于 和 1 之间的 0 数组,其中 0 表示元素在视口之外, 1 表示元素在视口内。

我们可以使用偏移量来计算目标元素和视口相交的点。也可以使用一组字符串来定义它们: start 、 和 end ,它们分别表示 00.5 center1

让我们看一个示例,说明如何使用运动值来创建滚动指示器。我们只需要将 scrollYProgress 值传递给进度条元素的 styleX style 属性,如下面的代码示例所示:

js 复制代码
const { scrollYProgress } = useScroll();

<motion.div className="progress-bar" style={{ scaleX: scrollYProgress }} />

效果如下:

可以使用 CodeSandbox 与演示进行交互。

useScroll 值可以与其他钩子(如 useTransformuseSpring )结合使用,以组成复杂的动画,如下例所示:

js 复制代码
function Images({ text, url }) {
  const ref = useRef(null);
  const { scrollYProgress } = useScroll({ target: ref });
  const y = useTransform(scrollYProgress, [0, 1], [-300, 350]);
  return (
    <section>
      <div ref={ref}>
        <img src={url} alt={text} />
      </div>
      <motion.h2 style={{ y }}>{text}</motion.h2>
    </section>
  );
}

在此示例中,我们使用 useTransform hook 根据页面每个部分中图像的滚动位置创建文本的视差效果:

要检查这是如何完成的,可以在CodeSandbox上打开演示并检查代码。

将 Framer Motion 与三种替代方案进行比较

为 Web 应用程序创建动画时,决定使用哪个库可能具有挑战性。尽管有许多动画库可供选择,但这些库中只有少数提供内置功能来对滚动元素进行动画处理。

那么问题就变成了为什么你应该使用Framer Motion而不是这些替代方案。有许多因素需要考虑,但一般来说,Framer Motion 在简单性和复杂性之间取得了平衡,对于希望实现简单动画并实现缩放的开发人员来说,这是正确的选择。

为了更清楚地了解这种平衡,让我们将 Framer Motion 与三个提供滚动动画功能的库进行比较:GSAP、React Animate on Scroll 和 React Reveal。

Framer Motion 与 GSAP 对比

GSAP 是一个功能强大的动画库,它为创建动画提供了强大的工具集。它的 ScrollTrigger API 专注于基于滚动的动画。您可以使用它来创建高级滚动触发动画,并精确控制滚动触发器和时间线。

与 Framer Motion 相比,GSAP 的 ScrollTrigger 更加灵活和强大。但是,它的学习曲线更陡峭,设置起来需要更多努力,而 Framer Motion 则更简单。

Framer Motion 与 React Animate on Scroll

React Animate On Scroll是一个轻量级动画库,它提供了一个简单的界面,用于在元素进入视口时触发动画。

与其他库(如 Framer Motion 和 GSAP)相比,该库在动画自定义方面不太灵活。但是,它确实简化了基于滚动位置对元素进行动画处理的过程。

Framer Motion 与 React Reveal

与 React Animate On Scroll 类似,React Reveal 库也提供了一个简单的界面,用于根据滚动位置对元素进行动画处理。然而,React Reveal 不允许用户定义他们的动画,而是提供了一组预定义的动画,很容易地应用于元素中。

这种简单性使 React Reveal 成为开发人员的不错选择,他们希望以一种快速简便的方式将动画添加到他们的 React 应用程序中。然而,这也意味着 React Reveal 的灵活性不如 Framer Motion 和 GSAP 等综合动画库。

原文: Implementing React scroll animations with Framer Motion - LogRocket Blog

相关推荐
学习使我快乐013 小时前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio19953 小时前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
黄尚圈圈4 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水5 小时前
简洁之道 - React Hook Form
前端
正小安7 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch9 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光9 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   9 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   9 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d