从一个代码库中运行React和React Native的动画

305 阅读10分钟

简介

动画对于使一个应用程序具有互动性是很重要的。它们让用户感觉到与应用程序的联系更加紧密。

随着React Native的发展势头,很多产品都是用React Native构建的,或者已经迁移到了React Native。然而,虽然网页的代码在某些情况下可以在移动应用中重复使用,但一个主要的问题是,动画往往不能延续,因为API完全不同。

在这篇文章中,我们将学习如何利用React Native文件解析和react-spring(一个动画库)来编写一个单一的代码库,在React和React Native中运行动画。

我们将从学习React Native文件解析和react-spring开始,并通过构建一个非常基本的示例应用继续学习。在这个示例应用中,我们将编写一个代码,它既可以在网络上的React中运行,也可以在移动应用中的React Native中运行。

React Native的文件解析

React Native有一个高效的文件解析系统,可以让我们编写针对平台或针对本地的代码。它将检测一个文件是否有.ios.js.android.js 扩展名,并在需要时从其他组件加载相关平台(Android/iOS)的文件。这样,我们就可以为两个不同的平台编写两段不同的代码或组件。

同样,当我们创建带有.js.native.js 扩展名的文件时,.js 文件会被Node.js和web接收,而.native.js 文件会被React NativeMetro bundler接收。

这使我们有能力建立易于维护的应用程序,并在不同平台上重复使用大量的代码。

考虑一个如下结构的文件夹。

|-TestComponent
  |-index.js
  |-index.native.js

让我们假设我们在TestComponent 组件中使用App 组件,如下所示。

const App = () => (
  <div>
    <TestComponent />
  </div>
)

当代码为浏览器捆绑时,TestComponent 的导入将从index.js 文件中导入该组件。然而,如果代码是为React Native捆绑的,它就会拾取index.native.js

我们将利用文件解析的这个方面,为两个平台编写一个单一的动画代码。

React-spring

React-spring是一个基于弹簧物理学的动画库,它应该涵盖你的大部分UI相关的动画需求。它为你提供了足够灵活的工具,可以自信地将你的想法铸成移动的界面。

React-spring是两个现有React动画库之间的桥梁。React MotionAnimated。它继承了Animated的强大插值和性能,以及React Motion的易用性。

与其他动画库相比,react-spring的主要优势在于它能够应用动画而不依赖React逐帧渲染更新。动画是基于物理学的。

没有必要(除非你有意这样做)定制持续时间或缓和。这意味着你的动画可以相当无缝地处理中断和变化。其结果是平滑、柔和、自然的动画。

React-spring也是一个跨平台的动画库。它在网页、React Native和其他平台都有类似的实现,但作为不同的包。我们可以根据自己的要求来挑选合适的模块。然而,它们在所有平台上共享相同的API。这有助于我们使用 react-spring 来实现所有平台一个代码的目标。

我们将在本文要构建的示例应用程序中使用useSpring 钩子。useSpring ,通过覆盖值来改变动画,或者通过使用API传递回调函数来动态更新值,从而将值变成动画值。

构建一个示例应用程序

让我们使用刚才学到的上述概念,构建一个示例应用程序。这个示例应用程序将使用React的网页,React Native的移动应用程序,以及react-spring的动画。从功能上来说,这将是一个非常简单的应用,在移动应用和网络上,都会出现一个文本并放大应用的负载。

然而,我们必须首先为这个应用程序的运行创建基本的构件。你可以手动配置Metro bundler和webpack,或者使用现有的启动工具包。

我使用了expocreate-react-app 合并成一个项目。该应用程序的完整工作版本可以在这里找到。

现在让我们看看一些代码。考虑一下下面的项目。

|-src
  |-Box
    |-index.js
    |-Box.js
    |-Box.native.js
  |-Text
    |-index.js
    |-Text.js
    |-Text.native.js
  |-App.js

我们这里有两个组件:BoxText 。这两个组件都利用了React Native中的文件解析,这使得我们能够编写出适用于两个平台的单一组件。

Box

Box 是一个容器组件。我们将使用Box 作为所有其他组件或文本的容器。

在网页中,我们使用divsection 作为容器。然而,React Native不支持这些元素,而是使用View 组件作为容器。我们的目标是为所有的平台编写一个代码,因此我们将创建这个Box 组件,根据平台翻译成divView

考虑到React Native的文件分辨率,我们将为Box 组件创建两个文件。

  • Box.js
  • Box.native.js

Box.js 文件将被网络平台使用,它只是一个div 的别名。这将像这样导出div 元素。

// Box.js
export default 'div';

Box.native.js 文件将被React Native平台使用。我们从React Native导入View 元素,并像这样从文件中导出它。

// Box.native.js
import {View} from 'react-native'
export default View

我们创建一个通用的index.js 文件,这样在其他文件中导入和使用Box 元素就很容易,也更容易阅读。如果你看到这里,我们没有明确提到Box 是从.js 还是.native.js 文件导入的。

我们让捆绑器来决定,只是在index.js 文件中导出该组件,这样它就可以被其他组件使用。

// index.js
import Box from './Box';
export default Box

Text

Text 组件将被用于在网页和移动应用程序中添加文本。在Web中,我们使用像h1,h2, 或p 这样的元素来显示文本,而在React Native中,我们使用Text 组件。这个Text 组件现在将根据平台翻译成pText

我们将为Text 组件创建两个文件。

  • Text.js
  • Text.native.js

Text.js 文件将被网络平台使用,它只是p 的一个别名。这将像这样导出p 元素。

// Text.js
export default 'p';

Text.native.js 文件将被React Native平台使用。我们从React Native导入Text 组件,并从这里的文件导出。

// Text.native.js
import {Text} from 'react-native'
export default Text

Box 组件一样,我们也在这里创建一个普通的index.js 文件。这使得导入,以及在其他文件中使用Text 组件变得更加容易和可读。

// index.js
import Text from './Text';
export default Text

现在我们已经有了BoxText 组件的基本构件,我们可以继续构建示例应用程序。

让我们创建一个新的组件,App.js ,它将使用Box 作为容器,Text 来显示我们的标题:React Spring Animation 。我们还可以给该组件添加一些样式。

它应该看起来像这样。

// App.js

import React from 'react';
import Box from './Box';
import Text from './Text';

function App() {
  return (
    <Box style={{ marginTop: 50}}>
      <Text style={{ fontSize: 50 }}>React Spring Animation</Text>
    </Box>
  );
}
export default App;

上述代码为手机和网页创建了相同的文本,你可以在下面的截图中看到。在引擎盖下,代码在任何平台上执行之前都会被捆绑。

React Spring Animation 当webpack为网页捆绑代码时,它使用Box.jsText.js 文件,在App 组件中添加一个div 元素和p 元素,并在浏览器中运行。

然而,Metro捆绑了Box.native.jsText.native.js 文件,并在App 组件中添加了来自React Native的ViewText 组件。这在移动应用程序中也会得到预期的结果。

到目前为止,这就是我们的应用程序在手机和网络上的样子。Screenshot of mobile app that says "react spring animation" without movement Screenshot of webpage that says "react spring animation" without movement

添加动画

是时候为上述应用添加动画了。让我们通过增加字体大小给文本添加一个 "放大 "动画。

首先,将react-spring 作为依赖项安装到项目中。

npm i react-spring

yarn add react-spring

接下来,建立一个类似于BoxTextAnimated 组件。这个Animated 组件将是我们从react-spring 库中获取所有钩子、API 和工具的单一来源。

我们将把它与我们的BoxText 组件的结构类似,从而根据每个平台使用正确的钩子和API。

React-spring有两个我们将使用的模块:用于Web的react-spring 和用于React Native的react-spring/native 。在这个例子中,我们将使用react-spring 的钩子useSpring 来实现动画。

然而,我们应该从web的react-spring 中导入useSpring ,从React Native的react-spring/native 中导入useSpring 。因此,我们不能直接使用react-spring

考虑到这些事实,我们将建立一个Animated 组件,它将帮助我们为多个平台编写单个动画。

让我们添加一个新的组件文件夹,如下所示。

|-src
  |-Animated
    |-index.js
    |-Animated.js
    |-Animated.native.js

Animated

Animated 组件将提供所有需要的钩子和工具,从react-springreact-spring 在web中,我们使用useSpringuseTransition 这样的元素来制作动画,而在React Native中,我们使用来自react-spring/native 的相同钩子。

这个Animated 组件将根据平台从react-springreact-spring/native 中导入useSpring

我们将为Animated 组件创建两个文件。

  • Animated.js
  • Animated.native.js

Animated.js react-spring 文件将被网络平台使用,并将从 和 ,并将其导出。useSpring animated

// Animated.js
export { useSpring, animated } from 'react-spring'

Animated.native.js react-spring/native 文件将被React Native平台使用,并将从 和 ,并将其导出。useSpring animated

// Animated.native.js
export { useSpring, animated } from 'react-spring/native'

我们现在可以创建一个通用的index.js 文件,它将导入和导出Animated 组件,如BoxText 。这将使我们在其他文件中导入和使用Animated 组件变得更容易和更易读。

// index.js
export {useSpring, animated} from './Animated'

现在,让我们在App.js 中使用Animated 组件来制作文本的动画。

Animated 组件中导入useSpringanimated

import { useSpring, animated } from './Animated';

然后,使用Animated 功能创建一个启用动画的组件。一个组件只有在被Animated 函数扩展后才能使用react-spring制作动画。

const AnimatedText = animated(Text);

使用useSpring 函数创建动画,这与CSS的关键帧动画非常相似。

CSS中的关键帧通过为动画序列中的关键帧(或航点)定义样式来控制CSS动画序列的中间步骤。因此,我们可以在动画的不同阶段确定一个元素的样式。

useSpring 工作方式完全相同。你可以使用fromto 属性分别定义某些样式在动画开始和结束时的样子。

const styles = useSpring({
    from: {
      fontSize: 10,
    },
    to: { 
      fontSize: 50,
     },
  })

最后,将styles 传递给AnimatedText 组件的style 属性。

<AnimatedText style={{ ...styles }}>React Spring Animation</AnimatedText>

把所有东西放在一起,App.js 应该看起来像下面这样。

import React from 'react';
import { useSpring, animated } from './Animated';
import Box from './Box';
import Text from './Text';

const AnimatedText = animated(Text)

function App() {
  const styles = useSpring({
    from: {
      fontSize: 10,
    },
    to: { 
      fontSize: 50,
     }
  })
  return (
    <Box style={{ marginTop: 50}}>
      <AnimatedText style={{ ...styles }}>React Spring Animation</AnimatedText>
    </Box>
  );
}

上面的代码为网页和移动端(通过React Native)的文本添加了一个 "放大 "动画。

你可以在下面的gif图片中看到它的样子。

gif of webpage that says "react spring animation" with zoom in animation gif of mobile app that says "react spring animation" with zoom in animation

结论

使用上述技术,我们可以只创建一次几乎任何动画,并在网络和移动平台上重复使用。这可以实现快速的原型设计和简单的维护。它也有利于在不同的平台上实现功能的一致性。

不幸的是,这种技术有一个小的注意事项:只有React和React Native都支持的CSS属性可以用这种方式制作动画。任何涉及到其他CSS属性的东西仍然应该为两个平台保持两个版本的代码。

The postRun animations in React and React Native from one codebaseappeared first onLogRocket Blog.