React动画实现方案之 Framer Motion,让你的页面“自己”动起来
前言
相信很多前端同学都或多或少和动画打过交道。有的时候是产品想要的过度效果;有的时候是UI想要的酷炫动画。但是有没有人考虑过,是不是我们的页面上面的每一次变化,都可以像是自然而然的变化;是不是每一次用户点击所产生的交互,都可以在页面上活过来呢?
欢迎你打开了新的前端动画世界——《Framer Motion》
效果体验
这里,我在framer官网上面给大家录制了一下大概的使用效果。
在我们的常规认知中,实现这样的效果其实需要很多的css来实现,或者说需要我们进行大量的定制化逻辑编写。但是如果我们使用framer motion的话,只需要如下代码:
import { AnimatePresence, motion } from 'framer-motion';
const [selectedId, setSelectedId] = useState(null);
{items.map(item => (
<motion.div layoutId={item.id} onClick={() => setSelectedId(item.id)}>
<motion.h5>{item.subtitle}</motion.h5>
<motion.h2>{item.title}</motion.h2>
</motion.div>
))}
<AnimatePresence>
{selectedId && (
<motion.div layoutId={selectedId}>
<motion.h5>{item.subtitle}</motion.h5>
<motion.h2>{item.title}</motion.h2>
<motion.button onClick={() => setSelectedId(null)} />
</motion.div>
)}
</AnimatePresence>
从上面的实现我们可以看出,framer-motion可以说是我们在用react动效开发过程中的必备利器。那么接下来,我给大家简单介绍一些framer motion的基础用法。
快速开始
Framer Motion 需要 React 18 或更高版本。
安装
从npm安装framer-motion
npm install framer-motion
输入
安装后,您可以通过framer-motion引入Framer Motion
import { motion } from "framer-motion"
export const MyComponent = ({ isVisible }) => (
<motion.div animate={{ opacity: isVisible ? 1 : 0 }} />
)
使用方式
Framer motion的核心API是motion的组件。每个HTML和SVG标签都有对应的motion组件。
他们渲染的结果与对应的原生组件完全一致,并在其之上增加了一些动画和手势相关的props。
比如:
<motion.div />
<motion.span />
<motion.h1 />
<motion.svg />
...
示例
比如我们现在想要实现一个侧边栏效果。
节点的挂载与卸载(mount、unmount)
如果我们自己来实现的话,可能要考虑它的keyframe,它的初始状态与最终的css样式。那么如果用framer-motion来如何实现呢?
首先我们来设计一个会动的按钮Icon:
import * as React from "react";
import { motion } from "framer-motion";
const Path = props => (
<motion.path
fill="transparent"
strokeWidth="3"
stroke="hsl(0, 0%, 18%)"
strokeLinecap="round"
{...props}
/>
);
const MenuToggle = ({ toggle }) => (
<button onClick={toggle}>
<svg width="23" height="23" viewBox="0 0 23 23">
<Path
variants={{
closed: { d: "M 2 2.5 L 20 2.5" },
open: { d: "M 3 16.5 L 17 2.5" }
}}
/>
<Path
d="M 2 9.423 L 20 9.423"
variants={{
closed: { opacity: 1 },
open: { opacity: 0 }
}}
transition={{ duration: 0.1 }}
/>
<Path
variants={{
closed: { d: "M 2 16.346 L 20 16.346" },
open: { d: "M 3 2.5 L 17 16.346" }
}}
/>
</svg>
</button>
);
接下来,就由这个按钮来控制侧边栏的展示(mount)与隐藏(unmount):
import * as React from "react";
import { useRef } from "react";
import { motion, useCycle } from "framer-motion";
import { useDimensions } from "./use-dimensions";
const sidebar = {
open: (height = 1000) => ({
clipPath: `circle(${height * 2 + 200}px at 40px 40px)`,
transition: {
type: "spring",
stiffness: 20,
restDelta: 2
}
}),
closed: {
clipPath: "circle(30px at 40px 40px)",
transition: {
delay: 0.5,
type: "spring",
stiffness: 400,
damping: 40
}
}
};
export const Example = () => {
const [isOpen, toggleOpen] = useCycle(false, true);
const containerRef = useRef(null);
const { height } = useDimensions(containerRef);
return (
<motion.nav
initial={false}
animate={isOpen ? "open" : "closed"}
custom={height}
ref={containerRef}
>
<motion.div className="background" variants={sidebar} />
<MenuToggle toggle={() => toggleOpen()} />
</motion.nav>
);
};
也就是说,其实我们更多需要做的事情,从思考如何设计各元素之间的css联动与keyframe书写变成了如何按照文档写好framer-motion的配置。哪个更轻松相信大家一目了然。
列表
侧边栏一般都是带有菜单的,那么我们是不是可以让这个侧边栏也有一个逐次出现的效果呢?就像这样:
这里我们是不是已经开始肌肉记忆般的计算延迟时间,思考如何进行整体效果的分配。那么如果这里我们使用frame motion,它的实现方式应该是怎么样的呢?
首先我们先来进行单个Item的封装:
import * as React from "react";
import { motion } from "framer-motion";
const variants = {
open: {
y: 0,
opacity: 1,
transition: {
y: { stiffness: 1000, velocity: -100 }
}
},
closed: {
y: 50,
opacity: 0,
transition: {
y: { stiffness: 1000 }
}
}
};
const colors = ["#FF008C", "#D309E1", "#9C1AFF", "#7700FF", "#4400FF"];
export const MenuItem = ({ i }) => {
const style = { border: `2px solid ${colors[i]}` };
return (
<motion.li
variants={variants}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
>
<div className="icon-placeholder" style={style} />
<div className="text-placeholder" style={style} />
</motion.li>
);
};
然后我们在已封装Item的基础上,再进行整个菜单的封装:
import * as React from "react";
import { motion } from "framer-motion";
const itemIds = [0, 1, 2, 3, 4];
const variants = {
open: {
transition: { staggerChildren: 0.07, delayChildren: 0.2 }
},
closed: {
transition: { staggerChildren: 0.05, staggerDirection: -1 }
}
};
export const Navigation = () => (
<motion.ul variants={variants}>
{itemIds.map(i => (
<MenuItem i={i} key={i} />
))}
</motion.ul>
);
没错,动画!就是这么简单!
更多API
更详细、更具体的功能大家可以参考下官方的使用文档,我就不在这里一一列举了。
美中不足
其实不难看出,不论是实现的效果,还是使用方式,对于前端的同学来说framer-motion都是非常友好的工具。这一点从npm的Weekly Downloads以及github的star上面都不难看出。
但是目前也有一个问题,那就是包的体积问题。
这个包的大小对于部分的系统来说,还是不够友好。这也是很多人不选择使用它的原因。
React动画实现方案之 Framer Motion,让你的页面“自己”动起来的更多相关文章
- React动画组件——React-Transitio-group动画实现
React动画组件--React-Transitio-group动画实现 安装 项目目录下使用命令行 yarn add react-transition-group 安装组件.在需要使用动画的页面加入 ...
- 借鉴redux,实现一个react状态管理方案
react状态管理方案有很多,其中最简单的最常用的是redux. redux实现 redux做状态管理,是利用reducer和action实现的state的更新. 如果想要用redux,需要几个步骤 ...
- 【React自制全家桶】八、React动画以及react-transition-group动画库的使用
React动画通常有三种方法实现从易到难为: 1.transition(CSS3自带) 2.animation(CSS3自带) 3.react-transition-group动画库(需要引入插件) ...
- PageSlider中CSS3动画在除首屏之外先加载页面后执行动画的问题
PageSlider中CSS3动画在除首屏之外先加载页面后执行动画的问题,PageSlider中加入CSS3动画的话,默认只有首屏是从无到有执行动画,其他屏都是显示下页面再执行动画 这就造成其他屏的动 ...
- react动画难写?试试react版transformjs
简介 transformjs在非react领域用得风生水起,那么react技术栈的同学能用上吗?答案是可以的.junexie童鞋已经造了个react版本. 动画实现方式 传统 web 动画的两种方式: ...
- Unity3D 中 Generic 动画导入设置和 Root Motion 之间的关系
2条评论 Unity3D 的 Mecanim 动画系统可以直接复用 3DS MAX 中制作的动画文件中的位移,这个就是通过 applyRootMotion 来达成的,我们只需要在使用 Animator ...
- vue和react动画区别
触发动画 vue触发动画是 v-show,v-if ,动态组件或者组件的根节点 react 是CSSTransition上的属性 in 是true 或false触发动画
- React 动画 Animation
文章源自: https://facebook.github.io/react/docs/animation.html ReactCSSTransitionGroup 基于 ReactTransitio ...
- WebStorm里面配置运行React Native的方案
以前开发react native项目总是需要打开WebStorm编写代码,Xcode跑项目.显得有点多余. 今天教大家如何直接使用WebStorm这个IDE直接完成编码+运行项目工作.这样就可以不用打 ...
随机推荐
- java中为什么只存在值传递(以传入自定义引用类型为例)
java中只有值传递 为什么这么说?两个例子: public class Student { int sage = 20; String sname = "云胡不归"; publi ...
- 从0搭建Vue3组件库:button组件
button组件几乎是每个组件库都有的:其实实现一个button组件是很简单的.本篇文章将带你一步一步的实现一个button组件.如果你想了解完整的组件库搭建,你可以先看使用Vite和TypeScri ...
- 熔断器-Hystrix。。。之降级方法
与Feign的Fallback降级方法不同,这个hystrix降级方法是写在被调用方的 需要依赖: <dependency> <groupId>org.springframew ...
- feign远程调用出错
如果你传递的参数,比较复杂时,默认会采用POST的请求方式. 传递单个参数时,推荐使用@PathVariable,如果传递的单个参数比较多,这里也可以采用@RequestParam,Feign接口中不 ...
- Postman中的断言
Postman设置断言 一.断言的定义 1.什么是断言? 一般一个完整的接口测试,包括:请求->获取响应正文->断言,请求和获取响应正文很常见.断言一般是对请求的响应结果做操作,判断预期结 ...
- 基于Anacoda搭建虚拟环境cudnn6.0+cuda8.0+python3.6+tensorflow-gpu1.4.0
!一定要查准cudnn,cuda,tensorflow-gpu对应的版本号再进行安装,且本文一切安装均在虚拟环境中完成. 下文以笔者自己电脑为例,展开安装教程阐述(省略anaconda安装教程): 1 ...
- Jenkins+SpringCloud(多模块)+Vue项目详细配置
一.Jenkins安装及所需插件安装 安装过程略. 我这用到工具包括JDK.Git.Maven.NodeJS:可以选择自行在服务器安装,也可以通过Jenkins自动安装,位置在系统管理 >全局工 ...
- CF-1675D. Vertical Paths
题意:每次可以选择一条路径,要求这条路径中每个点都是上一个点的子节点,求最少需要几条路径将所有点走完 思路:将每个点有没有子节点判断出来,因为只有没有子节点的点需要新增一条路,所以需要路径的最小数目就 ...
- Html飞机大战(三):定义状态
好家伙, 1.为飞机大战定义状态 1.开始 START 有一个飞机大战LOGO &天空 2.开始时 STRATING 有一个飞机加载的界面&天空 3.运行时 RUNNING 我方飞机& ...
- 详谈 MySQL 8.0 原子 DDL 原理
柯煜昌 青云科技研发顾问级工程师 目前从事 RadonDB 容器化研发,华中科技大学研究生毕业,有多年的数据库内核开发经验. 文章字数 3800+,阅读时间 15 分钟 背景 MySQL 5.7 的字 ...