Reagieren - Ein- und Aushängen einer einzelnen Komponente animieren

94

Etwas so Einfaches sollte leicht zu erreichen sein, aber ich ziehe mir die Haare aus, wie kompliziert es ist.

Ich möchte nur das Ein- und Aushängen einer React-Komponente animieren, fertig. Folgendes habe ich bisher versucht und warum nicht jede Lösung funktioniert:

  1. ReactCSSTransitionGroup - Ich verwende überhaupt keine CSS-Klassen, es sind alles JS-Stile, also funktioniert das nicht.
  2. ReactTransitionGroup- Diese untergeordnete API ist großartig, erfordert jedoch die Verwendung eines Rückrufs, wenn die Animation abgeschlossen ist. Daher funktioniert es hier nicht, nur CSS-Übergänge zu verwenden. Es gibt immer Animationsbibliotheken, was zum nächsten Punkt führt:
  3. GreenSock - Die Lizenzierung ist für die geschäftliche Nutzung IMO zu restriktiv.
  4. Reaktion reagieren - Dies scheint großartig zu sein, ist aber TransitionMotionäußerst verwirrend und für das, was ich brauche, zu kompliziert.
  5. Natürlich kann ich einfach Tricks machen wie die Material-Benutzeroberfläche, bei der die Elemente gerendert werden, aber verborgen bleiben ( left: -10000px), aber ich würde diesen Weg lieber nicht gehen. Ich halte es für hacky und ich will meine Komponenten damit sie bereinigen und das DOM nicht überladen.

Ich möchte etwas, das einfach ist zu implementieren ist. Animieren Sie auf dem Mount eine Reihe von Stilen. Animieren Sie beim Aushängen denselben (oder einen anderen) Satz von Stilen. Getan. Es muss auch auf mehreren Plattformen eine hohe Leistung bieten.

Ich habe hier eine Mauer getroffen. Wenn mir etwas fehlt und es eine einfache Möglichkeit gibt, lassen Sie es mich wissen.

ffxsam
quelle
Welche Art von Animation sprechen wir hier?
Pranesh Ravi
Nur etwas Einfaches, wie das Einblenden einer CSS-Deckkraft und eintransform: scale
ffxsam
Punkt 1 und 2 verwirren mich. Welche Art von Animationen verwenden Sie? JS-Übergänge oder CSS-Übergänge?
Pranesh Ravi
1
Verwechseln Sie CSS-Stile / Klassen (z. B. .thing { color: #fff; }) nicht mit JS-Stilen ( const styles = { thing: { color: '#fff' } }))
ffxsam
Das Problem ist jedoch, dass Sie beim Versuch, den Stil mithilfe von Javascript zu ändern, tatsächlich den Stil eines Elements ersetzen, das keinen Übergang ergibt.
Pranesh Ravi

Antworten:

101

Dies ist etwas langwierig, aber ich habe alle nativen Ereignisse und Methoden verwendet, um diese Animation zu erzielen. Nein ReactCSSTransitionGroup, ReactTransitionGroupund etc.

Dinge, die ich benutzt habe

  • Reagieren Sie auf Lebenszyklusmethoden
  • onTransitionEnd Veranstaltung

Wie das funktioniert

  • Mounten Sie das Element basierend auf der übergebenen Mount-Requisite ( mounted) und mit dem Standardstil ( opacity: 0)
  • Verwenden Sie nach dem Mounten oder Aktualisieren componentDidMount( componentWillReceivePropsfür weitere Updates), um den Stil ( opacity: 1) mit einem Timeout zu ändern (um ihn asynchron zu machen).
  • Übergeben Sie beim Aufheben der Bereitstellung eine Requisite an die Komponente, um das Aufheben der Bereitstellung zu identifizieren, ändern Sie den Stil erneut ( opacity: 0) onTransitionEndund entfernen Sie das Aufheben der Bereitstellung aus dem DOM.

Setzen Sie den Zyklus fort.

Gehen Sie den Code durch, Sie werden verstehen. Wenn eine Klarstellung erforderlich ist, hinterlassen Sie bitte einen Kommentar.

Hoffe das hilft.

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
quelle
Danke dafür! Wo hast du davon erfahren onTransitionEnd? Ich sehe es nicht in den React-Dokumenten.
ffxsam
@ffxsam facebook.github.io/react/docs/events.html Es befindet sich unter den Übergangsereignissen.
Pranesh Ravi
1
Woher wusstest du, was es getan hat? Die Dokumentation erklärt nichts. Eine andere Frage: Woher wusstest du, componentWillReceivePropsdass etwas zurückgegeben werden kann? Wo kann ich mehr darüber lesen?
ffxsam
1
@ffxsam onTransitionEnd ist ein natives JavaScript-Ereignis. Sie können darüber googeln. facebook.github.io/react/docs/… gibt Ihnen eine Vorstellung von componentWillReceiveProps.
Pranesh Ravi
7
Übrigens denke ich, dass Ihr Code einen Fehler enthält. In Ihrer ParentKomponente verweisen Siethis.transitionEnd
ffxsam
14

Mit dem Wissen aus Praneshs Antwort habe ich eine alternative Lösung gefunden, die konfigurierbar und wiederverwendbar ist:

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

Verwendung:

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

Und schließlich in der renderMethode einer anderen Komponente :

return <div>
  <ReactTransitionGroup>
    <Thing />
  </ReactTransitionGroup>
</div>
ffxsam
quelle
1
Und wie mounten / unmounten Sie @ffxsam?
Wie ist componentWillLeave()und componentWillEnter()wird gerufen AnimatedMount?
Rokit
Funktioniert nicht für mich, hier meine Sandbox: Codesandbox.io/s/p9m5625v6m
Webwoman
10

Hier ist meine Lösung mit der neuen Hooks-API (mit TypeScript), die auf diesem Beitrag basiert , um die Unmount-Phase der Komponente zu verzögern:

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

Verwendung:

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- Link.

Deckele
quelle
1
elegante Lösung, wäre toll, wenn Sie einige Kommentare hinzugefügt haben :)
Webwoman
Warum sollte man auch die Erweiterung von typescrypt verwenden, da sie in der Erweiterung von Javascript gut funktioniert?
Webwoman
Auch Ihre Konsole gibt "Namespace NodeJS Timeout kann nicht gefunden werden"
Webwoman
1
@Webwoman Danke für deine Kommentare. Ich kann Ihr gemeldetes Problem mit "NodeJS-Zeitüberschreitung" nicht wiederherstellen. Siehe meinen CodeSandbox-Link unter der Antwort. In Bezug auf TypeScript bevorzuge ich persönlich die Verwendung von JavaScript, obwohl beide natürlich realisierbar sind.
Deckele
9

Ich habe diesem Problem während meiner Arbeit begegnet, und so einfach es schien, ist es wirklich nicht in React. In einem normalen Szenario, in dem Sie Folgendes rendern:

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

Bei this.state.showÄnderungen werden die Kinder sofort montiert / unmontiert.

Ein Ansatz, den ich gewählt habe, besteht darin, eine Wrapper-Komponente zu erstellen Animateund sie wie folgt zu verwenden

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

Jetzt this.state.showkönnen wir als Änderungen Änderungen an Requisiten wahrnehmen getDerivedStateFromProps(componentWillReceiveProps)und Zwischenrenderstufen erstellen, um Animationen auszuführen.

Ein Bühnenzyklus könnte so aussehen

Wir beginnen mit Static Stage wenn die Kinder montiert oder unmontiert sind.

Sobald wir die showFlag-Änderungen erkennen, betreten wir die Vorbereitungsphase, in der wir die erforderlichen Eigenschaften wie heightund berechnenwidth vonReactDOM.findDOMNode.getBoundingClientRect() .

Dann eintreten Wenn Sie Animate State eingeben, können Sie den CSS-Übergang verwenden, um Höhe, Breite und Deckkraft von 0 auf die berechneten Werte zu ändern (oder auf 0, wenn die Bereitstellung aufgehoben wird).

Am Ende des Übergangs verwenden wir onTransitionEnd API, um zur StaticBühne zurückzukehren.

Es gibt viel mehr Details darüber, wie die Stufen reibungslos übertragen werden, aber dies könnte eine Gesamtidee sein :)

Wenn jemand interessiert ist, habe ich eine React-Bibliothek https://github.com/MingruiZhang/react-animate-mount erstellt , um meine Lösung zu teilen. Jede Rückmeldung willkommen :)

Mingrui Zhang
quelle
Vielen Dank für Ihr Feedback. Entschuldigen Sie die grobe Antwort früher. Ich habe meiner Antwort mehr Details und ein Diagramm hinzugefügt, hoffe, dass dies für andere hilfreicher sein kann.
Mingrui Zhang
1
@MingruiZhang Es ist gut zu sehen, dass Sie die Kommentare positiv aufgenommen und Ihre Antwort verbessert haben. Es ist sehr erfrischend zu sehen. Gute Arbeit.
Bugs
5

Ich denke, die Verwendung von Transitionfrom react-transition-groupist wahrscheinlich der einfachste Weg, um das Ein- und Aushängen zu verfolgen. Es ist unglaublich flexibel. Ich verwende einige Klassen, um zu zeigen, wie einfach die Verwendung ist, aber Sie können definitiv Ihre eigenen JS-Animationen verwendenaddEndListener Requisiten verbinden - mit denen ich auch mit GSAP viel Glück hatte.

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

Und hier ist mein 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
quelle
1
Wenn Sie gestaltete Komponenten verwenden, können Sie die showRequisite einfach an die gestaltete Komponente übergeben H1und die gesamte Logik ausführen. Wie ...animation: ${({ show }) => show ? entranceKeyframes : exitKeyframes} 300ms ease-out forwards;
Aleks
2

Framer-Bewegung

Installieren Sie die Framer-Bewegung von 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
quelle
1

Für diejenigen, die eine Reaktionsbewegung in Betracht ziehen, kann das Animieren einer einzelnen Komponente beim Ein- und Aushängen überwältigend sein.

Es gibt eine Bibliothek namens React-Motion-UI-Pack , die diesen Prozess viel einfacher macht. Es ist ein Wrapper um React-Motion, was bedeutet, dass Sie alle Vorteile der Bibliothek nutzen können (dh Sie können die Animation unterbrechen und mehrere Unmounts gleichzeitig durchführen).

Verwendung:

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 definiert den Endzustand der Komponente. Leave ist der Stil, der angewendet wird, wenn die Komponente nicht bereitgestellt wird.

Möglicherweise stellen Sie fest, dass die React-Motion-Bibliothek nach mehrmaliger Verwendung des UI-Pakets möglicherweise nicht mehr so ​​entmutigend ist.

Björn Holdt
quelle
Projekt wird nicht mehr gepflegt (2018)
Micros
1

Das Animieren von Eingangs- und Ausgangsübergängen ist mit React -Move viel einfacher .

Beispiel auf Codesandbox

abgegrenzt
quelle
1

Dies kann einfach mit der CSSTransitionKomponente aus erfolgen react-transition-group, die genau den von Ihnen erwähnten Bibliotheken entspricht. Der Trick ist , müssen Sie die CSSTransition Komponente wickeln , ohne Show / Hide - Mechanismus wie Sie in der Regel würde .ie {show && <Child>}...Andernfalls Sie verstecken Animation und es wird nicht funktionieren. Beispiel:

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
quelle
0

Hier meine 2 Cent: Danke an @deckele für seine Lösung. Meine Lösung basiert auf seiner, es ist die Komponentenversion des Stateful, die vollständig wiederverwendbar ist.

hier meine Sandbox: https://codesandbox.io/s/302mkm1m .

hier meine 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);
Webfrau
quelle
0

So habe ich das 2019 gelöst, als ich einen Ladespinner gemacht habe. Ich verwende React-Funktionskomponenten.

Ich habe eine übergeordnete App- Komponente mit einer untergeordneten Spinner- Komponente.

Die App gibt an, ob die App geladen wird oder nicht. Wenn die App geladen wird, wird Spinner normal gerendert. Wenn die App nicht geladen wird ( isLoadingist falsch), wird Spinner mit der Requisite gerendert 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;

Der Spinner hat den Status, ob er versteckt ist oder nicht. Zu Beginn wird Spinner mit den Standardrequisiten und dem Standardstatus normal gerendert. Die Spinner-fadeInKlasse animiert das Einblenden. Wenn Spinner die Requisite erhält, wird shouldUnmountsie Spinner-fadeOutstattdessen mit der Klasse gerendert und animiert das Ausblenden.

Ich wollte jedoch auch, dass die Komponente nach dem Ausblenden ausgehängt wird.

Zu diesem Zeitpunkt habe ich versucht, das onAnimationEndsynthetische Ereignis "Reagieren" zu verwenden, ähnlich der obigen Lösung von @ pranesh-ravi, aber es hat nicht funktioniert. Stattdessen habe ich setTimeoutden Status mit einer Verzögerung eingestellt, die der Länge der Animation entspricht. Der Spinner wird nach der Verzögerung mit aktualisiert isHidden === trueund es wird nichts gerendert.

Der Schlüssel hierbei ist, dass der Elternteil das Kind nicht abmeldet, dem Kind mitteilt, wann es die Bereitstellung aufheben soll, und dass sich das Kind selbst abmeldet, nachdem es sich um das Aufheben der Bereitstellung gekümmert hat.

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
quelle
0

Ich brauchte auch dringend eine Einzelkomponenten-Animation. Ich war müde mit React Motion, aber ich zog meine Haare für solch ein triviales Problem. (Ich Sache). Nach einigem googeln bin ich auf diesen Beitrag auf ihrem Git Repo gestoßen. Hoffe es hilft jemandem ..

Referenziert von & auch die Gutschrift . Das funktioniert bei mir ab sofort. Mein Anwendungsfall war ein Modal zum Animieren und Aufheben der Bereitstellung beim Laden und Entladen.

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
quelle
0

Ich weiß, dass es hier viele Antworten gibt, aber ich habe immer noch keine gefunden, die meinen Bedürfnissen entspricht. Ich will:

  • Funktionskomponenten
  • Eine Lösung, mit der meine Komponenten beim Ein- und Aushängen problemlos ein- und ausgeblendet werden können.

Nach vielen Stunden des Fummelns habe ich eine Lösung, die zu 90% funktioniert. Ich habe die Einschränkung in einem Kommentarblock im folgenden Code geschrieben. Ich würde immer noch eine bessere Lösung lieben, aber dies ist die beste, die ich gefunden habe, einschließlich der anderen Lösungen hier.

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

Wenn Sie eine Komponente haben, die Sie ein- und ausblenden möchten, wickeln Sie sie in <Fade>Bsp. <Fade><MyComponent/><Fade>.

Beachten Sie, dass dies react-bootstrapfür die Klassennamen und für verwendet wird <Container/>, aber beide leicht durch benutzerdefiniertes CSS und ein normales altes ersetzt werden können <div>.

Aaron Sullivan
quelle
0

Wenn ich Velocityoder AnimeJSBibliothek verwende, um Knoten direkt (anstelle von cssoder setTimeout) zu animieren , habe ich herausgefunden, dass ich einen entwerfen kann hook, um den Animationsstatus onund die Funktion bereitzustellenonToggle zum Starten der Animation bereitzustellen (z. B. Slidedown, Fade).

Grundsätzlich bewirkt der Hook, dass die Animation ein- und ausgeschaltet und anschließend entsprechend aktualisiert onwird. Daher können wir den Status der Animation genau abrufen. Ohne dies würde ad-hoc antworten 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

Die Verwendung ist die folgende:

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

und die animierte Implementierung könnte sein,

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
quelle