React: animar el montaje y desmontaje de un solo componente


97

Algo así de simple debería lograrse fácilmente, sin embargo, me estoy tirando de los pelos por lo complicado que es.

Todo lo que quiero hacer es animar el montaje y desmontaje de un componente React, eso es todo. Esto es lo que he probado hasta ahora y por qué cada solución no funciona:

  1. ReactCSSTransitionGroup - No estoy usando clases CSS en absoluto, son todos estilos JS, por lo que esto no funcionará.
  2. ReactTransitionGroup- Esta API de nivel inferior es excelente, pero requiere que uses una devolución de llamada cuando la animación esté completa, por lo que solo usar transiciones CSS no funcionará aquí. Siempre hay bibliotecas de animación, lo que lleva al siguiente punto:
  3. GreenSock: la licencia es demasiado restrictiva para el uso comercial, en mi opinión.
  4. React Motion: esto parece genial, pero TransitionMotiones extremadamente confuso y demasiado complicado para lo que necesito.
  5. Por supuesto, puedo hacer trucos como lo hace Material UI, donde los elementos se representan pero permanecen ocultos ( left: -10000px) pero prefiero no seguir ese camino. Lo considero hacky y quiero que mis componentes se desmonten para que se limpien y no abarroten el DOM.

Quiero algo que sea fácil de implementar. En el monte, anime un conjunto de estilos; al desmontar, anime el mismo (u otro) conjunto de estilos. Hecho. También tiene que ser de alto rendimiento en múltiples plataformas.

He chocado contra una pared de ladrillos aquí. Si me falta algo y hay una manera fácil de hacerlo, avíseme.


¿De qué tipo de animación estamos hablando aquí?
Pranesh Ravi

Solo algo simple, como una opacidad de CSS que se desvanece y untransform: scale
ffxsam

Los puntos 1 y 2 me confunden. ¿Qué tipo de animaciones estás usando? ¿Transiciones JS o transiciones CSS?
Pranesh Ravi

1
No confunda estilos / clases CSS (por ejemplo .thing { color: #fff; }) con estilos JS ( const styles = { thing: { color: '#fff' } }))
ffxsam

Pero el problema es que, cuando intentas cambiar el estilo usando javascript, en realidad estás reemplazando el estilo de un elemento que no dará ninguna transición.
Pranesh Ravi

Respuestas:


102

Esto es un poco largo, pero he usado todos los eventos y métodos nativos para lograr esta animación. No ReactCSSTransitionGroup, ReactTransitionGroupetc.

Cosas que he usado

  • Reaccionar métodos de ciclo de vida
  • onTransitionEnd evento

Como funciona esto

  • Monte el elemento en función del soporte de montaje pasado ( mounted) y con el estilo predeterminado ( opacity: 0)
  • Después de montar o actualizar, use componentDidMount( componentWillReceivePropspara más actualizaciones) para cambiar el estilo ( opacity: 1) con un tiempo de espera (para que sea asíncrono).
  • Durante el desmontaje, pase un accesorio al componente para identificar el desmontaje, cambie de nuevo el estilo ( opacity: 0) onTransitionEnd, elimine desmontar el elemento del DOM.

Continúe el ciclo.

Repase el código, lo entenderá. Si necesita alguna aclaración, deje un comentario.

Espero que esto ayude.

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>


¡Gracias por esto! ¿De dónde aprendiste onTransitionEnd? No lo veo en los documentos de React.
ffxsam

@ffxsam facebook.github.io/react/docs/events.html Está debajo de los eventos de transición.
Pranesh Ravi

1
Sin embargo, ¿cómo supiste lo que hizo? La documentación no explica nada. Otra pregunta: ¿cómo supiste que componentWillReceivePropspuedes devolver algo? ¿Dónde puedo leer más sobre eso?
ffxsam

1
@ffxsam onTransitionEnd es un evento de JavaScript nativo. Puedes buscarlo en Google. facebook.github.io/react/docs/… le dará una idea acerca de componentWillReceiveProps.
Pranesh Ravi

7
Por cierto, creo que hay un error en tu código. En su Parentcomponente, se hace referencia athis.transitionEnd
ffxsam

14

Usando el conocimiento obtenido de la respuesta de Pranesh, se me ocurrió una solución alternativa que es configurable y reutilizable:

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>
    }
  }
};

Uso:

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);

Y finalmente, en el rendermétodo de otro componente :

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

1
¿Y cómo se monta / desmonta @ffxsam?

¿Cómo es componentWillLeave()y componentWillEnter()cómo te llaman AnimatedMount?
Rokit

No funciona para mí, aquí mi caja de arena: codesandbox.io/s/p9m5625v6m
Webwoman

11

Aquí está mi solución usando la nueva API de hooks (con TypeScript), basada en esta publicación , para retrasar la fase de desmontaje del componente:

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;
}

Uso:

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>
        </>
    );
}

Enlace CodeSandbox .


1
solución elegante, sería genial si ha agregado algunos comentarios :)
Webwoman

también ¿por qué usar la extensión de typescrypt ya que funciona bien en la extensión de JavaScript?
Webwoman

también su consola devuelve "no se puede encontrar el espacio de nombres NodeJS timeout"
Webwoman

1
@Webwoman Gracias por tus comentarios. No puedo volver a crear su problema informado con "NodeJS timeout", vea mi enlace CodeSandbox debajo de la respuesta. Con respecto a TypeScript, personalmente prefiero usarlo sobre JavaScript, aunque ambos son viables, por supuesto.
deckele

9

Contrarresté este problema durante mi trabajo y, por simple que parezca, realmente no está en React. En un escenario normal en el que renderiza algo como:

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

a medida que this.state.showcambia, los niños se montan / desmontan de inmediato.

Un enfoque que tomé fue crear un componente contenedor Animatey usarlo como

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

ahora como this.state.showcambios, podemos percibir cambios de prop getDerivedStateFromProps(componentWillReceiveProps)y crear etapas de render intermedias para realizar animaciones.

Un ciclo de etapas podría verse así

Empezamos con Static Stage cuando los niños están montados o desmontados.

Una vez que detectamos los showcambios de bandera, ingresamos a Prep Stage donde calculamos las propiedades necesarias como heighty widthdesde ReactDOM.findDOMNode.getBoundingClientRect().

Luego, ingresando al estado animado, podemos usar la transición css para cambiar la altura, el ancho y la opacidad de 0 a los valores calculados (o a 0 si se desmonta).

Al final de la transición, usamos onTransitionEndapi para volver al Staticescenario.

Hay muchos más detalles sobre cómo las etapas se transfieren sin problemas, pero esta podría ser una idea general :)

Si alguien está interesado, creé una biblioteca React https://github.com/MingruiZhang/react-animate-mount para compartir mi solución. Cualquier comentario bienvenido :)


Gracias por sus comentarios, perdón por la cruda respuesta anterior. Agregué más detalles y un diagrama a mi respuesta, espero que esto pueda ser más útil para otros.
Mingrui Zhang

1
@MingruiZhang Es bueno ver que ha tomado los comentarios de manera positiva y ha mejorado su respuesta. Es muy refrescante verlo. Buen trabajo.
Errores

6

Creo que usar Transitionfrom react-transition-groupes probablemente la forma más fácil de realizar un seguimiento del montaje / desmontaje. Es increíblemente flexible. Estoy usando algunas clases para mostrar lo fácil que es de usar, pero definitivamente puedes conectar tus propias animaciones JS utilizando addEndListenerprop, con lo que también he tenido mucha suerte usando GSAP.

Sandbox: https://codesandbox.io/s/k9xl9mkx2o

Y aquí está mi código.

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
Si usa componentes con estilo, simplemente puede pasar showprop ay H1hacer toda la lógica dentro del componente con estilo. Como ...animation: ${({ show }) => show ? entranceKeyframes : exitKeyframes} 300ms ease-out forwards;
Aleks

2

Movimiento del encuadre

Instale framer-motion de 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>
)

1

Para aquellos que están considerando reaccionar-movimiento, animar un solo componente cuando se monta y desmonta puede ser abrumador de configurar.

Hay una biblioteca llamada react-motion-ui-pack que hace que este proceso sea mucho más fácil de comenzar. Es un envoltorio alrededor de react-motion, lo que significa que obtienes todos los beneficios de la biblioteca (es decir, puedes interrumpir la animación, hacer que ocurran varios desmontajes al mismo tiempo).

Uso:

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 define cuál debería ser el estado final del componente; dejar es el estilo que se aplica cuando se desmonta el componente.

Es posible que descubra que una vez que haya usado el paquete de interfaz de usuario un par de veces, la biblioteca de reacción-movimiento podría dejar de ser tan abrumadora.


El proyecto ya no se mantiene (2018)
Micros


1

Esto se puede hacer fácilmente usando el CSSTransitioncomponente de react-transition-group, que es como las bibliotecas que mencionaste. El truco es que necesitas envolver el componente CSSTransition sin un mecanismo de mostrar / ocultar como haría normalmente .IE {show && <Child>}...De lo contrario usted está ocultando la animación y que no funcionará. Ejemplo:

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

Aquí mis 2 centavos: gracias a @deckele por su solución. Mi solución se basa en la suya, es la versión del componente con estado, completamente reutilizable.

aquí mi caja de arena: https://codesandbox.io/s/302mkm1m .

aquí mi 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);

0

Así es como resolví esto en 2019, mientras hacía una ruleta de carga. Estoy usando componentes funcionales de React.

Tengo un componente de la aplicación principal que tiene un componente Spinner secundario .

La aplicación tiene un estado para indicar si la aplicación se está cargando o no. Cuando se carga la aplicación, Spinner se procesa normalmente. Cuando la aplicación no se está cargando ( isLoadinges falso) Spinner se renderiza con el prop 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 tiene un estado para si está oculto o no. Al principio, con los accesorios y el estado predeterminados, Spinner se representa normalmente. La Spinner-fadeInclase lo anima desvaneciéndose. Cuando Spinner recibe el accesorio, shouldUnmountlo renderiza con elSpinner-fadeOut clase en su lugar, animándolo desvaneciéndose.

Sin embargo, también quería que el componente se desmontara después de desvanecerse.

En este punto, intenté usar el onAnimationEndevento sintético React, similar a la solución de @ pranesh-ravi anterior, pero no funcionó. En su lugar, solía setTimeoutconfigurar el estado como oculto con un retraso de la misma duración que la animación. Spinner se actualizará después del retraso con isHidden === truey no se procesará nada.

La clave aquí es que el padre no desmonta al niño, le dice al niño cuándo debe desmontar y el niño se desmonta solo después de que se encarga de su tarea de desmontaje.

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);
    }
}

0

También necesitaba urgentemente la animación de un solo componente. Me cansé de usar React Motion pero me estaba tirando de los pelos por un problema tan trivial ... (cosa). Después de buscar en Google, encontré esta publicación en su repositorio de git. Espero que ayude a alguien ...

Referenciado desde y también el crédito . Esto funciona para mí a partir de ahora. Mi caso de uso fue un modal para animar y desmontar en caso de carga y descarga.

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

Sé que hay muchas respuestas aquí, pero todavía no encontré una que se adapte a mis necesidades. Quiero:

  • Componentes funcionales
  • Una solución que permitirá que mis componentes aparezcan / desaparezcan fácilmente cuando estén montados / desmontados.

Después de muchas horas de tocar el violín, tengo una solución que funciona, diría que al 90%. Escribí la limitación en un bloque de comentarios en el siguiente código. Todavía me encantaría una solución mejor, pero esta es la mejor que he encontrado, incluidas las otras soluciones aquí.

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>
}

Si tiene un componente que desea fundir de entrada / salida, envuélvalo en <Fade>Ej. <Fade><MyComponent/><Fade>.

Tenga en cuenta que esto se usa react-bootstrappara los nombres de clase y para <Container/>, pero ambos podrían reemplazarse fácilmente con CSS personalizado y un archivo antiguo normal <div>.


0

Si uso Velocityo AnimeJSbiblioteca al nodo animado directamente (en lugar de csso setTimeout), a continuación, descubrí que puede diseñar una hookpara proporcionar el estado de la animación ony la funciónonToggle de la animación para iniciar la animación (por ejemplo, deslizamiento, atenuación).

Básicamente, lo que hace el gancho es activar y desactivar la animación, y luego actualizar en onconsecuencia. Por lo tanto, podemos obtener el estado de la animación con precisión. Sin hacerlo respondería ad-hoc 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

El uso es el siguiente,

  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>
  )

y la implementación animada podría ser,

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()
  })
}

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.