使用 `react-spring` 在React中实现动画效果

這篇文章是株式會社Hteam Finanzy 2021年Advent Calendar的第20天的文章。
這次由@okkuyama負責撰寫。

首先

如果在React前端实现中想要增强UI/UX,动画效果可能是相当重要且不可或缺的存在。然而,动画的实现很难界定是工程师的领域还是设计师的领域,并且实现的难度也较高。此外,动画方法也存在多种,并且每种方法的背景也有所不同,这也增加了实现的难度。

在React中实现动画的类型

使用CSS创建动画

如果要实现动画效果,有经验的人首先会想到使用HTML+CSS来实现CSS的transition和animation。
当然在React中也可以使用这种实现方式,主要的方法是预先定义好动画的CSS类,并在用户操作的触发下添加或移除预定义的类来实现动画效果。
优点是,如果过去有CSS动画的经验,那么这种方法应该可以很容易地实现。

使用React库实现动画。

在React中,即使没有CSS等知识,也有各种简单且灵活的库可用于进行动画。

以下是一些主流的选择:
– react-spring
– Framer Motion
– React Transition Group
– React-Motion
– React Move

我想描述一下这个非常受欢迎、最近采用了最新架构的react-spring库来进行动画实现的方法。

关于 react-spring。

根据react-spring网站上的描述,这个库是一个基于物理的动画库。由于这个库是在React-Motion这个之前的主要动画库的基础上发展而来的,所以熟悉那个库的人似乎可以轻松迁移到这个库上。

与传统的动画制作方法的不同

在react-spring的介绍中出现了“基于物理的动画”一词,那么它与传统动画有何不同呢?我认为了解这种差异将成为学习react-spring的捷径。

传统动画的方法是设置持续时间和缓动曲线。

継続時間: アニメーションの開始から終了までの時間を設定

イージング曲線: より自然なアニメーションを行うための変化割合を設定(例として、はじめゆっくりで徐々に早くなる、はじめ早く終了に近づくと遅くなる、などがあります)

传统动画的问题

尽管使用这种传统方法仍然可以获得足够的动画效果,但在尝试用交互式前端实现时会遇到以下问题。

由于动画的时间和动画的动作事先确定,所以无论用户进行何种操作,动画始终保持相同的动作。
例如,即使用户通过轻快的滑动等操作来进行快速操作,动画仍然保持不变,导致互动性变得较弱。

react-spring 是基于物理模拟的动画库。

在react-spring中,基本上没有持续时间或缓动曲线的概念。(※在设置中可以指定传统动画类型)
需要设置的是以下物理属性。(参考config)
– mass:质量
– tension:能量负载
– friction:阻力
– precision:精度
– velocity:速度
– …其他

通过设置上述的物理属性和变化后的值,react-spring可以通过物理模拟来产生动画效果。
根据这些设置值,动画会快速地响应快速的动作,并以缓慢的动作响应慢动作,从而实现更与用户操作互动的动画效果。

如果你不想费力地进行详细设置,也许使用方便的预设效果会更好,这样你可以得到一个合适的动画效果而不必过于纠结。

然而,由于物理基础动画没有时间概念,因此它并不适合用于纸芝居式的动画电影。但是,在这种情况下,您可以通过传递持续时间和缓动曲线作为属性,将其作为传统动画来运行。

实施方式

现在我们来介绍一下使用react-spring实现动画的方法。
在react-spring中,大致有两种实现方法:
– Hooks类型
– RenderProps类型
我认为最近的趋势是使用Hooks,所以本次我们将讨论使用Hooks类型的实现方法。

基本的动画实现

基本的动画实现是使用 hooks 中的 useSpring 来完成的。
以下是在加载组件的同时将组件本身淡入的动画实现方法。

import React, { useState, useEffect } from 'react'
import { useSpring, animated } from 'react-spring'

export const FadeInSample = () => {
  const [toggle, setToggle] = useState(false)
  const styles = useSpring({ opacity: toggle ? 1 : 0 })

  const handleToggle = () => {
    setToggle(_toggle => !_toggle)
  }

  useEffect(() => {
    handleToggle()
  }, [])

  return (
    <animated.div style={styles}>
      <p>Fade in text</p>
    </animated.div>
  )
}

实施内容说明

useSpringを使いstateのtoggle変数の状態で透過率(opacity)が0←→1で変化するアニメーションスタイルを定義。
アニメーションレンダリング可能なコンポーネントにuseSpringで作成したアニメーションスタイルを属性値として渡してアニメーションさせる。

进行重构以进一步简化

上述的示例在组件中使用toggle状态并切换动画。如果这个toggle除了动画以外还会被其他地方使用,那么这个实现方法也是可以的。但是,为了只实现动画而使用useState来管理状态是冗余的,也可以使用以下的实现方法。

import React, { useState, useEffect } from 'react'
import { useSpring, animated } from 'react-spring'

export const FadeInSample = () => {
  const [styles, setStyles] = useSpring(() => { opacity: 0 })

  useEffect(() => {
    setStyles({ opacity: 1 })
  }, [])

  return (
    <animated.div style={styles}>
      <p>Fade in text</p>
    </animated.div>
  )
}

与上次不同的地方是useSpring的定义方式变化了。

    前回の方式:アニメーションスタイルのみ受け取る
  const styles = useSpring({ opacity: toggle ? 1 : 0 })
    今回の方式:アニメーションスタイルと、設定メソッドを受け取る
  const [styles, setStyles] = useSpring(() => { opacity: 0 })

※ 后者的定义是注意将函数返回样式对象的方式传递给useSpring,而不是直接传递样式对象本身。

通过用户操作触发的动画

在用户进行按钮操作等时执行动画的方法。
在这种情况下,需要明确地保持淡入和淡出时的状态。

import React, { useState, useEffect } from 'react'
import { useSpring, animated } from 'react-spring'

export const FadeInSample = () => {
  const [toggle, setToggle] = useState(false)
  const styles = useSpring({ opacity: toggle ? 1 : 0 })

  const handleToggle = () => {
    setToggle(_toggle => !_toggle)
  }

  return (
    <>
      <button onClick={handleToggle}>{toggle ? 'Fade Out' : 'Fade IN'}</button>
      <animated.div style={styles}>
        <p>Fade in text</p>
      </animated.div>
    </>
  )
}

如果想触发动画完成

如果想要在动画完成后执行某些处理,以下是一种可能的实现方式。

import React, { useState, useEffect } from 'react'
import { useSpring, animated } from 'react-spring'

export const FadeInSample = () => {
  const [styles, setStyles] = useSpring(() => { opacity: 0 })

  useEffect(() => {
    setStyles({
      opacity: 1,
      onRest: () => {
        // アニメーション終了後の処理
      }
    })
  }, [])

  return (
    <animated.div style={styles}>
      <p>Fade in text</p>
    </animated.div>
  )
}

使用onRest函数来定义动画结束后的处理回调。

进行页面转换等过渡。

在使用Next.js等跨多个页面的单页应用实现时,可能会有一些场景需要在页面转换时使用过渡动画。在这种情况下,如果使用react-spring,实现起来相对简单。

import React, { useState } from 'react'
import { useTransition, animated, config } from 'react-spring'

// 遷移させるコンポーネント
const Component1 = () => <animated.div>Component1</animated.div>
const Component2 = () => <animated.div>Component2</animated.div>
const Component3 = () => <animated.div>Component3</animated.div>

export const Transition = () => {
  const [pageIndex, setPageIndex] = useState(0)
  // トランジションで切り替えするためコンポーネントを配列格納する
  const componentList = [Component1, Component2, Component3]

  // トランジション遷移定義
  const transitions = useTransition(pageIndex, item => item, {
    unique: true,
    from: { opacity: 0 },
    enter: { opacity: 1 },
    leave: { opacity: 0 },
    config: config.molasses,
  })

  const handleTransition = () => {
    setPageIndex(page => (page+1) % componentList.length)
  }

  return (
    <div onClick={handleTransition}>
      {transitions.map(({item, props, key}) => {
        const Component = componentList[item]
        return <Component key={key} />
      })}
    </div>
  )
}

使用传统类型的持续时间和缓动曲线来创建动画。

有时候可能需要在时间轴上进行纸带动画式的动画。 在这种情况下,只需通过更改传递给useSpring的参数即可实现。

进行传统动画时需要注意的事项是,只将duration和easing作为传递给useSpring的参数。如果传递物理基本参数(质量,紧张度,摩擦力,精度,速度等),将优先使用物理基本动画,请注意。

在这个示例中,我们使用了一个名为d3-ease的缓动曲线库,但您也可以自行定义函数进行设置。

import React from 'react'
import { useSpring, animated } from 'react-spring'

export const TimeLineAnimation = () => {
  const animation = {
    config: {
      duration: 2000,
      easing: d3.easeLinear(1),
    },
    from: {
      transform: 'translateX(-300px)',
    },
    to: {
      transform: 'translateX(0px)',
    },
  }
  const [styles] = useSpring(animation)

  return (
    <animated.div style={styles}>
      <p>Slide in text</p>
    </animated.div>
  )
}

其他

请根据需要使用适当的情况下,除了useSpring和useTransiton之外,React-Spring还提供了以下的hooks,请尝试使用。

useSprings : 複数のアニメーションの作成

useChain : 複数のアニメーションをつなげ、連続したアニメーションを行う

useTrail : 複数のアニメーションを作成、各アニメーションは前のアニメーションに従って連続して動作します

总结

我认为react-spring是一个相当复杂的动画库,可以以较低的成本学习,并提供非常强大和交互性的UI/UX。可以根据用户输入的值,实现快速或慢速的动画效果,从而实现良好的用户体验。

此外,我认为与Next.js等单页面应用程序库结合使用也是一大亮点。

通过使用预设值,即使没有进行复杂的设置,也可以通过简单的设置值实现动画效果,因此建议先使用默认值进行搭建,并且后续可以仔细调整和修改物理属性,这样就可以进行动画的最终调整的分工。

如果你理解了react-spring动画的概念,实现起来会很简单,所以为什么不趁这个机会快速实现一下呢?我希望通过实现一个出色的用户体验来提高DAU。

广告
将在 10 秒后关闭
bannerAds