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

103

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

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

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

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

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

ffxsam
источник
О какой анимации идет речь?
Пранеш Рави
Просто что - то простое, как CSS непрозрачности замирания в иtransform: scale
ffxsam
Пункты 1 и 2 меня смущают. Какие анимации вы используете? Переходы JS или переходы CSS?
Пранеш Рави
1
Не путайте стили / классы CSS (например .thing { color: #fff; }) со стилями JS ( const styles = { thing: { color: '#fff' } }))
ffxsam
Но проблема в том, что когда вы пытаетесь изменить стиль с помощью javascript, вы фактически заменяете стиль элемента, который не дает никакого перехода.
Пранеш Рави

Ответы:

104

Это немного длинновато, но я использовал все собственные события и методы для создания этой анимации. Нет ReactCSSTransitionGroup, ReactTransitionGroupи т.д.

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

  • Реагировать на методы жизненного цикла
  • 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 animation
  }
  
  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 animation
  }
  
  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>

Пранеш Рави
источник
Спасибо за это! Откуда вы узнали onTransitionEnd? Я не вижу этого в документации по React.
ffxsam
@ffxsam facebook.github.io/react/docs/events.html Он находится под событиями перехода.
Пранеш Рави,
1
Но как вы узнали, что он сделал, документация ничего не объясняет. Другой вопрос: откуда ты знал, componentWillReceivePropsможно что-то вернуть? Где я могу прочитать об этом подробнее?
ffxsam
1
@ffxsam onTransitionEnd - это собственное событие JavaScript. Вы можете погуглить об этом. facebook.github.io/react/docs/… даст вам представление о componentWillReceiveProps.
Пранеш Рави,
7
Кстати, я думаю, что в вашем коде есть ошибка. В вашем Parentкомпоненте вы ссылаетесьthis.transitionEnd
ffxsam
15

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

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>
ffxsam
источник
1
А как монтировать / размонтировать @ffxsam?
Как тебя componentWillLeave()и componentWillEnter()тебя вызывают AnimatedMount?
Rokit
У меня не работает, вот моя песочница: codeandbox.io/s/p9m5625v6m
Webwoman
15

Вот мое решение с использованием нового API хуков (с TypeScript), основанное на этом посте , для задержки фазы размонтирования компонента:

function useDelayUnmount(isMounted: boolean, delayTime: number) {
    const [ shouldRender, setShouldRender ] = useState(false);

    useEffect(() => {
        let timeoutId: number;
        if (isMounted && !shouldRender) {
            setShouldRender(true);
        }
        else if(!isMounted && shouldRender) {
            timeoutId = setTimeout(
                () => setShouldRender(false), 
                delayTime
            );
        }
        return () => clearTimeout(timeoutId);
    }, [isMounted, delayTime, shouldRender]);
    return shouldRender;
}

Применение:

const Parent: React.FC = () => {
    const [ isMounted, setIsMounted ] = useState(true);
    const shouldRenderChild = useDelayUnmount(isMounted, 500);
    const mountedStyle = {opacity: 1, transition: "opacity 500ms ease-in"};
    const unmountedStyle = {opacity: 0, transition: "opacity 500ms ease-in"};

    const handleToggleClicked = () => {
        setIsMounted(!isMounted);
    }

    return (
        <>
            {shouldRenderChild && 
                <Child style={isMounted ? mountedStyle : unmountedStyle} />}
            <button onClick={handleToggleClicked}>Click me!</button>
        </>
    );
}

Ссылка CodeSandbox .

Deckele
источник
2
элегантное решение, было бы здорово, если бы вы добавили несколько комментариев :)
Webwoman
также зачем использовать расширение typescrypt, если оно хорошо работает с расширением javascript?
Webwoman
также ваша консоль возвращает «не удается найти пространство имен NodeJS, тайм-аут»
Webwoman
1
@Webwoman Спасибо за ваши комментарии. Я не могу воссоздать указанную вами проблему с "таймаутом NodeJS", см. Ссылку на мой CodeSandbox под ответом. Что касается TypeScript, я лично предпочитаю использовать его, а не JavaScript, хотя оба варианта, конечно, жизнеспособны.
Deckele
10

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

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

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

Один из подходов, который я выбрал, - это создать компонент-оболочку Animateи использовать его как

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

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

Цикл стадии может выглядеть так

Мы начинаем со статической сцены, когда дети садятся или снимаются.

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

Затем, войдя в Animate State, мы можем использовать css-переход, чтобы изменить высоту, ширину и непрозрачность от 0 до вычисленных значений (или до 0 при демонтировании).

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

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

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

Мингруй Чжан
источник
Спасибо за отзыв, извините за грубый ответ ранее. Я добавил больше деталей и схему к своему ответу, надеюсь, это может быть более полезным для других.
Mingrui Zhang
2
@MingruiZhang Приятно видеть, что вы положительно восприняли комментарии и улучшили свой ответ. Это очень приятно видеть. Хорошая работа.
Ошибки
6

Я думаю, что использование Transitionfrom react-transition-group- это, вероятно, самый простой способ отследить монтаж / демонтаж. Это невероятно гибко. Я использую некоторые классы, чтобы показать, насколько легко их использовать, но вы определенно можете подключить свои собственные JS-анимации, используя addEndListenerопору, с которой мне тоже очень повезло, используя GSAP.

Песочница: https://codesandbox.io/s/k9xl9mkx2o

А вот мой код.

import React, { useState } from "react";
import ReactDOM from "react-dom";
import { Transition } from "react-transition-group";
import styled from "styled-components";

const H1 = styled.h1`
  transition: 0.2s;
  /* Hidden init state */
  opacity: 0;
  transform: translateY(-10px);
  &.enter,
  &.entered {
    /* Animate in state */
    opacity: 1;
    transform: translateY(0px);
  }
  &.exit,
  &.exited {
    /* Animate out state */
    opacity: 0;
    transform: translateY(-10px);
  }
`;

const App = () => {
  const [show, changeShow] = useState(false);
  const onClick = () => {
    changeShow(prev => {
      return !prev;
    });
  };
  return (
    <div>
      <button onClick={onClick}>{show ? "Hide" : "Show"}</button>
      <Transition mountOnEnter unmountOnExit timeout={200} in={show}>
        {state => {
          let className = state;
          return <H1 className={className}>Animate me</H1>;
        }}
      </Transition>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Шалана
источник
1
Если вы используете стилизованные компоненты, вы можете просто передать showопору H1и выполнять всю логику внутри стилизованного компонента. Как ...animation: ${({ show }) => show ? entranceKeyframes : exitKeyframes} 300ms ease-out forwards;
Алекс
3

Framer motion

Установите framer-motion из npm.

import { motion, AnimatePresence } from "framer-motion"

export const MyComponent = ({ isVisible }) => (
  <AnimatePresence>
    {isVisible && (
      <motion.div
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
      />
    )}
  </AnimatePresence>
)
tony95
источник
1

Для тех, кто рассматривает реакцию на движение, анимация отдельного компонента при его монтировании и размонтировании может быть сложной задачей.

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

Применение:

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 - это стиль, применяемый при размонтировании компонента.

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

Бьорн Холдт
источник
Проект больше не поддерживается (2018)
Micros
1

Это можно легко сделать с помощью CSSTransitionкомпонента из react-transition-group, который аналогичен упомянутым вами библиотекам. Хитрость в том, что вам нужно обернуть компонент CSSTransition без механизма отображения / скрытия, как вы это обычно делаете. Т.е. в {show && <Child>}...противном случае вы скрываете анимацию, и она не будет работать. Пример:

ParentComponent.js

import React from 'react';
import {CSSTransition} from 'react-transition-group';

function ParentComponent({show}) {
return (
  <CSSTransition classes="parentComponent-child" in={show} timeout={700}>
    <ChildComponent>
  </CSSTransition>
)}


ParentComponent.css

// animate in
.parentComponent-child-enter {
  opacity: 0;
}
.parentComponent-child-enter-active {
  opacity: 1;
  transition: opacity 700ms ease-in;
}
// animate out
.parentComponent-child-exit {
  opacity: 1;
}
.parentComponent-child-exit-active {
  opacity: 0;
  transition: opacity 700ms ease-in;
}
Лорен
источник
0

Вот мои 2cents: спасибо @deckele за его решение. Мое решение основано на его версии, это компонентная версия с сохранением состояния, полностью повторно используемая.

вот моя песочница: https://codesandbox.io/s/302mkm1m .

вот мой snippet.js:

import ReactDOM from "react-dom";
import React, { Component } from "react";
import style from  "./styles.css"; 

class Tooltip extends Component {

  state = {
    shouldRender: false,
    isMounted: true,
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.state.shouldRender !== nextState.shouldRender) {
      return true
    }
    else if (this.state.isMounted !== nextState.isMounted) {
      console.log("ismounted!")
      return true
    }
    return false
  }
  displayTooltip = () => {
    var timeoutId;
    if (this.state.isMounted && !this.state.shouldRender) {
      this.setState({ shouldRender: true });
    } else if (!this.state.isMounted && this.state.shouldRender) {
      timeoutId = setTimeout(() => this.setState({ shouldRender: false }), 500);
      () => clearTimeout(timeoutId)
    }
    return;
  }
  mountedStyle = { animation: "inAnimation 500ms ease-in" };
  unmountedStyle = { animation: "outAnimation 510ms ease-in" };

  handleToggleClicked = () => {
    console.log("in handleToggleClicked")
    this.setState((currentState) => ({
      isMounted: !currentState.isMounted
    }), this.displayTooltip());
  };

  render() {
    var { children } = this.props
    return (
      <main>
        {this.state.shouldRender && (
          <div className={style.tooltip_wrapper} >
            <h1 style={!(this.state.isMounted) ? this.mountedStyle : this.unmountedStyle}>{children}</h1>
          </div>
        )}

        <style>{`

           @keyframes inAnimation {
    0% {
      transform: scale(0.1);
      opacity: 0;
    }
    60% {
      transform: scale(1.2);
      opacity: 1;
    }
    100% {
      transform: scale(1);  
    }
  }

  @keyframes outAnimation {
    20% {
      transform: scale(1.2);
    }
    100% {
      transform: scale(0);
      opacity: 0;
    }
  }
          `}
        </style>
      </main>
    );
  }
}


class App extends Component{

  render(){
  return (
    <div className="App"> 
      <button onClick={() => this.refs.tooltipWrapper.handleToggleClicked()}>
        click here </button>
      <Tooltip
        ref="tooltipWrapper"
      >
        Here a children
      </Tooltip>
    </div>
  )};
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Webwoman
источник
0

Вот как я решил эту проблему в 2019 году при создании счетчика загрузки. Я использую функциональные компоненты React.

У меня есть родительский компонент приложения , у которого есть дочерний компонент Spinner .

В приложении есть состояние того, загружается приложение или нет. Когда приложение загружается, Spinner отображается нормально. Когда приложение не загружается ( isLoadingfalse), Spinner отображается с помощью свойства shouldUnmount.

App.js :

import React, {useState} from 'react';
import Spinner from './Spinner';

const App = function() {
    const [isLoading, setIsLoading] = useState(false);

    return (
        <div className='App'>
            {isLoading ? <Spinner /> : <Spinner shouldUnmount />}
        </div>
    );
};

export default App;

Спиннер имеет статус , скрыт он или нет. Вначале, со свойствами и состоянием по умолчанию, Spinner отображается нормально. Spinner-fadeInКласс одушевляет это угасание. Когда Spinner получает опору shouldUnmountон делает с Spinner-fadeOutклассом вместо этого, оживляя его выведение.

Однако я также хотел, чтобы компонент размонтировался после исчезновения.

На этом этапе я попытался использовать onAnimationEndсинтетическое событие React, подобное решению @ pranesh-ravi выше, но оно не сработало. Вместо этого я setTimeoutустанавливал скрытое состояние с задержкой, равной длине анимации. Spinner обновится после задержки с isHidden === true, и ничего не будет отображаться.

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

Spinner.js :

import React, {useState} from 'react';
import './Spinner.css';

const Spinner = function(props) {
    const [isHidden, setIsHidden] = useState(false);

    if(isHidden) {
        return null

    } else if(props.shouldUnmount) {
        setTimeout(setIsHidden, 500, true);
        return (
            <div className='Spinner Spinner-fadeOut' />
        );

    } else {
        return (
            <div className='Spinner Spinner-fadeIn' />
        );
    }
};

export default Spinner;

Spinner.css:

.Spinner {
    position: fixed;
    display: block;
    z-index: 999;
    top: 50%;
    left: 50%;
    margin: -40px 0 0 -20px;
    height: 40px;
    width: 40px;
    border: 5px solid #00000080;
    border-left-color: #bbbbbbbb;
    border-radius: 40px;
}

.Spinner-fadeIn {
    animation: 
        rotate 1s linear infinite,
        fadeIn .5s linear forwards;
}

.Spinner-fadeOut {
    animation: 
        rotate 1s linear infinite,
        fadeOut .5s linear forwards;
}

@keyframes fadeIn {
    0% {
        opacity: 0;
    }
    100% {
        opacity: 1;
    }
}
@keyframes fadeOut {
    0% {
        opacity: 1;
    }
    100% {
        opacity: 0;
    }
}

@keyframes rotate {
    100% {
        transform: rotate(360deg);
    }
}
mjw
источник
0

Я также остро нуждался в однокомпонентной анимации. Я устал использовать React Motion, но я тянул за волосы из-за такой тривиальной проблемы ... (я вещь). После некоторого поиска в Google я наткнулся на этот пост в их репозитории git. Надеюсь, это кому-то поможет ..

Ссылка From & также кредит . На данный момент это работает для меня. Мой вариант использования был модальным для анимации и размонтирования в случае загрузки и выгрузки.

class Example extends React.Component {
  constructor() {
    super();
    
    this.toggle = this.toggle.bind(this);
    this.onRest = this.onRest.bind(this);

    this.state = {
      open: true,
      animating: false,
    };
  }
  
  toggle() {
    this.setState({
      open: !this.state.open,
      animating: true,
    });
  }
  
  onRest() {
    this.setState({ animating: false });
  }
  
  render() {
    const { open, animating } = this.state;
    
    return (
      <div>
        <button onClick={this.toggle}>
          Toggle
        </button>
        
        {(open || animating) && (
          <Motion
            defaultStyle={open ? { opacity: 0 } : { opacity: 1 }}
            style={open ? { opacity: spring(1) } : { opacity: spring(0) }}
            onRest={this.onRest}
          >
            {(style => (
              <div className="box" style={style} />
            ))}
          </Motion>
        )}
      </div>
    );
  }
}

Рахул Сингх
источник
0

Я знаю, что здесь есть много ответов, но я все еще не нашел того, который подходит для моих нужд. Я хочу:

  • Функциональные компоненты
  • Решение, которое позволит моим компонентам легко исчезать / появляться при установке / отключении.

После многих часов возни у меня есть решение, которое работает, я бы сказал, на 90%. Я написал ограничение в блоке комментариев в приведенном ниже коде. Я все еще хотел бы лучшее решение, но это лучшее, что я нашел, включая другие решения здесь.

const TIMEOUT_DURATION = 80 // Just looked like best balance of silky smooth and stop delaying me.

// Wrap this around any views and they'll fade in and out when mounting /
// unmounting.  I tried using <ReactCSSTransitionGroup> and <Transition> but I
// could not get them to work.  There is one major limitation to this approach:
// If a component that's mounted inside of <Fade> has direct prop changes,
// <Fade> will think that it's a new component and unmount/mount it.  This
// means the inner component will fade out and fade in, and things like cursor
// position in forms will be reset. The solution to this is to abstract <Fade>
// into a wrapper component.

const Fade: React.FC<{}> = ({ children }) => {
  const [ className, setClassName ] = useState('fade')
  const [ newChildren, setNewChildren ] = useState(children)

  const effectDependency = Array.isArray(children) ? children : [children]

  useEffect(() => {
    setClassName('fade')

    const timerId = setTimeout(() => {
      setClassName('fade show')
      setNewChildren(children)
    }, TIMEOUT_DURATION)

    return () => {
      clearTimeout(timerId)
    }   

  }, effectDependency)

  return <Container fluid className={className + ' p-0'}>{newChildren}</Container>
}

Если у вас есть компонент, который вы хотите усилить / затухать, оберните его в <Fade>Ex. <Fade><MyComponent/><Fade>.

Обратите внимание, что здесь используются react-bootstrapимена классов и for <Container/>, но оба могут быть легко заменены пользовательским CSS и обычным старым <div>.

Аарон Салливан
источник
0

Если я использую библиотеку Velocityили AnimeJSдля непосредственной анимации узла (вместо cssили setTimeout), то я обнаружил, что могу спроектировать объект, hookчтобы предоставить статус анимации onи функцию onToggleдля запуска анимации (например, скольжение вниз, исчезновение).

По сути, ловушка включает и выключает анимацию, а затем обновляет ее onсоответствующим образом. Таким образом, мы можем точно получить статус анимации. Без этого ответил бы по специальному запросу duration.

/**
 * A hook to provide animation status.
 * @class useAnimate
 * @param {object} _                props
 * @param {async} _.animate         Promise to perform animation
 * @param {object} _.node           Dom node to animate
 * @param {bool} _.disabled         Disable animation
 * @returns {useAnimateObject}      Animate status object
 * @example
 *   const { on, onToggle } = useAnimate({
 *    animate: async () => { },
 *    node: node
 *  })
 */

import { useState, useCallback } from 'react'

const useAnimate = ({
  animate, node, disabled,
}) => {
  const [on, setOn] = useState(false)

  const onToggle = useCallback(v => {
    if (disabled) return
    if (v) setOn(true)
    animate({ node, on: v }).finally(() => {
      if (!v) setOn(false)
    })
  }, [animate, node, disabled, effect])

  return [on, onToggle]
}

export default useAnimate

Использование следующее,

  const ref = useRef()
  const [on, onToggle] = useAnimate({
    animate: animateFunc,
    node: ref.current,
    disabled
  })
  const onClick = () => { onToggle(!on) }

  return (
      <div ref={ref}>
          {on && <YOUROWNCOMPONENT onClick={onClick} /> }
      </div>
  )

и анимированная реализация может быть,

import anime from 'animejs'

const animateFunc = (params) => {
  const { node, on } = params
  const height = on ? 233 : 0
  return new Promise(resolve => {
    anime({
      targets: node,
      height,
      complete: () => { resolve() }
    }).play()
  })
}

Windmaomao
источник
0

Вы всегда можете использовать методы жизненного цикла React, но react-transition-group - безусловно, самая удобная библиотека для анимаций, с которой я столкнулся, независимо от того, используете ли вы styled-componentsили простой CSS. Это особенно полезно, когда вы хотите отслеживать монтирование и размонтирование вашего компонента и соответственно визуализировать анимацию. Используйте Transitionсо стилями-компонентами и CSSTransitionкогда вы используете простые имена классов css.

Webmessi
источник