在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;
相关推荐
正小安1 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch3 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光3 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   3 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   3 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web3 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常3 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇4 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr4 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho5 小时前
【TypeScript】知识点梳理(三)
前端·typescript