React - anime le montage et le démontage d'un seul composant

97

Quelque chose d'aussi simple devrait être facilement accompli, mais je m'arrache les cheveux en raison de sa complexité.

Tout ce que je veux faire, c'est animer le montage et le démontage d'un composant React, c'est tout. Voici ce que j'ai essayé jusqu'à présent et pourquoi chaque solution ne fonctionnera pas:

  1. ReactCSSTransitionGroup - Je n'utilise pas du tout de classes CSS, ce sont tous des styles JS, donc cela ne fonctionnera pas.
  2. ReactTransitionGroup- Cette API de niveau inférieur est excellente, mais elle vous oblige à utiliser un rappel lorsque l'animation est terminée, donc l'utilisation de transitions CSS ne fonctionnera pas ici. Il existe toujours des bibliothèques d'animations, ce qui conduit au point suivant:
  3. GreenSock - La licence est trop restrictive pour une utilisation professionnelle de l'OMI.
  4. React Motion - Cela semble génial, mais TransitionMotionest extrêmement déroutant et trop compliqué pour ce dont j'ai besoin.
  5. Bien sûr, je peux juste faire de la supercherie comme le fait Material UI, où les éléments sont rendus mais restent cachés ( left: -10000px) mais je préfère ne pas suivre cette voie. Je considère que c'est piraté, et je veux que mes composants se démontent pour qu'ils nettoient et n'encombrent pas le DOM.

Je veux quelque chose de facile à mettre en œuvre. Au montage, animez un ensemble de styles; au démontage, animez le même (ou un autre) ensemble de styles. Terminé. Il doit également être performant sur plusieurs plates-formes.

J'ai heurté un mur de briques ici. Si quelque chose me manque et qu'il existe un moyen simple de le faire, faites-le moi savoir.

ffxsam
la source
De quel genre d'animation parlons-nous ici?
Pranesh Ravi
Juste quelque chose de simple, comme un fondu d'opacité CSS et untransform: scale
ffxsam
Les points 1 et 2 me déroutent. Quel genre d'animations utilisez-vous? Transitions JS ou transitions CSS?
Pranesh Ravi
1
Ne confondez pas les styles / classes CSS (par exemple .thing { color: #fff; }) avec les styles JS ( const styles = { thing: { color: '#fff' } }))
ffxsam
Mais le problème est que lorsque vous essayez de changer le style en utilisant javascript, vous remplacez en fait le style d'un élément qui ne donnera aucune transition.
Pranesh Ravi

Réponses:

102

C'est un peu long mais j'ai utilisé tous les événements et méthodes natifs pour réaliser cette animation. Non ReactCSSTransitionGroup, ReactTransitionGroupetc.

Les choses que j'ai utilisées

  • Méthodes de cycle de vie React
  • onTransitionEnd un événement

Comment ça marche

  • Montez l'élément en fonction de l'accessoire de montage passé ( mounted) et avec le style par défaut ( opacity: 0)
  • Après le montage ou la mise à jour, utilisez componentDidMount( componentWillReceivePropspour d'autres mises à jour) pour changer le style ( opacity: 1) avec un délai (pour le rendre asynchrone).
  • Lors du démontage, passez un accessoire au composant pour identifier le démontage, modifiez à nouveau le style ( opacity: 0) onTransitionEnd, supprimez le démontage de l'élément du DOM.

Continuez le cycle.

Parcourez le code, vous comprendrez. Si des éclaircissements sont nécessaires, veuillez laisser un commentaire.

J'espère que cela t'aides.

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>

Pranesh Ravi
la source
Merci pour cela! Où avez-vous appris onTransitionEnd? Je ne le vois pas dans la documentation React.
ffxsam
@ffxsam facebook.github.io/react/docs/events.html C'est sous les événements de transition.
Pranesh Ravi
1
Comment avez-vous su ce qu'il a fait, la documentation n'explique rien. Autre question: comment saviez-vous que vous componentWillReceivePropspouvez retourner quelque chose? Où puis-je en savoir plus à ce sujet?
ffxsam
1
@ffxsam onTransitionEnd est un événement JavaScript natif. Vous pouvez google à ce sujet. facebook.github.io/react/docs/… vous donnera une idée de componentWillReceiveProps.
Pranesh Ravi
7
BTW je pense qu'il y a une erreur dans votre code. Dans votre Parentcomposant, vous référencezthis.transitionEnd
ffxsam
14

En utilisant les connaissances acquises grâce à la réponse de Pranesh, j'ai proposé une solution alternative configurable et réutilisable:

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

Usage:

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

Et enfin, dans la renderméthode d' un autre composant :

return <div>
  <ReactTransitionGroup>
    <Thing />
  </ReactTransitionGroup>
</div>
ffxsam
la source
1
Et comment monter / démonter @ffxsam?
Comment est-ce componentWillLeave()et componentWillEnter()être appelé AnimatedMount?
Rokit
Cela ne fonctionne pas pour moi, voici mon bac à sable: codesandbox.io/s/p9m5625v6m
Webwoman
11

Voici ma solution utilisant la nouvelle API de hooks (avec TypeScript), basée sur cet article , pour retarder la phase de démontage du composant:

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

Usage:

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

Lien CodeSandbox .

Deckele
la source
1
solution élégante, ce serait génial si vous avez ajouté quelques commentaires :)
Webwoman
aussi pourquoi utiliser l'extension de typescrypt puisqu'elle fonctionne bien dans l'extension de javascript?
Webwoman
votre console renvoie également "Impossible de trouver le délai d'expiration de l'espace de noms NodeJS"
Webwoman
1
@Webwoman Merci pour vos commentaires. Je ne peux pas recréer votre problème signalé avec "NodeJS timeout", voir mon lien CodeSandbox sous la réponse. En ce qui concerne TypeScript, je préfère personnellement l'utiliser plutôt que JavaScript, bien que les deux soient viables bien sûr.
deckele
9

J'ai contré ce problème pendant mon travail, et aussi simple que cela puisse paraître, ce n'est vraiment pas dans React. Dans un scénario normal où vous rendez quelque chose comme:

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

au fur et à mesure des this.state.showchangements, les enfants sont montés / démontés tout de suite.

Une approche que j'ai adoptée consiste à créer un composant wrapper Animateet à l'utiliser comme

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

maintenant en tant que this.state.showchangements, nous pouvons percevoir les changements d'accessoires avec getDerivedStateFromProps(componentWillReceiveProps)et créer des étapes de rendu intermédiaires pour effectuer des animations.

Un cycle d'étape pourrait ressembler à ceci

Nous commençons par Static Stage lorsque les enfants sont montés ou démontés.

Une fois que nous détectons les showchangements d'indicateur, nous entrons dans la phase de préparation où nous calculons les propriétés nécessaires comme heightet à widthpartir de ReactDOM.findDOMNode.getBoundingClientRect().

Ensuite, en entrant Animate State, nous pouvons utiliser la transition css pour changer la hauteur, la largeur et l'opacité de 0 aux valeurs calculées (ou à 0 en cas de démontage).

À la fin de la transition, nous utilisons l' onTransitionEndAPI pour revenir à l' Staticétape.

Il y a beaucoup plus de détails sur la façon dont les étapes se transfèrent en douceur, mais cela pourrait être une idée globale :)

Si quelqu'un est intéressé, j'ai créé une bibliothèque React https://github.com/MingruiZhang/react-animate-mount pour partager ma solution. Tout commentaire est le bienvenu :)

Mingrui Zhang
la source
Merci pour vos commentaires, désolé pour la réponse grossière plus tôt. J'ai ajouté plus de détails et un diagramme à ma réponse, j'espère que cela pourra être plus utile aux autres.
Mingrui Zhang
1
@MingruiZhang Il est bon de voir que vous avez pris les commentaires positivement et amélioré votre réponse. C'est très rafraîchissant à voir. Bon travail.
Bugs
6

Je pense que l'utilisation Transitionde react-transition-groupest probablement le moyen le plus simple de suivre le montage / démontage. C'est incroyablement flexible. J'utilise certaines classes pour montrer à quel point il est facile à utiliser, mais vous pouvez certainement connecter vos propres animations JS en utilisant des addEndListeneraccessoires - avec lesquels j'ai également eu beaucoup de chance avec GSAP.

Bac à sable: https://codesandbox.io/s/k9xl9mkx2o

Et voici mon code.

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);
Shalanah
la source
1
Si vous utilisez des composants stylisés, vous pouvez simplement passer showprop H1et exécuter toute la logique à l'intérieur du composant stylisé. Comme ...animation: ${({ show }) => show ? entranceKeyframes : exitKeyframes} 300ms ease-out forwards;
Aleks
2

Mouvement du cadreur

Installez framer-motion à partir 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>
)
tony95
la source
1

Pour ceux qui envisagent de réagir-motion, animer un seul composant lors de son montage et de son démontage peut être une tâche difficile à configurer.

Il existe une bibliothèque appelée react-motion-ui-pack qui facilite grandement le démarrage de ce processus. C'est un wrapper autour de react-motion, ce qui signifie que vous bénéficiez de tous les avantages de la bibliothèque (c'est-à-dire que vous pouvez interrompre l'animation, avoir plusieurs démontages en même temps).

Usage:

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 définit ce que doit être l'état final du composant; congé est le style appliqué lorsque le composant est démonté.

Vous constaterez peut-être qu'une fois que vous avez utilisé le pack d'interface utilisateur plusieurs fois, la bibliothèque react-motion n'est peut-être plus aussi intimidante.

Björn Holdt
la source
Le projet n'est plus maintenu (2018)
Micros
1

Cela peut être fait facilement en utilisant le CSSTransitioncomposant from react-transition-group, qui est exactement comme les bibliothèques que vous avez mentionnées. L'astuce est que vous devez envelopper le composant CSSTransition sans mécanisme d' affichage / masquage comme vous le feriez généralement .ie {show && <Child>}...Sinon, vous masquez l' animation et cela ne fonctionnera pas. Exemple:

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;
}
Lauren
la source
0

Voici mes 2cents: merci à @deckele pour sa solution. Ma solution est basée sur la sienne, c'est la version du composant stateful, entièrement réutilisable.

ici mon bac à sable: https://codesandbox.io/s/302mkm1m .

voici mon 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
la source
0

Voici comment j'ai résolu ce problème en 2019, en créant un spinner de chargement. J'utilise des composants fonctionnels React.

J'ai un composant d' application parent qui a un composant Spinner enfant .

L'application a un état indiquant si l'application se charge ou non. Lorsque l'application se charge, Spinner est rendu normalement. Lorsque l'application ne se charge pas ( isLoadingest faux) Spinner est rendu avec l'accessoire 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 a un état pour savoir s'il est caché ou non. Au début, avec les accessoires et l'état par défaut, Spinner est rendu normalement. La Spinner-fadeInclasse l'anime en se fondant. Lorsque Spinner reçoit l'accessoire, shouldUnmountil le rend avec la Spinner-fadeOutclasse à la place, l'animant en disparaissant.

Cependant, je voulais aussi que le composant se démonte après la disparition.

À ce stade, j'ai essayé d'utiliser l' onAnimationEndévénement synthétique React, similaire à la solution de @ pranesh-ravi ci-dessus, mais cela n'a pas fonctionné. Au lieu de cela, j'avais l'habitude setTimeoutde définir l'état sur hidden avec un délai de la même longueur que l'animation. Spinner se mettra à jour après le délai avec isHidden === true, et rien ne sera rendu.

La clé ici est que le parent ne démonte pas l'enfant, il dit à l'enfant quand le démonter et l'enfant se démonte après avoir pris en charge ses activités de démontage.

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
la source
0

J'avais aussi un besoin urgent d'animation à un seul composant. J'étais fatigué d'utiliser React Motion mais je me tirais les cheveux pour un problème aussi trivial ... (je truc). Après quelques recherches sur Google, je suis tombé sur ce post sur leur dépôt git. J'espère que cela aide quelqu'un.

Référencé de & aussi le crédit . Cela fonctionne pour moi à partir de maintenant. Mon cas d'utilisation était un modal pour animer et démonter en cas de chargement et de déchargement.

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

Rahul Singh
la source
0

Je sais qu'il y a beaucoup de réponses ici, mais je n'en ai toujours pas trouvé une qui réponde à mes besoins. Je voudrais:

  • Composants fonctionnels
  • Une solution qui permettra à mes composants de s'effacer facilement lorsqu'ils sont montés / démontés.

Après de nombreuses heures de bidouillage, j'ai une solution qui fonctionne je dirais à 90%. J'ai écrit la limitation dans un bloc de commentaires dans le code ci-dessous. J'adorerais toujours une meilleure solution, mais c'est la meilleure que j'ai trouvée, y compris les autres solutions ici.

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 vous avez un composant que vous souhaitez faire apparaître / sortir en fondu, enveloppez-le dans <Fade>Ex. <Fade><MyComponent/><Fade>.

Notez que cela utilise react-bootstrappour les noms de classe et pour <Container/>, mais les deux peuvent être facilement remplacés par un CSS personnalisé et un ancien <div>.

Aaron Sullivan
la source
0

Si j'utilise Velocityou AnimeJSbibliothèque pour animer le nœud directement (au lieu de cssou setTimeout), alors j'ai découvert que je peux concevoir un hookpour fournir le statut de l'animation onet la fonction onTogglepour lancer l'animation (par exemple, glisser vers le bas, fondu).

Fondamentalement, ce que fait le hook est d'activer et de désactiver l'animation, puis de mettre à jour le en onconséquence. Par conséquent, nous pouvons obtenir le statut de l'animation avec précision. Sans cela, je répondrais sur une base 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'utilisation est la suivante,

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

et l'implémentation animée pourrait être,

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
la source