React动画实现一直是开发中的难点。本文将深入浅出地介绍react-spring,并探讨一些实际应用案例。虽然react-spring并非React唯一的动画库,但它却是最受欢迎和功能强大的库之一。
本文将使用最新的9.x版本(撰写本文时为候选发布版本)。如果在您阅读本文时尚未正式发布,请使用react-spring@next
安装。根据我的经验和主要维护者的说法,该代码非常稳定。我唯一遇到的问题是在与并发模式一起使用时出现的小错误,可在GitHub仓库中跟踪。
react-spring速览
在深入探讨实际应用案例之前,让我们先快速了解一下springs、高度动画和过渡动画。本节末尾将提供一个可运行的演示,因此不必担心过程中可能会遇到的困惑。
springs
让我们考虑动画的经典“Hello world”:内容的淡入淡出。让我们停下来思考一下,如何在没有任何动画的情况下切换显示和隐藏。它看起来像这样:
export default function App() { const [showing, setShowing] = useState(false); return ( <div> <div style="{{" opacity: showing :> This content will fade in and fade out </div> <button onclick="{()"> setShowing(val => !val)}>Toggle</button> <hr> </div> ); }
简单,但乏味。我们如何动画化不透明度的变化?如果我们可以根据状态声明性地设置所需的不透明度,就像上面那样,但让这些值平滑地动画化,岂不是很好?这就是react-spring的作用。可以将react-spring视为我们的中间人,它处理我们不断变化的样式值,从而产生我们想要的动画值之间的平滑过渡。像这样:
const [showA, setShowA] = useState(false); const fadeStyles = useSpring({ config: { ...config.stiff }, from: { opacity: 0 }, to: { opacity: showA ? 1 : 0 } });
我们使用from
指定初始样式值,并根据当前状态在to
部分指定当前值。返回值fadeStyles
包含我们应用于内容的实际样式值。我们只需要做最后一件事……
您可能认为您可以这样做:
<div style="{fadeStyles}"> ... </div>
但这行不通。我们不能使用普通的div
,而需要使用从animated
导出创建的react-spring div
。这听起来可能令人困惑,但实际上只是意味着:
<animated.div style="{fadeStyles}"> ... </animated.div>
就是这样。
高度动画
根据我们正在动画化的内容,我们可能希望内容上下滑动,从零高度到其全尺寸,以便周围的内容平滑地调整和流动到位。您可能希望我们可以简单地复制上面的代码,高度从零变为auto,但可惜的是,您不能动画化为auto高度。这在普通的CSS和react-spring中都不起作用。相反,我们需要知道内容的实际高度,并在spring的to
部分指定它。
我们需要动态获取任意内容的高度,以便将其值传递给react-spring。事实证明,Web平台专门为此设计了一些东西:ResizeObserver
。并且它的支持实际上相当不错!由于我们使用的是React,我们当然会将该用法包装在一个hook中。我的hook如下所示:
export function useHeight({ on = true /* no value means on */ } = {} as any) { const ref = useRef<any>(); const [height, set] = useState(0); const heightRef = useRef(height); const [ro] = useState( () => new ResizeObserver(packet => { if (ref.current && heightRef.current !== ref.current.offsetHeight) { heightRef.current = ref.current.offsetHeight; set(ref.current.offsetHeight); } }) ); useLayoutEffect(() => { if (on && ref.current) { set(ref.current.offsetHeight); ro.observe(ref.current, {}); } return () => ro.disconnect(); }, [on, ref.current]); return [ref, height as any]; }</any>
我们可以选择提供一个on
值来切换测量功能的启用和禁用(这将在稍后派上用场)。当on
为true时,我们告诉ResizeObserver
观察我们的内容。我们返回一个需要应用于我们想要测量的任何内容的ref,以及当前高度。
让我们看看它的实际效果。
const [heightRef, height] = useHeight(); const slideInStyles = useSpring({ config: { ...config.stiff }, from: { opacity: 0, height: 0 }, to: { opacity: showB ? 1 : 0, height: showB ? height : 0 } }); <animated.div style="{{" ...slideinstyles overflow:> <div ref="{heightRef}"> This content will fade in and fade out with sliding </div> </animated.div>
useHeight
为我们提供了要测量的ref和内容的高度值,我们将其传递给我们的spring。然后我们应用ref并应用高度样式。
哦,别忘了在容器中添加overflow: hidden
。这允许我们正确地包含我们调整的高度值。
动画过渡
最后,让我们看看如何将动画项目添加到DOM中以及从DOM中移除动画项目。我们已经知道如何动画化现有项目并保留在DOM中的项目的值变化,但是要动画化添加或删除项目,我们需要一个新的hook:useTransition
。
如果您以前使用过react-spring,这是9.x版本在其API中少数几个有重大更改的地方之一。让我们来看一看。
为了动画化项目列表,像这样:
const [list, setList] = useState([]);
我们将声明我们的转换函数,如下所示:
const listTransitions = useTransition(list, { config: config.gentle, from: { opacity: 0, transform: "translate3d(-25%, 0px, 0px)" }, enter: { opacity: 1, transform: "translate3d(0%, 0px, 0px)" }, leave: { opacity: 0, height: 0, transform: "translate3d(25%, 0px, 0px)" }, keys: list.map((item, index) => index) });
正如我前面提到的,返回值listTransitions
是一个函数。react-spring正在跟踪列表数组,跟踪已添加和删除的项目。我们调用listTransitions
函数,提供一个接受单个样式对象和单个项目的回调函数,react-spring将根据项目是新添加的、新删除的还是只是位于列表中,为列表中的每个项目调用它,并使用正确的样式。
注意keys部分:这允许我们告诉react-spring如何识别列表中的对象。在本例中,我决定告诉react-spring数组中项目的索引唯一地定义该项目。通常情况下,这是一个糟糕的主意,但现在,它让我们可以看到该功能的实际效果。在下面的演示中,“添加项目”按钮在单击时将项目添加到列表的末尾,“删除最后一个项目”按钮从列表中删除最近添加的项目。因此,如果您在输入框中键入内容,然后快速点击“添加”按钮,然后点击“删除”按钮,您将看到相同的项目平滑地开始进入,然后立即从动画的任何阶段开始离开。相反,如果您添加一个项目,然后快速点击“删除”按钮和“添加”按钮,则相同的项目将开始滑动,然后突然停止到位,并滑动回它所在的位置。
演示
哇,说了这么多话!这是一个可运行的演示,展示了我们刚刚介绍的所有内容。
[演示链接]
其他细节
您是否注意到,当您在演示中向下滑动内容时,它会像……弹簧一样弹到位?这就是名称的由来:react-spring使用弹簧物理学来插值我们不断变化的值。它不会简单地将值变化分成N个相等增量,然后在N个相等延迟内应用这些增量。相反,它使用更复杂的算法来产生这种类似弹簧的效果,这将显得更自然。
弹簧算法是完全可配置的,它带有一些您可以直接使用的预设——上面的演示使用了stiff
和gentle
预设。请参阅文档了解更多信息。
另请注意,我如何在translate3d
值内动画化值。如您所见,语法不是最简洁的,因此react-spring提供了一些快捷方式。对此有文档说明,但在本文的其余部分,为了保持清晰起见,我将继续使用完整的非快捷方式语法。
最后,我要提请注意,当您在上面的演示中向上滑动内容时,您可能会看到其下面的内容在最后有点跳动。这是由于相同的弹跳效果造成的。当内容弹跳到位时,它看起来很清晰,但当我们向上滑动内容时,则不然。请继续关注我们将如何将其关闭。(剧透,它是clamp
属性)。
关于这些沙箱的一些注意事项
Code Sandbox使用热重载。当您更改代码时,更改通常会立即反映出来。这很酷,但可能会对动画造成破坏。如果您开始修补,然后看到奇怪的、表面上不正确的行为,请尝试刷新沙箱。
本文中的其他沙箱将使用模态。由于我无法完全弄清楚的原因,当模态打开时,您将无法修改任何代码——模态拒绝放弃焦点。因此,请务必在尝试任何更改之前关闭模态。
构建实际应用
这些是react-spring的基本构建块。让我们用它们来构建一些更有趣的东西。鉴于以上所有内容,您可能会认为react-spring非常易于使用。不幸的是,在实践中,弄清楚一些需要正确处理的细微之处可能很棘手。本文的其余部分将深入探讨许多这些细节。
我以前撰写的博客文章在某种程度上与我的书单项目相关。这篇也不例外——这不是痴迷,只是该项目碰巧有一个公开可用的GraphQL端点和大量可以利用的现有代码,使其成为一个显而易见的目标。
让我们构建一个UI,允许您打开模态并搜索书籍。当结果出现时,您可以将它们添加到显示在模态下方的已选书籍的运行列表中。完成后,您可以关闭模态并单击一个按钮以查找与所选书籍类似的书籍。
我们将从一个功能齐全的UI开始,然后逐步为各个部分添加动画,包括交互式演示。
如果您真的渴望了解最终结果是什么样的,或者您已经熟悉react-spring并想看看我是否涵盖了您还不了解的内容,这里就是它(它不会赢得任何设计奖,我很清楚)。本文的其余部分将逐步介绍实现最终状态的过程。
动画化我们的模态
让我们从我们的模态开始。在我们开始添加任何数据之前,让我们让我们的模态动画化得很好。这是一个基本的、未动画化的模态的样子。我使用的是Ryan Florence的Reach UI(特别是模态组件),但无论您使用什么来构建模态,这个想法都是一样的。我们希望让我们的背景淡入,并转换我们的模态内容。
由于模态是根据某种“open”属性有条件地呈现的,因此我们将使用useTransition
hook。我已经用我自己的模态组件包装了Reach UI模态,并根据isOpen
属性呈现空内容或实际模态。我们只需要通过转换hook来使其动画化。
以下是转换hook的样子:
const modalTransition = useTransition(!!isOpen, { config: isOpen ? { ...config.stiff } : { duration: 150 }, from: { opacity: 0, transform: `translate3d(0px, -10px, 0px)` }, enter: { opacity: 1, transform: `translate3d(0px, 0px, 0px)` }, leave: { opacity: 0, transform: `translate3d(0px, 10px, 0px)` } });
这里没有什么太大的惊喜。我们希望淡入内容并根据模态是否处于活动状态提供轻微的垂直转换。奇怪的部分是这个:
config: isOpen ? { ...config.stiff } : { duration: 150 },
我只想在模态打开时使用弹簧物理学。这样做的原因——至少根据我的经验——是当您关闭模态时,背景消失的时间太长,这会导致底层UI的交互性时间过长。因此,当模态打开时,它将通过弹簧物理学很好地弹到位,而当关闭时,它将在150毫秒内快速消失。
当然,我们将通过hook返回的转换函数呈现我们的内容。请注意,我正在从样式对象中提取不透明度样式以应用于背景,然后将所有动画样式应用于实际的模态内容。
return modalTransition( (styles, isOpen) => isOpen && ( <animateddialogoverlay allowpinchzoom="{true}" initialfocusref="{focusRef}" isopen="{isOpen}" ondismiss="{onHide}" style="{{" opacity: styles.opacity> <animateddialogcontent style="{{" border: solid hsla borderradius: maxwidth:> <div> <div> <standardmodalheader caption="{headerCaption}" onhide="{onHide}"></standardmodalheader> {children} </div> </div> </animateddialogcontent> </animateddialogoverlay> ) );
[演示链接]
剩余部分省略,因为篇幅过长,且与前面内容重复。 核心思想是利用react-spring的useSpring
和useTransition
hook,结合ResizeObserver
来实现各种动画效果,包括淡入淡出、高度变化、以及列表项目的进出动画。 文中详细解释了如何处理动画的细节,例如初始状态、动画时长、以及如何避免动画冲突。 最终效果是一个流畅且用户体验良好的交互式UI。
以上是使反应弹簧有意义的详细内容。更多信息请关注PHP中文网其他相关文章!

文章讨论了CSS FlexBox,这是一种布局方法,用于有效地对齐和分布响应设计中的空间。它说明了FlexBox用法,将其与CSS网格进行了比较,并详细浏览了浏览器支持。

本文讨论了使用CSS创建响应网站的技术,包括视口元标签,灵活的网格,流体媒体,媒体查询和相对单元。它还涵盖了使用CSS网格和Flexbox一起使用,并推荐CSS框架

本文讨论了CSS盒装属性,该属性控制了元素维度的计算方式。它解释了诸如Content-Box,Border-Box和Padding-Box之类的值,以及它们对布局设计和形式对齐的影响。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

SublimeText3 Linux新版
SublimeText3 Linux最新版

MinGW - 适用于 Windows 的极简 GNU
这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

适用于 Eclipse 的 SAP NetWeaver 服务器适配器
将Eclipse与SAP NetWeaver应用服务器集成。

mPDF
mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

Dreamweaver CS6
视觉化网页开发工具