在React中实现好看的动画Framer Motion(案例:跨DOM元素平滑过渡)

前言

介绍

Framer Motion 是一个适用于 React 网页开发的动画库,它可以让开发者轻松地在他们的项目中添加复杂和高性能的动画效果。该库提供了一整套针对 React 组件的动画、过渡和手势处理功能,使得通过声明式的 API 来创建动画变得简单直观。

接下来我将带你使用 Framer Motion 实现一个选中样式平滑过渡的案例。

案例

基本环境

本案例基于 Vite + React + TypeScript + TailwindCSS 搭建。TailwindCSS 不是必须的,我主要是为了不写 CSS 样式,若想实现和本案例相同的效果,可以安装一下 TailwindCSS。

基本页面

基本页面比较简单,主要就是渲染不同的导航链接,当点击时设置选中的导航,在选中的导航内渲染一个 span 用于显示选中样式,App.tsx

tsx 复制代码
import { useState } from "react";

const navs = [
  {
    name: "Home",
    href: "#home",
  },
  {
    name: "About",
    href: "#about",
  },
  {
    name: "Contact",
    href: "#contact",
  },
];

function App() {
  const [activeNav, setActiveNav] = useState("#home");

  return (
    <div className="mx-auto grid h-full max-w-2xl place-items-center space-y-4 py-4">
      <div className="space-y-8">
        <ul className="flex gap-2">
          {navs.map((nav, index) => (
            <li key={index} className="relative">
              <a
                href={nav.href}
                className="block rounded-md px-4 py-2 text-sm font-medium"
                onClick={() => {
                  setActiveNav(nav.href);
                }}
              >
                {nav.name}

                {nav.href === activeNav && (
                  <span className="absolute inset-0 -z-10 rounded-full bg-gray-100"></span>
                )}
              </a>
            </li>
          ))}
        </ul>

        <ul className="flex flex-col gap-2">
          {navs.map((nav, index) => (
            <li key={index} className="relative">
              <a
                href={nav.href}
                className="block rounded-md px-4 py-1 text-sm font-medium hover:bg-gray-100"
                onClick={() => {
                  setActiveNav(nav.href);
                }}
              >
                {nav.name}

                {nav.href === activeNav && (
                  <span className="absolute inset-y-0 left-0 w-1 rounded-full bg-gray-200"></span>
                )}
              </a>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

export default App;
  1. navs 数组:定义了导航链接的数据。
  2. activeNav 状态:存储当前选中的导航项。
  3. 导航列表:通过<ul>元素创建垂直和水平的导航条目。
  4. 点击事件处理:更新activeNav状态以高亮显示当前选中导航。
  5. 条件渲染:根据activeNav状态在选中的导航下显示指示标识。

此时已经完成了案例的基本样式,通过点击发现此时还没有任何动画,选中之间的过渡比较生硬。

npm 安装 Framer Motion:

bash 复制代码
npm install framer-motion

使用 pnpm:

bash 复制代码
pnpm add framer-motion

修改 app.tsx

tsx 复制代码
import { motion } from "framer-motion";

<motion.span
	className="absolute inset-0 -z-10 rounded-full bg-gray-100"
	layoutId="activeNav"
	transition={{
		type: "spring",
		stiffness: 380,
		damping: 30,
	}}
></motion.span>

<motion.span
	className="absolute inset-y-0 left-0 w-1 rounded-full bg-gray-200"
	layoutId="activeNav2"
	transition={{
		type: "spring",
		stiffness: 380,
		damping: 30,
	}}
></motion.span>

引入 framer-motion,然后把设置选中样式的 span 改为 motion.span,添加 layoutIdtransition 属性,动画设置为 spring 弹簧动画。

layoutId 用来创建动画以平滑地过渡共享元素的布局变化。
transition 属性用来定义动画的持续时间、延迟、缓动函数(如线性、弹性)、循环次数等特性。
stiffness 这个参数影响弹簧的硬度。一个更高的刚度值意味着弹簧更硬,它会更快地回到它的起始位置,导致动画更快地开始并且拥有一个更小的震荡周期。简言之,刚度越大,弹簧越"紧",动画运动越快。
damping 这个参数影响弹簧动画的阻尼效果,也就是减震效果。一个更高的阻尼值会导致弹簧运动时震荡得更少,从而更快地稳定下来。如果阻尼值设置得很低,动画会在达到静止状态之前在结束位置附近做较多的震荡。

完整代码

tsx 复制代码
import { useState } from "react";

import { motion } from "framer-motion";

const navs = [
  {
    name: "Home",
    href: "#home",
  },
  {
    name: "About",
    href: "#about",
  },
  {
    name: "Contact",
    href: "#contact",
  },
];

function App() {
  const [activeNav, setActiveNav] = useState("#home");

  return (
    <div className="mx-auto grid h-full max-w-2xl place-items-center space-y-4 py-4">
      <div className="space-y-8">
        <ul className="flex gap-2">
          {navs.map((nav, index) => (
            <li key={index} className="relative">
              <a
                href={nav.href}
                className="block rounded-md px-4 py-2 text-sm font-medium"
                onClick={() => {
                  setActiveNav(nav.href);
                }}
              >
                {nav.name}

                {nav.href === activeNav && (
                  <motion.span
                    className="absolute inset-0 -z-10 rounded-full bg-gray-100"
                    layoutId="activeNav"
                    transition={{
                      type: "spring",
                      stiffness: 380,
                      damping: 30,
                    }}
                  ></motion.span>
                )}
              </a>
            </li>
          ))}
        </ul>

        <ul className="flex flex-col gap-2">
          {navs.map((nav, index) => (
            <li key={index} className="relative">
              <a
                href={nav.href}
                className="block rounded-md px-4 py-1 text-sm font-medium hover:bg-gray-100"
                onClick={() => {
                  setActiveNav(nav.href);
                }}
              >
                {nav.name}

                {nav.href === activeNav && (
                  <motion.span
                    className="absolute inset-y-0 left-0 w-1 rounded-full bg-gray-200"
                    layoutId="activeNav2"
                    transition={{
                      type: "spring",
                      stiffness: 380,
                      damping: 30,
                    }}
                  ></motion.span>
                )}
              </a>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

export default App;
相关推荐
蟾宫曲3 小时前
在 Vue3 项目中实现计时器组件的使用(Vite+Vue3+Node+npm+Element-plus,附测试代码)
前端·npm·vue3·vite·element-plus·计时器
秋雨凉人心3 小时前
简单发布一个npm包
前端·javascript·webpack·npm·node.js
liuxin334455663 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
qq13267029403 小时前
运行Zr.Admin项目(前端)
前端·vue2·zradmin前端·zradmin vue·运行zradmin·vue2版本zradmin
魏时烟4 小时前
css文字折行以及双端对齐实现方式
前端·css
哥谭居民00015 小时前
将一个组件的propName属性与父组件中的variable变量进行双向绑定的vue3(组件传值)
javascript·vue.js·typescript·npm·node.js·css3
踢足球的,程序猿5 小时前
Android native+html5的混合开发
javascript
2401_882726485 小时前
低代码配置式组态软件-BY组态
前端·物联网·低代码·前端框架·编辑器·web
web130933203986 小时前
ctfshow-web入门-文件包含(web82-web86)条件竞争实现session会话文件包含
前端·github
胡西风_foxww6 小时前
【ES6复习笔记】迭代器(10)
前端·笔记·迭代器·es6·iterator