React Spring教程:制作动画React应用

React Spring Tutorial: Making Animated React Apps

将动画整合到UI设计中可能是一件棘手的事情。 去年12月,我们发表了一篇文章,描述了使用Framer Motion制作的React应用中的"黄油平滑"动画。 Framer Motion的问题是缺少有关如何做最简单的事情的教程。 研究React Spring库可以使人们面临相反的问题。

尽管其文档井井有条,详细,易于使用,并提供了许多令人印象深刻的示例,但是如果您的目的是获取基础知识,则其中大多数都太复杂了。 因此,我们认为编写一些React Spring教程可能会有所帮助。

本文对从React Spring开始的人很有用。 我们将研究各种方法,这些方法可以将多个弹簧组合在一起以制作更复杂的动画,包括示例。

React Spring库的5个钩子

动画为设计和改善应用的用户体验增添了生气。 React Spring是一个基于物理的动画库。 它可以计算出幕后的所有机制,从而获得精美流畅的动画,并允许我们为用户部分或完全配置它。 而且,React Spring比使用纯CSS更容易(至少,没有疯狂的复杂且难以维护的大量代码)。

目前,React Spring库包含 5个钩子:

  • useSpring –更改动画a => b状态的单个动画。

  • useSprings –多个弹簧动画用于列表,这些列表也会更改动画状态a => b。

  • usetrail –具有公共数据集的多个spring动画,其中每个后续动画都落后于上一个动画。

  • useTransition –挂载/卸载列表的动画,其中元素被添加,删除和更新。

  • useChain –用于确定几个先前定义的动画的执行顺序和顺序。

  • React Spring库的所有这5个钩子在本质上都是相似的,但是每个钩子都有其独特之处。 让我们更详细地检查其中的每一个。

    useSpring

    我们将从最简单的一个开始-useSpring。 它将传递的值转换为动画值。

    如果console.log由Spring返回的值,我们将得到一个包含动画道具的对象:Using useSpring

    对于第一个示例,我们将创建一个简单的动画组件,该组件将在单击时调整大小并移动backgroundPosition

    首先,让我们导入所需的钩子和动画组件:

    对于外观动画,只需指出以下内容即可:

    1
    import { Animated, useSpring } from"React Spring";

    对于外观动画,只需指出以下内容即可:

    1
    2
    3
    4
    const springProps = useSpring({
       from: { opacity: 0, ... },
       to: { opacity: 1, ... }
     })

    Spring可以对从初始from到最终to的状态变化进行动画处理。

    此外,可以省略to道具以使其更简单:

    1
    2
    3
    4
    const springProps = useSpring({
       opacity: 1,
       from: { opacity: 0 },
     })

    您可以设置几乎所有CSS属性的动画或使用任意名称,保留关键字除外。

    接下来,您需要将springProps传递给动画元素。 但是,值得一提的是,由于传输的值正在更新,因此它们不能与普通组件或标签一起使用。

    需要以下样式组件/情感/等组件:

    1
    const AnimatedBox = styled(Animated(ComponentName/TagName))`...`;

    或只是任何HTML标记,并带有"动画"前缀:Button</Animated.button>

    Animated是动画基元,允许您处理传递的动画道具。 Animated扩展了本机元素的功能,以便它们可以接受动画值。

    剩下的就是将prop传递给所需的组件:

    1
     

    React Spring钩子可以接受几乎任何值或颜色,HTML属性,甚至可以是字符串模式(例如,bordertop:"6px solid red")等。一个非常不幸的限制是,无法为fromto传递不同的属性 在一个道具中。 也就是说,必须将px转换为px,%=>%,number => number等,否则会发生错误。

    <iframex style =" BORDER-BOTtoM:0像素; BORDER-LEFT:0像素;宽度:100%;高度:500像素;溢出:隐藏; BORDER-toP:0像素; BORDER-RIGHT:0像素;边框半径:4像素" title = react-spring-useSpring src ="https://codesandbox.io/embed/react-spring-useSpring-irxxw?fontsize=14&hidenavigation=1&theme=dark&view=preview" sandbox ="allow-modals allow-forms allow-popups allow- 脚本allow-same-origin"> </iframex>

    从下面的示例中可以看到,如果需要,可以对动画状态进行重构。 例如,当使用任意名称或插值时,您需要一次描述多个组件的动画状态时,这很有用(稍后将对其进行详细说明)。

    我们将使用usestate更改动画状态。 请注意,尽管我们使用usestate,但仍需要在道具from中设置动画的初始状态。否则,道具数据将为空,然后再更改状态。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const [clicked, setClicked] = usestate(false);
     const { size, ..., ...springProps } = useSpring({
       size: clicked ? 300 : 200,
       backgroundPosition: clicked ?"50% 100%" :"50% 0%",
       ...
       from: {
         size: 200,
         backgroundPosition:"50% 0%"
         ...
       }
     });

    我们将解构size的值(对widthheight进行动画处理将需要此值),并将其他值(在本例中为backgroundPosition)传递给springProps

    剩下的只是将动画值传递给我们的组件,并且当然,在单击时更改其状态:

    1
    2
     setClicked(!clicked)}
    />

    现在,在单击之后,状态将从false更改为true,因此组件的高度和宽度将从200px更改为300px,背景元素的backgroundPosition也将从" 50% 0%"到" 50%100%"。

    也许值得一提的是此示例中使用的ProgressBar计数器。 让我们用以下代码行完成钩子:

    1
    counter: clicked ? 100 : 0,

    重要的是不要忘记解构计数器,因为我们将需要在传递给AnimatedItem组件的springProps之外使用它。

    为了从0到100迭代计数器值,我们将使用一些插值:

    1
    2
      {counter.interpolate(val => Math.floor(val) +"%")}
    </AnimatedBox>

    换句话说,内插使您可以迭代函数的值或指定值的范围。 请注意,传递到动画图元的插值工作效率更高,并且占用的空间更少。

    很简单,对-

    useSprings

    1
    import { Animated, useSprings } from"React Spring";

    useSprings挂钩与先前的挂钩略有不同。 它为静态列表创建多个spring动画。

    <iframex style =" BORDER-BOTtoM:0像素; BORDER-LEFT:0像素;宽度:100%;高度:500像素;溢出:隐藏; BORDER-toP:0像素; BORDER-RIGHT:0像素;边框半径:4像素" title = react-spring-useSprings src ="https://codesandbox.io/embed/react-spring-useSprings-rsk8x?fontsize=14&hidenavigation=1&theme=dark&view=preview" sandbox =" allow-modals allow-forms allow-popups allow- 脚本allow-same-origin"> </iframex>

    在此示例中,配色方案列表是我们的列表,该列表使用useSprings。 较大的块是通常的useSpring,它在单击列表元素时会接收所需颜色的索引值,并将其索引传递给state

    1
    const [index, setindex] = usestate(null);

    useSprings占用列表的长度,并确定每个列表元素的动画参数:

    1
    2
    3
    const springs = useSprings(list.length, list.map(item => ({
       ...
    }));

    在此示例中,useSprings

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const springs = useSprings(
       colorScheme.length,
       colorScheme.map((item, i) => ({
         background: item.hex,
         color: item.fontColor,
         opacity: index === null | i === index ? 1 : 0.6,
         height: index === null ? 120 : 60,
         from: {
           opacity: 0,
           height: 120
         }
       }))
     );

    背景和颜色值取自colorScheme对象的数组:

    1
    2
    3
    4
    const colorScheme = [
      { name:"Red munsell", hex:"#ec0b43", fontColor:"#fff" },
      ...
    ];

    乍一看,springs参数似乎令人困惑,但实际上,它非常简单。 在单击列表元素之一之前(即,当index = null时),所有有色块的不透明度值=1。如果已经单击了列表元素之一,则当前元素的不透明度= 1。 所有其他元素的不透明度= 0.6。 在高度值的情况下,它甚至更简单。 在我们单击其中一个色块之前,其值= 120,而在被单击后= 60。

    我们仍然必须将springs的值传递给组件列表:

    1
    2
    3
    4
    5
    6
    7
    <GridContainer pt={1}>
      {springs.map((prop, i) => (
         { setindex(i); onItemClick(i); }}
          style={prop}
        />
      ))}
    </GridContainer>

    单击时定义状态索引并执行onItemClick函数的位置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     const [springProps, setspringProps] = useSpring(() => ({
       from: { height: 0, opacity: 0 }
     }));
     
     const onItemClick = i => {
       const { name, hex, fontColor } = colorScheme[i];
       setspringProps({
         name,
         background: hex,
         color: fontColor,
         height: 200,
         opacity: 1
       });
     };

    在这里,我们使用第二种方法来确定useSpring –使用设置函数– setspringProps。 单击时,我们将所选列表项的index – i传递给onItemClick函数,该函数接收所需项的值(名称,十六进制,fontColor)并使用这些值更新useSpring 设置高度和不透明度。

    它仅保留将springProps的值传递给组件:

    1
    2
    3
    4
    5
      {springProps.name}</AnimatedBox>
     
        {index !== null && colorScheme[index].hex}
      </AnimatedBox>
    </AnimatedItem>

    在这种情况下,十六进制值直接取自对象的原始数组,因为如果我们从spring中获取此值,则将获得RGBA格式的颜色插值,这与我们的预期不完全相同。

    usetrail

    1
    import { Animated, usetrail, interpolate } from"React Spring";

    usetrail允许您通过单个配置创建多个spring动画,并且每个后续spring在上一个spring之后执行。 用于交错动画。

    1
    const trail = usetrail(list.length, { ... });

    对于此示例,配置大致如下所示:

    1
    2
    3
    4
    5
    6
    const [trail, set] = usetrail(imgList.length, () => ({
      opacity: 1, ...
      from: {
        opacity: 0, ...
      }
    }));

    将线索传递到组件中:

    1
    2
    3
    4
    5
    6
    const [trail, set] = usetrail(imgList.length, () => ({
      opacity: 1, ...
      from: {
        opacity: 0, ...
      }
    }));

    在此示例中,您应该注意的是一组值的插值,它允许您一次更改多个变换函数的transform属性。 它与单参数插值略有不同:

    1
    transform: x.interpolate(x => `translateX(${x}px)`)

    但是原理是一样的。

    结果,我们得到了一个轨迹动画,该动画更改了transform(scale, translate and skewX)和外观的不透明度的值,并且在单击列表容器时也再次更改了比例。 通过单击按钮,不透明度也将借助于我们已经知道的set函数进行更改。

    useTransition

    1
    import { Animated, useTransition } from"React Spring";

    useTransition允许您创建动画过渡组。 它接受列表的元素,其键和生命周期。 动画在元素出现和消失时触发。

    您可以将转换用于阵列,在组件之间切换或用于同一组件的安装/卸载。

    <iframex style =" BORDER-BOTtoM:0像素; BORDER-LEFT:0像素;宽度:100%;高度:500像素;溢出:隐藏; BORDER-toP:0像素; BORDER-RIGHT:0像素;边框半径:4像素" title = react-spring-useTransition src =" https://codesandbox.io/embed/react-spring-useTransition-iz27j?fontsize=14&hidenavigation=1&theme=dark&view=preview" sandbox =" allow-modals allow-forms allow-popups allow- 脚本allow-same-origin"> </iframex>

    让我们借助useTransition:创建一个滑块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const [[index, dir], setindex] = usestate([0, 0]);
    const transitions = useTransition(slides[index], item => item.url, {
      from: {
        opacity: 0,
        transform: `translate3d(${dir === 1 ? 100 : -100}%,0,0) scale(0.5)`
       },
      enter: { ... },
      leave: { ... }
    });

    使用已经熟悉的原理,我们将列表的长度传输到useTransition(因为我们正在对滑块进行动画处理,因此当前幻灯片的索引将用作长度)和列表元素本身(背景图片的网址) )。

    from, enter, leave属性描述了当前幻灯片在安装时从fromenter以及在卸载时从enterleave的状态。

    让我们将配置传递给组件:

    1
    2
    3
    4
    5
    6
    7
    8
    {transitions.map(({ item, prop, key }) => (
      <Box key={key}>
        <Slide
          style={prop}
          background={`url(${item.url}`}
       />
      </Box>
    ))}

    单击箭头控件时,index的值和幻灯片的方向(dir)会更改:

    1
    2
     slideLeft()}/>
     slideRight()}/>

    其中slideLeftslideRight是:

    1
    2
    const slideLeft = () => setindex([(index - 1 + slides.length) % slides.length, -1]);
    const slideRight = () => setindex([(index + 1) % slides.length, 1]);

    slideLeftslideRight不仅可以更改indexdir的值,还可以在到达最后一张幻灯片后对其进行重置。

    结果,我们得到了一个滑块,可以在其中安装/卸载幻灯片,并在转换indexdir时更改opacitytransform的值。

    控制子弹使用已经熟悉的useSrings挂钩进行动画处理,因此我们不再赘述。

    useChain

    useChain允许您设置先前定义的动画挂钩的执行顺序。 为此,您需要使用refs,这将随后阻止动画的独立执行。

    1
    import { Animated, useChain } from"React Spring";

    让我们使用此挂钩为汉堡菜单元素设置动画。 在我们的例子中,首先将执行MenuBaruseSpring动画,然后是菜单组件列表-MenuItemuseSprings动画。

    <iframex style =" BORDER-BOTtoM:0像素; BORDER-LEFT:0像素;宽度:100%;高度:500像素;溢出:隐藏; BORDER-toP:0像素; BORDER-RIGHT:0像素;边框半径:4像素" title = react-spring-useChain src =" https://codesandbox.io/embed/react-spring-useChain-l07f8?fontsize=14&hidenavigation=1&theme=dark&view=preview" sandbox =" allow-modals allow-forms allow-popups allow- 脚本allow-same-origin"> </iframex>

    useSprings:定义ref并使用相同的原理

    1
    2
    3
    4
    5
    const springRef = useRef();
     const { ... , ...springProps } = useSpring({
       ref: springRef,
       ...
     });

    现在我们要做的就是用useChain定义动画的执行顺序:

    1
    2
    3
    4
    useChain(
       open ? [springRef, springsRef] : [springsRef, springRef],
       open ? [0, 0.25] : [0, 0.75]
     );

    在此示例中,如果状态为open === true,则在扩展菜单时,将首先执行MenuBar的动画,然后执行MenuItems。 如果为open === false,即关闭菜单,则执行顺序相反。

    设置动画顺序后,还可以指定timeSteps – 0-1(1 = 1000ms)之间的值的数组,用于定义动画的开始和结束。 例如,[0,0.25] 0 * 1000ms = 0和0.25 * 1000ms = 250ms。

    奖金

    使用已经熟悉的React Spring钩子的一些奖励示例:

    手风琴– useSpring

    <iframex style =" BORDER-BOTtoM:0像素; BORDER-LEFT:0像素;宽度:100%;高度:500像素;溢出:隐藏; BORDER-toP:0像素; BORDER-RIGHT:0像素;边框半径:4像素" title = react-spring-accordion src =" https://codesandbox.io/embed/react-spring-accordion-ymljk?fontsize=14&hidenavigation=1&theme=dark&view=preview" sandbox =" allow-modals allow-forms allow-popups allow- 脚本allow-same-origin"> </iframex>

    卡清单– useSprings

    <iframex style =" BORDER-BOTtoM:0像素; BORDER-LEFT:0像素;宽度:100%;高度:500像素;溢出:隐藏; BORDER-toP:0像素; BORDER-RIGHT:0像素;边框半径:4像素" title = react-spring-cards-list src =" https://codesandbox.io/embed/react-spring-cards-list-9onoj?fontsize=14&hidenavigation=1&theme=dark&view=preview" sandbox =" allow-modals allow-forms allow -popups allow-scripts allow-same-origin"> </iframex>

    图片库– useChain

    <iframex style =" BORDER-BOTtoM:0像素; BORDER-LEFT:0像素;宽度:100%;高度:500像素;溢出:隐藏; BORDER-toP:0像素; BORDER-RIGHT:0像素;边框半径:4像素" title = react-spring-image-gallery src =" https://codesandbox.io/embed/react-spring-image-gallery-rnt6t?fontsize=14&hidenavigation=1&theme=dark&view=preview" sandbox =" allow-modals allow-forms allow -popups allow-scripts allow-same-origin"> </iframex> 要带走的要点

    React Spring库的优势:

  • 动画基于物理学。 无需(除非您有意地想要)自定义持续时间或缓动。 结果是平滑,柔和和自然的动画。

  • 易于使用且清晰的文档。

  • React Spring的创建者Paul Henschel的CodeSandbox页面上的文档+中提供了许多有趣而美丽的演示。

  • 如有必要,您可以使用use-gesture中的一组非常有用的钩子。

  • 库存储库正在不断更新和维护。

  • 图书馆周围形成了一个很小但很活跃的社区。

  • 保罗·亨舍尔(Paul Henschel)经常在他的Twitter页面上分享有趣的见解,演示以及更多内容。

  • React Spring库的缺点:

  • 没有简单有效的循环动画的方法,文档中的示例建议使用无限循环,实际上会导致严重的性能问题:

  • 1
    2
    3
    4
    5
    6
    const { value } = useSpring({
       to: async next => {
         while (1) await next({ ... })
       },
       ...
     })

  • 正如我们已经提到的,React Spring的另一个非常不幸的局限性是不可能在单个道具中为fromto传递不同的属性。 也就是说,必须将px转换为px,%=>%,number => number等,否则会发生错误。

  • 另外,该库不允许您为auto的值设置动画。 该文档建议针对这些以及需要组件的确切高度和宽度的其他目的使用react-resize-aware,react-measure等。

  • 现在,您已经了解了在React中使用动画的一种相对新的相对简单的方法,请尝试对应用程序的各个方面进行动画处理。 我们开始做吧!