React: anima il montaggio e lo smontaggio di un singolo componente


97

Qualcosa di così semplice dovrebbe essere realizzato facilmente, ma mi sto strappando i capelli per quanto sia complicato.

Tutto quello che voglio fare è animare il montaggio e lo smontaggio di un componente React, il gioco è fatto. Ecco cosa ho provato finora e perché ogni soluzione non funzionerà:

  1. ReactCSSTransitionGroup - Non sto usando affatto le classi CSS, sono tutti stili JS, quindi non funzionerà.
  2. ReactTransitionGroup- Questa API di livello inferiore è eccezionale, ma richiede di utilizzare una richiamata quando l'animazione è completa, quindi il solo utilizzo delle transizioni CSS non funzionerà qui. Ci sono sempre librerie di animazioni, il che porta al punto successivo:
  3. GreenSock - La licenza è troppo restrittiva per l'uso aziendale IMO.
  4. React Motion - Sembra fantastico, ma TransitionMotionè estremamente confuso ed eccessivamente complicato per ciò di cui ho bisogno.
  5. Ovviamente posso fare solo trucchi come fa Material UI, dove gli elementi vengono renderizzati ma rimangono nascosti ( left: -10000px) ma preferirei non seguire quella strada. Lo considero hacky e voglio che i miei componenti si smontino in modo da pulire e non ingombrare il DOM.

Voglio qualcosa che sia facile da implementare. Sul monte, anima una serie di stili; allo smontaggio, anima lo stesso (o un altro) set di stili. Fatto. Deve anche essere ad alte prestazioni su più piattaforme.

Ho colpito un muro di mattoni qui. Se mi manca qualcosa e c'è un modo semplice per farlo, fammelo sapere.


Di che tipo di animazione stiamo parlando qui?
Pranesh Ravi

Solo qualcosa di semplice, come una dissolvenza in transform: scale
apertura

I punti 1 e 2 mi confondono. Che tipo di animazioni stai usando? Transizioni JS o transizioni CSS?
Pranesh Ravi

1
Non confondere stili / classi CSS (ad esempio .thing { color: #fff; }) con stili JS ( const styles = { thing: { color: '#fff' } }))
ffxsam

Ma il problema è che, quando provi a cambiare lo stile usando javascript, stai effettivamente sostituendo lo stile di un elemento che non darà alcuna transizione.
Pranesh Ravi

Risposte:


102

Questo è un po 'lungo ma ho usato tutti gli eventi e i metodi nativi per ottenere questa animazione. No ReactCSSTransitionGroup, ReactTransitionGroupecc.

Cose che ho usato

  • Metodi del ciclo di vita di reazione
  • onTransitionEnd evento

Come funziona

  • Montare l'elemento in base al supporto di montaggio passato ( mounted) e con lo stile predefinito ( opacity: 0)
  • Dopo il montaggio o l'aggiornamento, utilizzare componentDidMount( componentWillReceivePropsper ulteriori aggiornamenti) per modificare lo stile ( opacity: 1) con un timeout (per renderlo asincrono).
  • Durante lo smontaggio, passa un puntello al componente per identificare unmount, cambia di nuovo lo stile ( opacity: 0) onTransitionEnd,, rimuovi smonta l'elemento dal DOM.

Continua il ciclo.

Leggi il codice, capirai. Se hai bisogno di chiarimenti, lascia un commento.

Spero che questo ti aiuti.

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>


Grazie per questo! Dove l'hai saputo onTransitionEnd? Non lo vedo nei documenti di React.
ffxsam

@ffxsam facebook.github.io/react/docs/events.html È sotto gli eventi di transizione.
Pranesh Ravi

1
Come facevi a sapere cosa faceva, però, la documentazione non spiega nulla. Un'altra domanda: come sapevi di componentWillReceivePropspoter restituire qualcosa? Dove posso leggere di più su questo?
ffxsam

1
@ffxsam onTransitionEnd è un evento JavaScript nativo. Puoi google su di esso. facebook.github.io/react/docs/… ti darà un'idea di componentWillReceiveProps.
Pranesh Ravi

7
BTW penso che ci sia un errore nel tuo codice. Nel tuo Parentcomponente, fai riferimento athis.transitionEnd
ffxsam

14

Utilizzando la conoscenza acquisita dalla risposta di Pranesh, ho escogitato una soluzione alternativa configurabile e riutilizzabile:

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

Utilizzo:

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

E infine, nel rendermetodo di un altro componente :

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

1
E come monti / smonti @ffxsam?

Come è componentWillLeave()e componentWillEnter()sempre chiamato AnimatedMount?
Rokit

Non funziona per me, qui la mia sandbox: codesandbox.io/s/p9m5625v6m
Webwoman

11

Ecco la mia soluzione utilizzando la nuova API hook (con TypeScript), basata su questo post , per ritardare la fase di smontaggio 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;
}

Utilizzo:

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

Link CodeSandbox .


1
soluzione elegante, sarebbe fantastico se avessi aggiunto qualche commento :)
Webwoman

anche perché usare l'estensione typescrypt dato che funziona bene con l'estensione javascript?
Webwoman

anche la tua console restituisce "Impossibile trovare timeout NodeJS spazio dei nomi"
Webwoman

1
@Webwoman Grazie per i tuoi commenti. Non riesco a ricreare il tuo problema segnalato con "Timeout NodeJS", vedi il mio link CodeSandbox sotto la risposta. Per quanto riguarda TypeScript, personalmente preferisco usarlo su JavaScript, sebbene entrambi siano ovviamente praticabili.
deckele

9

Ho contrastato questo problema durante il mio lavoro e, per quanto sembrasse semplice, non è proprio in React. In uno scenario normale in cui si esegue il rendering di qualcosa come:

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

in caso di this.state.showmodifiche i bambini vengono montati / smontati immediatamente.

Un approccio che ho adottato è stato creare un componente wrapper Animatee usarlo in questo modo

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

ora come this.state.showmodifiche, possiamo percepire i cambiamenti dell'elica getDerivedStateFromProps(componentWillReceiveProps)e creare fasi di rendering intermedie per eseguire animazioni.

Un ciclo di fasi potrebbe assomigliare a questo

Iniziamo con Static Stage quando i bambini sono montati o smontati.

Una volta rilevate le showmodifiche ai flag, entriamo nella fase di preparazione in cui calcoliamo le proprietà necessarie come heighte widthda ReactDOM.findDOMNode.getBoundingClientRect().

Quindi entrando in Animate State possiamo usare la transizione CSS per cambiare altezza, larghezza e opacità da 0 ai valori calcolati (oa 0 se si smonta).

Alla fine della transizione, usiamo l' onTransitionEndAPI per tornare allo Staticstadio.

Ci sono molti più dettagli su come le fasi si trasferiscono senza intoppi, ma questa potrebbe essere un'idea generale :)

Se qualcuno fosse interessato, ho creato una libreria React https://github.com/MingruiZhang/react-animate-mount per condividere la mia soluzione. Qualsiasi feedback benvenuto :)


Grazie per il tuo feedback, scusa per la grossolana risposta precedente. Ho aggiunto più dettagli e un diagramma alla mia risposta, spero che questo possa essere più utile per gli altri.
Mingrui Zhang

1
@ MingruiZhang È bello vedere che hai preso positivamente i commenti e hai migliorato la tua risposta. È molto rinfrescante da vedere. Buon lavoro.
Bug

6

Penso che l'uso di Transitionfrom react-transition-groupsia probabilmente il modo più semplice per monitorare il montaggio / smontaggio. È incredibilmente flessibile. Sto usando alcune classi per mostrare quanto sia facile da usare, ma puoi sicuramente collegare le tue animazioni JS utilizzando addEndListenerprop - con cui ho avuto molta fortuna anche usando GSAP.

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

Ed ecco il mio codice.

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
Se usi componenti con stile, puoi semplicemente passare l' showelica H1e fare tutta la logica all'interno del componente con stile. Come ...animation: ${({ show }) => show ? entranceKeyframes : exitKeyframes} 300ms ease-out forwards;
Aleks

2

Movimento del telaio

Installa framer-motion da 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

Per coloro che considerano il movimento reattivo, animare un singolo componente quando monta e smonta può essere difficile da configurare.

C'è una libreria chiamata react-motion-ui-pack che rende molto più facile iniziare questo processo. È un involucro attorno al movimento di reazione, il che significa che ottieni tutti i vantaggi dalla libreria (cioè puoi interrompere l'animazione, fare in modo che più smontaggi avvengano contemporaneamente).

Utilizzo:

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>

Invio definisce quale dovrebbe essere lo stato finale del componente; leave è lo stile che viene applicato quando il componente viene smontato.

Potresti scoprire che dopo aver utilizzato il pacchetto dell'interfaccia utente un paio di volte, la libreria React-Motion potrebbe non essere più così scoraggiante.


Il progetto non è più mantenuto (2018)
Micros


1

Questo può essere fatto facilmente usando il CSSTransitioncomponente da react-transition-group, che è proprio come le librerie che hai menzionato. Il trucco è che devi avvolgere il componente CSSTransition senza un meccanismo mostra / nascondi come faresti normalmente .ie {show && <Child>}...Altrimenti stai nascondendo l' animazione e non funzionerà. Esempio:

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

Ecco i miei 2 centesimi: grazie a @deckele per la sua soluzione. La mia soluzione si basa sulla sua, è la versione del componente stateful, completamente riutilizzabile.

qui la mia sandbox: https://codesandbox.io/s/302mkm1m .

qui il mio 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

Ecco come l'ho risolto nel 2019, mentre creavo uno spinner di caricamento. Sto usando componenti funzionali React.

Ho un componente App genitore che ha un componente Spinner figlio .

L'app ha uno stato per stabilire se l'app si sta caricando o meno. Quando l'app viene caricata, Spinner viene renderizzato normalmente. Quando l'app non si carica ( isLoadingè falso) Spinner viene renderizzato con il 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 ha lo stato per sapere se è nascosto o meno. All'inizio, con gli oggetti di scena e lo stato predefiniti, Spinner viene renderizzato normalmente. La Spinner-fadeInclasse lo anima in dissolvenza in entrata. Quando Spinner riceve l'elica, shouldUnmountlo rende invece con la Spinner-fadeOutclasse, animandolo in dissolvenza.

Tuttavia, volevo anche che il componente si smontasse dopo essere scomparso.

A questo punto ho provato a utilizzare l' onAnimationEndevento sintetico React, simile alla soluzione di @ pranesh-ravi sopra, ma non ha funzionato. Invece ho usato setTimeoutper impostare lo stato su nascosto con un ritardo della stessa lunghezza dell'animazione. Spinner si aggiornerà dopo il ritardo con isHidden === truee non verrà visualizzato nulla.

La chiave qui è che il genitore non smonta il bambino, gli dice quando smontare e il bambino si smonta da solo dopo che si è preso cura della sua attività di smontaggio.

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

Avevo anche un disperato bisogno di un'animazione a componente singola. Mi sono stancato di usare React Motion ma mi stavo tirando i capelli per un problema così banale .. (cosa). Dopo un po 'di googling mi sono imbattuto in questo post sul loro repository git. Spero che aiuti qualcuno ..

Referenziato da e anche il credito . Questo funziona per me fin da ora. Il mio caso d'uso era un modale da animare e smontare in caso di caricamento e scaricamento.

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

So che ci sono molte risposte qui, ma non ho ancora trovato quella che si adatta alle mie esigenze. Voglio:

  • Componenti funzionali
  • Una soluzione che consentirà ai miei componenti di dissolversi facilmente in apertura / chiusura quando vengono montati / smontati.

Dopo molte ore di giocherellare, ho una soluzione che funziona direi al 90%. Ho scritto la limitazione in un blocco di commenti nel codice sottostante. Mi piacerebbe comunque una soluzione migliore, ma questa è la migliore che ho trovato, comprese le altre soluzioni qui.

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

Se hai un componente a cui vuoi applicare la dissolvenza in entrata / uscita, avvolgilo in <Fade>Es. <Fade><MyComponent/><Fade>.

Nota che questo utilizza react-bootstrapper i nomi delle classi e per <Container/>, ma entrambi potrebbero essere facilmente sostituiti con CSS personalizzati e un vecchio normale <div>.


0

Se uso Velocityo la AnimeJSlibreria per animare direttamente il nodo (invece di csso setTimeout), ho scoperto che posso progettare un hookper fornire lo stato dell'animazione one la funzione onToggleper dare il via all'animazione (es. Slidedown, fade).

Fondamentalmente ciò che fa l'hook è attivare e disattivare l'animazione e successivamente aggiornare di onconseguenza. Pertanto possiamo ottenere accuratamente lo stato dell'animazione. Senza farlo risponderei su un 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

L'utilizzo è il seguente,

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

e l'implementazione animata potrebbe essere,

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.