React-анимация монтирования и размонтирования одного компонента

что-то такое простое должно быть легко выполнено, но я вытаскиваю свои волосы из-за того, насколько это сложно.

все, что я хочу сделать, это анимировать монтаж и демонтаж компонента React, вот и все. Вот что я пробовал до сих пор, и почему каждое решение не будет работать:

  1. ReactCSSTransitionGroup - Я вообще не использую классы CSS, это все стили JS, поэтому это не сработает.
  2. ReactTransitionGroup - этот API нижнего уровня великолепен, но он требует, чтобы вы использовали обратный вызов когда анимация будет завершена, поэтому просто использование CSS-переходов здесь не будет работать. Всегда есть анимационные библиотеки, что приводит к следующему пункту:
  3. GreenSock-лицензирование слишком ограничительно для использования в бизнесе IMO.
  4. React Motion-это кажется большим, но TransitionMotion чрезвычайно запутанно и слишком сложно для того, что мне нужно.
  5. конечно, я могу просто сделать обман, как делает материал UI, где элементы отображаются, но остаются скрытыми (left: -10000px), но Я бы предпочел не идти этим путем. Я считаю, что это хаки, и я хочу мои компоненты для размонтирования, чтобы они очищались и не загромождали DOM.

Я хочу что-то, что легко для реализации. При монтировании анимируйте набор стилей; при размонтировании анимируйте тот же (или другой) набор стилей. Сделанный. Он также должен быть высокопроизводительным на нескольких платформах.

Я ударился о кирпичную стену здесь. Если я что-то упускаю и есть простой способ сделай это, дай мне знать.

6 ответов


это немного долго, но я использовал все собственные события и методы для достижения этой анимации. Нет!--2-->, ReactTransitionGroup и т. д.

вещи, которые я использовал

  • методы жизненного цикла React
  • onTransitionEnd событие

как это работает

  • установите элемент на основе пропеллера крепления (mounted) и со стилем по умолчанию (opacity: 0)
  • после установки или обновить, использовать componentDidMount (componentWillReceiveProps для дальнейших обновлений), чтобы изменить стиль (opacity: 1) с таймаутом (чтобы сделать его асинхронным).
  • во время размонтирования передайте опору компоненту для идентификации размонтирования, снова измените стиль(opacity: 0), onTransitionEnd, удалите размонтировать элемент из DOM.

продолжить цикл.

пройдись по коду, ты поймешь. Если требуется какое-либо разъяснение, просьба оставить комментарий.

надеюсь, что это помогает.

class App extends React.Component{
  constructor(props) {
    super(props)
    this.transitionEnd = this.transitionEnd.bind(this)
    this.mountStyle = this.mountStyle.bind(this)
    this.unMountStyle = this.unMountStyle.bind(this)
    this.state ={ //base css
      show: true,
      style :{
        fontSize: 60,
        opacity: 0,
        transition: 'all 2s ease',
      }
    }
  }
  
  componentWillReceiveProps(newProps) { //check for the mounted props
    if(!newProps.mounted)
      return this.unMountStyle() //call outro animation when mounted prop is false
    this.setState({ //remount the node when the mounted prop is true
      show: true
    })
    setTimeout(this.mountStyle, 10) //call the into animiation
  }
  
  unMountStyle() { //css for unmount animation
    this.setState({
      style: {
        fontSize: 60,
        opacity: 0,
        transition: 'all 1s ease',
      }
    })
  }
  
  mountStyle() { // css for mount animation
    this.setState({
      style: {
        fontSize: 60,
        opacity: 1,
        transition: 'all 1s ease',
      }
    })
  }
  
  componentDidMount(){
    setTimeout(this.mountStyle, 10) //call the into animiation
  }
  
  transitionEnd(){
    if(!this.props.mounted){ //remove the node on transition end when the mounted prop is false
      this.setState({
        show: false
      })
    }
  }
  
  render() {
    return this.state.show && <h1 style={this.state.style} onTransitionEnd={this.transitionEnd}>Hello</h1> 
  }
}

class Parent extends React.Component{
  constructor(props){
    super(props)
    this.buttonClick = this.buttonClick.bind(this)
    this.state = {
      showChild: true,
    }
  }
  buttonClick(){
    this.setState({
      showChild: !this.state.showChild
    })
  }
  render(){
    return <div>
        <App onTransitionEnd={this.transitionEnd} mounted={this.state.showChild}/>
        <button onClick={this.buttonClick}>{this.state.showChild ? 'Unmount': 'Mount'}</button>
      </div>
  }
}

ReactDOM.render(<Parent />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-with-addons.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

используя знания, полученные из ответа Пранеша, я придумал альтернативное решение, которое настраивается и используется повторно:

const AnimatedMount = ({ unmountedStyle, mountedStyle }) => {
  return (Wrapped) => class extends Component {
    constructor(props) {
      super(props);
      this.state = {
        style: unmountedStyle,
      };
    }

    componentWillEnter(callback) {
      this.onTransitionEnd = callback;
      setTimeout(() => {
        this.setState({
          style: mountedStyle,
        });
      }, 20);
    }

    componentWillLeave(callback) {
      this.onTransitionEnd = callback;
      this.setState({
        style: unmountedStyle,
      });
    }

    render() {
      return <div
        style={this.state.style}
        onTransitionEnd={this.onTransitionEnd}
      >
        <Wrapped { ...this.props } />
      </div>
    }
  }
};

использование:

import React, { PureComponent } from 'react';

class Thing extends PureComponent {
  render() {
    return <div>
      Test!
    </div>
  }
}

export default AnimatedMount({
  unmountedStyle: {
    opacity: 0,
    transform: 'translate3d(-100px, 0, 0)',
    transition: 'opacity 250ms ease-out, transform 250ms ease-out',
  },
  mountedStyle: {
    opacity: 1,
    transform: 'translate3d(0, 0, 0)',
    transition: 'opacity 1.5s ease-out, transform 1.5s ease-out',
  },
})(Thing);

и, наконец, в другом компонента render способ:

return <div>
  <ReactTransitionGroup>
    <Thing />
  </ReactTransitionGroup>
</div>

я поднял эту проблему во время моей работы, и просто, как казалось, это действительно не реагируют. В обычном сценарии, где вы визуализируете что-то вроде:

this.state.show ? {childen} : null;

as this.state.show изменения дети монтируются / размонтируются сразу же.

один подход я взял это создание компонента фантик Animate и использовать его как

<Animate show={this.state.show}>
  {childen}
</Animate>

теперь this.state.show изменения, мы можем воспринимать изменения опоры с getDerivedStateFromProps(componentWillReceiveProps) и создать промежуточные этапы рендеринга для выполнения анимации.

A stage cycle might look like this

начнем с Статический Этап когда дети установлены или unmounted.

как только мы обнаружить show флаг меняется, вводим Подготовительный Этап где мы вычисляем необходимые свойства, такие как height и width С ReactDOM.findDOMNode.getBoundingClientRect().

задание Государственный Анимировать мы можем использовать CSS-перехода для изменения высоты, ширины и непрозрачности от 0 до расчетных значений (или до 0 если размонтирование).

В конце перехода, мы используем onTransitionEnd API, чтобы вернуться к Static этап.

есть гораздо больше деталей, как этапы передачи плавно, но это может быть общая идея:)

если кто-то заинтересован, я создал библиотеку React https://github.com/MingruiZhang/react-animate-mount поделиться своим решением. Любая обратная связь добро пожаловать:)


Что делать, если onMount вы добавляете другое имя класса, в котором есть переход, и onUnMount вы удаляете это имя класса?


для тех, кто рассматривает react-motion, анимация одного компонента, когда он монтируется и размонтируется, может быть подавляющей для настройки.

есть библиотека под названием react-motion-ui-pack что делает этот процесс намного проще для начала. Это обертка вокруг react-motion, что означает, что вы получаете все преимущества от библиотеки (т. е. вы можете прерывать анимацию, иметь несколько размонтированных одновременно).

использование:

import Transition from 'react-motion-ui-pack'

<Transition
  enter={{ opacity: 1, translateX: 0 }}
  leave={{ opacity: 0, translateX: -100 }}
  component={false}
>
  { this.state.show &&
      <div key="hello">
        Hello
      </div>
  }
</Transition>

Enter определяет конечное состояние компонента; leave-это стиль, применяемый при размонтировании компонента.

вы можете обнаружить, что после того, как вы использовали пакет пользовательского интерфейса пару раз, библиотека react-motion может быть не такой сложной.


Анимация переходов ввода и выхода намного проще с react-move.

пример на codesandbox