专栏名称: 前端外刊评论
最新、最前沿的前端资讯,最有深入、最干前端相关的技术译文。
目录
相关文章推荐
商务河北  ·  经开区“美•强•优”三重奏 ·  16 小时前  
奇舞精选  ·  从 DeepSeek 看25年前端的一个小趋势 ·  昨天  
奇舞精选  ·  从 DeepSeek 看25年前端的一个小趋势 ·  昨天  
前端早读课  ·  【第3452期】React 开发中使用开闭原则 ·  2 天前  
前端早读课  ·  【第3451期】前端 TypeError ... ·  3 天前  
51好读  ›  专栏  ›  前端外刊评论

React Web 动画的 5 种创建方式,每一种都不简单

前端外刊评论  · 公众号  · 前端  · 2017-08-16 21:59

正文

感谢 @枫上雾棋 的投稿,本文翻译自 React Animations in Depth。

以前一直投入在 React Native 中,写动画的时候不是用 CSS 中的 transitions / animations,就是依赖像 GreenSock 这样的库,最近转向 Web,在 Tweet 得到很多大佬关于React Web 动画 的回应,于是决定分享给大家,如有其他见解,非常欢迎在下面评论中交流

以下便是本文要分享的创建 React 动画 的几种方式

  • CSS animation

  • JS Style

  • React Motion

  • Animated

  • Velocity React

下面,勒次个特斯大特一特

CSS animation

给元素添加 class 是最简单,最常见的书写方式。如果你的 app 正在使用 CSS,那么这将是你最愉快的选择

赞同者: 我们只需修改 opacity 和 transform 这样的属性,就可构建基本的动画,而且,在组件中,我们可以非常容易地通过 state 去更新这些值

反对者:这种方式并不跨平台,在 React Native 中就不适用,而且,对于较复杂的动画,这种方式难以控制

接下来,我们通过一个实例来体验一下这种创建方式:当 input focus 的时候,我们增加它的宽度

首先,我们要创建两个 input 要用到的 class

  1. .input {

  2.  width: 150px;

  3.  padding: 10px;

  4.  font-size: 20px;

  5.  border: none;

  6.  border-radius: 4px;

  7.  background-color: #dddddd;

  8.  transition: width .35s linear;

  9.  outline: none;

  10. }

  11. .input-focused {

  12.  width: 240px;

  13. }

一个是它原始的样式,一个是它 focus 后的样式

下面,我们就开始书写我们的 React 组件

在此,推荐一个 在线的 React VS Code IDE,真的很强大,读者不想构建自己的 React app,可以在其中检验以下代码的正确性

  1. class App extends Component {

  2.  state = {

  3.    focused: false,

  4.  }

  5.  componentDidMount() {

  6.    this ._input.addEventListener('focus', this.focus);

  7.    this._input.addEventListener('blur', this.focus);

  8.  }

  9.  focus = () => {

  10.    this.setState(prevState => ({

  11.      focused: !prevState.focused,

  12.    }));

  13.   }

  14.  render() {

  15.    return (

  16.      <div className="App">

  17.        <div className="container">

  18.          <input

  19.            ref={input => this ._input = input}

  20.            className={['input', this.state.focused && 'input-focused'].join(' ')}

  21.          />

  22.        div>

  23.      div>

  24.    );

  25.  }

  26. }

  • 我们有一个 focused 的 state,初始值为 false,我们通过更新该值来创建我们的动画

  • 在 componentDidMount 中,我们添加两个监听器,一个 focus,一个 blur,指定的回调函数都是 focus

  • focus 方法会获取之前 focused 的值,并负责切换该值

  • 在 render 中,我们通过 state 来改变 input 的 classNames,从而实现我们的动画

JS Style

JavaScipt styles 跟 CSS 中的 classes 类似,在 JS 文件中,我们就可以拥有所有逻辑

赞同者:跟 CSS动画 一样,且它的表现更加清晰。它也不失为一个好方法,可以不必依赖任何 CSS

反对者:跟 CSS动画 一样,也是不跨平台的,且动画一旦复杂,也难以控制

在下面的实例中,我们将创建一个 input,当用户输入时,我们将一个 button 从 disable 转变为 enable

  1. class App extends Component {

  2.  state = {

  3.    disabled: true,

  4.  }

  5.  onChange = (e) => {

  6.    const length = e.target.value.length;

  7.    if (length > 0) {

  8.      this.setState({ disabled: false });

  9.    } else {

  10.      this.setState({ disabled: true });

  11.     }

  12.  }

  13.  render() {

  14.    const { disabled } = this.state;

  15.    const label = disabled ? 'Disabled' : 'Submit';

  16.     return (

  17.      <div style={styles.App}>

  18.        <input

  19.          style={styles.input}

  20.          onChange={this.onChange}

  21.         />

  22.        <button

  23.          style={Object.assign({},

  24.            styles.button,

  25.            !this.state.disabled && styles.buttonEnabled

  26.           )}

  27.          disabled={disabled}

  28.        >

  29.          {label}

  30.        button>

  31.      div>

  32.     );

  33.  }

  34. }

  35. const styles = {

  36.   App: {

  37.    display: 'flex',

  38.    justifyContent: 'left',

  39.  },

  40.  input: {

  41.    marginRight : 10,

  42.    padding: 10,

  43.    width: 190,

  44.    fontSize: 20,

  45.    border : 'none',

  46.    backgroundColor: '#ddd',

  47.    outline: 'none',

  48.  },

  49.  button: {

  50.    width : 90,

  51.    height: 43,

  52.    fontSize: 17,

  53.    border: 'none',

  54.    borderRadius: 4,

  55.    transition: '.25s all',

  56.    cursor: 'pointer',

  57.  },

  58.  buttonEnabled: {

  59.    width : 120,

  60.    backgroundColor: '#ffc107',

  61.  }

  62. }

  • 我们有一个 disabled 的 state,初始值为 true

  • onChange 方法会获取用户的输入,当输入非空时,就切换 disabled 的值

  • 根据 disabled 的值,确定是否将 buttonEnabled 添加到 button 中

React Motion

React Motion 是 Cheng Lou 书写的一个非常不错的开源项目。它的思想是你可以对Motion 组件 进行简单的样式设置,然后你就可以在回调函数中通过这些值,享受动画带来的乐趣

对于绝大多数的动画组件,我们往往不希望对动画属性(宽高、颜色等)的变化时间做硬编码处理,react-motion 提供的 spring 函数就是用来解决这一需求的,它可以逼真地模仿真实的物理效果,也就是我们常见的各类缓动效果

下面是一个森破的示例

  1. < Motion style={{ x: spring(this.state.x) }}>

  2.  {

  3.    ({ x }) =>

  4.      <div style={{ transform: `translateX(${x}px)` }} />

  5.  }

  6. Motion>

这是官方提供的几个 demo,真的可以是不看不知道,一看吓一跳

  • Chat Heads

  • Draggable Balls

  • TodoMVC List Transition

  • Water Ripples

  • Draggable List

赞同者:React Motion 可以在 React Web 中使用,也可以在 React Native 中使用,因为它是跨平台的。其中的 spring 概念最开始对我来说感觉挺陌生,然而上手之后,发现它真的很神奇,并且,它有很详细的 API

反对者:在某些情况下,他不如纯 CSS / JS 动画,虽然它有不错的 API,容易上手,但也需要学习成本

为了使用它,首先我们要用 yarn 或 npm 安装它

  1. yarn add react -motion

在下面的实例中,我们将创建一个 dropdown 菜单,当点击按钮时,下拉菜单友好展开

  1. class App extends Component {

  2.  state = {

  3.    height : 38,

  4.  }

  5.  animate = () => {

  6.     this.setState((state) => ({ height: state.height === 233 ? 38 : 233 }));

  7.  }

  8.  render() {

  9.     return (

  10.      <div className="App">

  11.        <div style={styles.button} onClick={this.animate}>Animatediv>

  12.         <Motion

  13.          style={{ height: spring(this.state.height) }}

  14.        >

  15.          {

  16.             ({ height }) =>

  17.            <div style={Object.assign({}, styles.menu, { height } )}>

  18.              <p style={styles.selection}>Selection 1p>

  19.              < p style={styles.selection}>Selection 2p>

  20.              <p style={styles.selection}>Selection 3p>

  21.              <p style={styles.selection}>Selection 4p>

  22.              < p style={styles.selection}>Selection 5p>

  23.              <p style={styles.selection}>Selection 6p>

  24.            div>

  25.          }

  26.         Motion>

  27.      div>

  28.    );

  29.  }

  30. }

  31. const styles = {

  32.  menu: {

  33.    marginTop : 20,

  34.    width: 300,

  35.    border: '2px solid #ddd',

  36.    overflow: 'hidden',

  37.   },

  38.  button: {

  39.    display: 'flex',

  40.    width: 200,

  41.    height : 45,

  42.    justifyContent: 'center',

  43.    alignItems: 'center',

  44.    border: 'none',

  45.    borderRadius : 4,

  46.    backgroundColor: '#ffc107',

  47.    cursor: 'pointer',

  48.   },

  49.  selection: {

  50.    margin: 0,

  51.    padding: 10,

  52.    borderBottom : '1px solid #ededed',

  53.  },

  54. }

  • 我们从 react-motion 中 import Motion 和 spring

  • 我们有一个 height 的 state,初始值为 38,代表 menu 的高度

  • animate 方法设置 menu 的 height,如果 原 height 为 38,则设置新 height 为 233,如果 原 height 为 233,则设置 新 height 为 38

  • 在 render 中,我们使用 Motion 组件 包装整个 p 标签 列表,将 this.state.height 的当前值设为组件的 height,然后在组件的回调函数中使用该值作为整个下拉的高度

  • 当按钮被点击时,我们通过 this.animate 切换下拉的高度

Animated

Animated 是基于 React Native 使用的同一个动画库建立起来的

它背后的思想是创建声明式动画,通过传递配置对象来控制动画

赞同者:跨平台,它在 React Native 中已经非常稳定,如果你在 React Native 中使用过,那么你将不用再重复学习。其中的 interpolate 是一个神奇的插值函数,我们将在下面看到

反对者:基于 Twitter 的交流,它目前貌似不是 100% 的稳定,在老的浏览器中的,存在前缀和性能的问题,而且,它也有学习成本

为了使用 Animated,我们首先还是要用 yarn 或 npm 安装它

  1. yarn add animated

在下面的实例中,我们将模拟在提交表单成功后显示的动画 message

  1. import Animated from 'animated/lib/targets/react-dom';

  2. import Easing from 'animated/lib/Easing';

  3. class AnimatedApp extends Component {

  4.  animatedValue = new Animated.Value(0);

  5.  animate = () => {

  6.     this.animatedValue.setValue(0);

  7.    Animated.timing(

  8.      this.animatedValue,

  9.      {

  10.        toValue: 1,

  11.        duration: 1000,

  12.        easing : Easing.elastic(1),

  13.      }

  14.    ).start();

  15.   }

  16.  render() {

  17.     const marginLeft = this.animatedValue.interpolate({

  18.      inputRange: [0, 1],

  19.      outputRange: [-120, 0],

  20.     });

  21.    return (

  22.       <div className="App">

  23.          <div style={styles.button} onClick={this.animate}>Animatediv>

  24.          <Animated.div

  25.            style={

  26.              Object.assign(

  27.                {},

  28.                styles .box,

  29.                { opacity: this.animatedValue, marginLeft })}

  30.           >

  31.            <p>Thanks for your submission!p>

  32.          Animated.div>

  33.       div>- 我们将 `animatedValue`

  34. `marginLeft`  作为 `Animated.div ` `style` 属性,  );

  35.   }

  36. }

  37. const styles = {

  38.  button: {

  39.    display: 'flex',

  40.    width : 125,

  41.    height: 50,

  42.    justifyContent: 'center',

  43.    alignItems : 'center',

  44.    border: 'none',

  45.    borderRadius: 4,

  46.    backgroundColor : '#ffc107',

  47.    cursor: 'pointer',

  48.  },

  49.  box:







请到「今天看啥」查看全文