2015-10-29 11 views
11

Mi piacerebbe animare tra due componenti in cui il primo componente si dissolve e viene rimosso dal DOM prima che il componente successivo venga aggiunto al DOM e si dissolva. Altrimenti, il nuovo componente viene aggiunto a il DOM e occupa spazio prima che il vecchio componente venga rimosso. Si può vedere il problema in questo violino:Reagire animare la transizione tra i componenti

http://jsfiddle.net/phepyezx/4

// css snippet 
.switch-enter { 
    opacity: 0.01; 
} 
.switch-enter.switch-enter-active { 
    opacity: 1.0; 
} 
.switch-leave { 
    opacity: 1.0; 
} 
.switch-leave.switch-leave-active { 
    opacity: 0; 
} 

// React snippet 
<ReactCSSTransitionGroup transitionName="switch"> 
    <div key={key} className={className}>{this.text()}</div> 
</ReactCSSTransitionGroup> 

Una soluzione inaccettabile (per me) è quello di nascondere il componente originale con i CSS prima di passare al nuovo componente come si vede qui:

http://jsfiddle.net/phepyezx/5

// Change to css 
.switch-leave { 
    visibility: hidden; 
    height: 0px; 
    width: 0px; 
    opacity: 1.0; 
} 

c'è un modo per "ritardo" reagiscono da montaggio di un nuovo componente prima che l'originale è stato rimosso? Sono aperto alla velocità o qualche altra libreria per raggiungere questo obiettivo.

Grazie

risposta

8

risolto utilizzando il metodo componentWillUnmount() ciclo di vita.

http://jsfiddle.net/phepyezx/9/

Ecco il codice:

var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; 

const Off = React.createClass({ 
    componentWillUnmount() { 
     this.props.handleTransitionEnd(); 
    }, 
    render() { 
     return (
      <div className="off button">OFF</div> 
     ) 
    } 
}); 

const On = React.createClass({ 
    componentWillUnmount() { 
     this.props.handleTransitionEnd(); 
    }, 
    render() { 
     return (
      <div className="on button">ON</div> 
     ) 
    } 
}); 

var Switch = React.createClass({ 
    getInitialState: function() { 
     return { 
      on: false, 
      transitionEnd: true 
     }; 
    }, 

    toggle: function(e) { 
     this.setState({ 
      on: !this.state.on, 
      transitionEnd: false 
     }); 
    }, 

    handleTransitionEnd() { 
     this.setState({transitionEnd: true}); 
    }, 

    renderOff() { 
     if (! this.state.on && this.state.transitionEnd) { 
      return (
       <Off key="off" handleTransitionEnd={this.handleTransitionEnd} /> 
      ) 
     } 
    }, 

    renderOn() { 
     if (this.state.on && this.state.transitionEnd) { 
      return (
       <On key="on" handleTransitionEnd={this.handleTransitionEnd} /> 
      ) 
     } 
    }, 

    render: function() { 
     return (
      <div> 
       <button onClick={this.toggle}>Toggle</button> 
       <ReactCSSTransitionGroup transitionName="switch"> 
       {this.renderOff()} 
       {this.renderOn()} 
       </ReactCSSTransitionGroup> 
      </div> 
     );   
    } 
}); 

React.render(<Switch/>, document.getElementById("switch")); 

E il CSS:

.switch-enter { 
    opacity: 0.01; 
} 
.switch-enter.switch-enter-active { 
    opacity: 1.0; 
    transition: opacity 500ms ease-in; 
} 
.switch-leave { 
    opacity: 1.0; 
} 
.switch-leave.switch-leave-active { 
    opacity: 0; 
    transition: opacity 500ms ease-out; 
} 

è possibile ottenere lo stesso risultato efficace con Jonny Buchanan's answer che utilizza il posizionamento assoluto e un ritardo, invece di componentWillUnmount()

+0

@JonnyBuchanan Entrambi i tuoi approcci sembrano utili. Ora che è passato un po 'di tempo, quale approccio hai trovato che funziona meglio per te? @ RickJolly, il tuo approccio funziona meglio per determinati scenari? Scala bene le liste più grandi (bambini) all'interno di ReactCSSTransitionGroup? Il modo in cui capisco è che il tuo approccio incorre in un ciclo 'render()' in più (forse un ciclo in più per ogni oggetto che viene rimosso?). Hai trovato che avere vantaggi o svantaggi? (Forse debugging - forse è più chiaro cosa sta succedendo con l'extra 'render()'?) –

+0

Inoltre, per quanto riguarda il fatto che i componenti da animare devono implementare 'componentWillUnmount', come pure prendere' handleTransitionEnd() ' come "prop" ... hai trovato che essere un dolore per altre situazioni? –

13

Un'altra soluzione è di rendere gli elementi in entrata e in uscita occupano lo stesso spazio, ad esempio facendoli sia posizionato in modo assoluto:

<ReactCSSTransitionGroup 
    className="container" 
    component="div" 
    transitionName="switch"> 
... 

.container { 
    position: relative; 
} 
.container > div { 
    position: absolute; 
} 

http://jsfiddle.net/phepyezx/7/


È può utilizzare transition-delay per attendere che il componente in uscita scompaia prima di visualizzare il componente in ingresso, ad esempio:

.fade-enter { 
    opacity: 0.01; 
} 
.fade-enter.fade-enter-active { 
    opacity: 1; 
    transition: opacity 1s; 
    transition-delay: 1s; 
} 

.fade-leave { 
    opacity: 1; 
} 
.fade-leave.fade-leave-active { 
    opacity: 0.01; 
    transition: opacity 1s; 
} 
+0

Sì. Non è proprio lo stesso effetto di come svanire completamente il primo originale. Immagino che quello che sto cercando non sia possibile senza l'inganno. Suppongo di poter utilizzare il metodo del ciclo di vita componentDidLeave() del livello inferiore di ReactTransisionGroup per impostare lo stato per sapere quando iniziare a eseguire il rendering del nuovo componente. Un altro approccio consiste nell'utilizzare plain javascript per applicare un listener di eventi di transizione sul nodo DOM sottostante come dimostrato in questo post: http://www.chloechen.io/react-animation-done-in-two-ways/ –

+0

Aggiunto un nuovo un po 'di transizione-ritardo' –

+0

Grazie mille! Quasi perfetto, ma richiede un posizionamento assoluto.Sfortunatamente, non ho trovato nulla in cui il componente in ingresso non sia montato (o visualizzato: 'none') finché il componente in uscita non esce. –

2

Se si vuole ritardare la prestazione del componente successivo, si potrebbe usare qualcosa di simile:

import React, { Component } from 'react'; 

export default class DelayedRender extends Component { 

    static propTypes = { 
     delay: React.PropTypes.number.isRequired, 
     children: React.PropTypes.element, 
     className: React.PropTypes.string 
    }; 

    constructor(props) { 
     super(props); 

     this.state = { 
      render: false 
     }; 
    } 

    componentDidMount() { 
     setTimeout(() => { 
      const delayedClassNames = this.refs.noDelayed.className; 
      this.setState({ 
       render: true, 
       classNames: delayedClassNames 
      }); 
     }, this.props.delay); 
    } 

    render() { 
     const { children, className } = this.props; 
     return this.state.render ? 
      <div className={this.state.classNames}>{children}</div> : 
      <div className={className} ref="noDelayed" ></div>; 
    } 
} 

E nel metodo di rendering:

const ROUTE_TRANSITION_TIME = 500; 
const views = []; 

if (shouldRenderDelayedRoute) { 
    views.push(
     <DelayedRender delay={ROUTE_TRANSITION_TIME} key="book"> 
      <A ref="book"/> 
     </DelayedRender> 
    ); 
} else { 
    views.push(<B key="library"/>); 
} 

<ReactCSSTransitionGroup 
    transitionEnterTimeout={ROUTE_TRANSITION_TIME} 
    transitionLeaveTimeout={ROUTE_TRANSITION_TIME} 
    transitionName="fade-transition" 
         > 
    {views} 
</ReactCSSTransitionGroup>