2016-01-26 24 views
7

Nelle ultime due settimane ho lavorato a un'applicazione utilizzando React. Finora tutto funziona bene, ma ora voglio aggiungere alcune transizioni ad esso. Queste transizioni sono un po 'più complesse di tutti gli esempi che sono riuscito a trovare.Le transizioni di pagina animate reagiscono

Ho 2 pagine, una panoramica e una pagina di dettaglio che mi piacerebbe passare.

sto usando reagire-router per gestire le mie vie:

<Route path='/' component={CoreLayout}> 

    <Route path=':pageSlug' component={Overview} /> 
    <Route path=':pageSlug/:detailSlug' component={DetailView} /> 

</Route> 

Panoramica si presenta così: enter image description here

Visione dettagliata si presenta così: enter image description here

L'idea della transizione è che fai clic su uno degli elementi della panoramica. Questo elemento che è stato cliccato si sposta verso la posizione che dovrebbe avere sul detailView. La transizione dovrebbe essere iniziata da un cambio di rotta (credo) e dovrebbe anche essere possibile invertire la rotta.

Ho già provato utilizzando ReactTransitionGroup sul Layout, che ha un metodo di rendering che assomiglia a questo:

render() { 
    return (
     <div className='layout'> 
      <ReactTransitionGroup> 
       React.cloneElement(this.props.children, { key: this.props.location.pathname }) 
      </ReactTransitionGroup> 
     </div> 
    ) 
} 

Questo darà il componente figlio la possibilità di ricevere la speciale lifecycle hooks. Ma mi piacerebbe accedere ai componenti figlio in qualche modo durante questi hook e continuare a fare le cose in modo React.

Qualcuno potrebbe indicarmi la giusta direzione per il prossimo passo da compiere? O forse indicarmi un esempio che forse mi è sfuggito da qualche parte? Nei progetti precedenti ho utilizzato Ember insieme a liquid fire per ottenere questo tipo di transizioni, c'è forse qualcosa come questo per React?

Sto usando react/react-redux/react-router/react-router-redux.

+0

è possibile utilizzare la libreria Greensock animazione con i ganci del ciclo di vita per fare alcuni davvero belle transizioni. –

+0

Avete fatto qualche progresso su questo? Ho cercato di implementare qualcosa di simile e ho trovato una soluzione molto semplice, ma funziona. Mi sono ispirato ad Androids Shared Element Transitions e all'implementazione in exponentjs/ex-navigation (React Native). Sarebbe bello sapere se hai trovato una soluzione buona e riutilizzabile prima di pubblicare le mie cose. – tommy

+0

FYI react-router v4 (ancora in alfa, forse in versione beta) ha eliminato gli hook del ciclo di vita personalizzato e utilizza semplicemente gli hook standard del ciclo di vita dei componenti, che potrebbero consentire di fare ciò che si sta tentando di fare –

risposta

3

Edit: Aggiunto un esempio di lavoro

https://lab.award.is/react-shared-element-transition-example/

(Alcuni problemi in Safari per MacOS per me)


L'idea è di avere gli elementi per essere animati avvolto in un contenitore che memorizza le sue posizioni quando montato. Ho creato un componente React semplice chiamato SharedElement che fa esattamente questo.

Così passo per passo il vostro esempio (Overview vista e Detailview):

  1. La vista Overview viene montato. Ogni elemento (i quadrati) all'interno della Panoramica viene incluso nello SharedElement con un ID univoco (ad esempio articolo-, articolo 1 ecc.). Il componente SharedElement memorizza la posizione per ciascun elemento in una variabile statica Store (dall'ID che gli hai fornito).
  2. Si passa a Detailview. Detailview è racchiuso in un altro SharedElement con lo stesso ID dell'elemento su cui si è fatto clic, quindi ad esempio item-4.
  3. Ora questa volta, SharedElement vede che un articolo con lo stesso ID è già registrato nel suo negozio. Clonerà il nuovo elemento, applicherà la posizione dei vecchi elementi ad esso (quella della vista Detail) e si animerà nella nuova posizione (l'ho fatto usando GSAP). Al termine dell'animazione, sovrascrive la nuova posizione per l'articolo nel negozio.

Utilizzando questa tecnica, in realtà è indipendente dal Reagire Router (nessun metodo del ciclo di vita particolari, ma componentDidMount) e sarà anche il lavoro in fase di atterraggio sulla pagina Panoramica prima e la navigazione alla pagina Panoramica.

Condividerò la mia implementazione con voi, ma tenete presente che ha alcuni bug noti. Per esempio. devi affrontare z-indeces e ti trabocca; e non gestisce annullando la registrazione delle posizioni degli elementi dal negozio. Sono abbastanza sicuro che se qualcuno può passare un po 'di tempo su questo, puoi farne un piccolo piccolo plugin.

L'implementazione:

index.js

import React from "react"; 
import ReactDOM from "react-dom"; 
import App from "./App"; 

import Overview from './Overview' 
import DetailView from './DetailView' 

import "./index.css"; 

import { Router, Route, IndexRoute, hashHistory } from 'react-router' 

const routes = (
    <Router history={hashHistory}> 
     <Route path="/" component={App}> 
      <IndexRoute component={Overview} /> 
      <Route path="detail/:id" component={DetailView} /> 
     </Route> 
    </Router> 
) 

ReactDOM.render(
    routes, 
    document.getElementById('root') 
); 

App.js

import React, {Component} from "react" 
import "./App.css" 

export default class App extends Component { 
    render() { 
     return (
      <div className="App"> 
       {this.props.children} 
      </div> 
     ) 
    } 
} 

Overview.js - Notare l'ID sul SharedElement

import React, { Component } from 'react' 
import './Overview.css' 
import items from './items' // Simple array containing objects like {title: '...'} 
import { hashHistory } from 'react-router' 
import SharedElement from './SharedElement' 

export default class Overview extends Component { 

    showDetail = (e, id) => { 
     e.preventDefault() 

     hashHistory.push(`/detail/${id}`) 
    } 

    render() { 
     return (
      <div className="Overview"> 
       {items.map((item, index) => { 
        return (
         <div className="ItemOuter" key={`outer-${index}`}> 
          <SharedElement id={`item-${index}`}> 
           <a 
            className="Item" 
            key={`overview-item`} 
            onClick={e => this.showDetail(e, index + 1)} 
           > 
            <div className="Item-image"> 
             <img src={require(`./img/${index + 1}.jpg`)} alt=""/> 
            </div> 

            {item.title} 
           </a> 
          </SharedElement> 
         </div> 
        ) 
       })} 
      </div> 
     ) 
    } 

} 

DetailView.js - Nota l'ID sul SharedElement

import React, { Component } from 'react' 
import './DetailItem.css' 
import items from './items' 
import { hashHistory } from 'react-router' 
import SharedElement from './SharedElement' 

export default class DetailView extends Component { 

    getItem =() => { 
     return items[this.props.params.id - 1] 
    } 

    showHome = e => { 
     e.preventDefault() 

     hashHistory.push(`/`) 
    } 

    render() { 
     const item = this.getItem() 

     return (
      <div className="DetailItemOuter"> 
       <SharedElement id={`item-${this.props.params.id - 1}`}> 
        <div className="DetailItem" onClick={this.showHome}> 
         <div className="DetailItem-image"> 
          <img src={require(`./img/${this.props.params.id}.jpg`)} alt=""/> 
         </div> 
         Full title: {item.title} 
        </div> 
       </SharedElement> 
      </div> 
     ) 
    } 

} 

SharedElement.js

import React, { Component, PropTypes, cloneElement } from 'react' 
import { findDOMNode } from 'react-dom' 
import TweenMax, { Power3 } from 'gsap' 

export default class SharedElement extends Component { 

    static Store = {} 
    element = null 

    static props = { 
     id: PropTypes.string.isRequired, 
     children: PropTypes.element.isRequired, 
     duration: PropTypes.number, 
     delay: PropTypes.number, 
     keepPosition: PropTypes.bool, 
    } 

    static defaultProps = { 
     duration: 0.4, 
     delay: 0, 
     keepPosition: false, 
    } 

    storeNewPosition(rect) { 
     SharedElement.Store[this.props.id] = rect 
    } 

    componentDidMount() { 
     // Figure out the position of the new element 
     const node = findDOMNode(this.element) 
     const rect = node.getBoundingClientRect() 
     const newPosition = { 
      width: rect.width, 
      height: rect.height, 
     } 

     if (! this.props.keepPosition) { 
      newPosition.top = rect.top 
      newPosition.left = rect.left 
     } 

     if (SharedElement.Store.hasOwnProperty(this.props.id)) { 
      // Element was already mounted, animate 
      const oldPosition = SharedElement.Store[this.props.id] 

      TweenMax.fromTo(node, this.props.duration, oldPosition, { 
       ...newPosition, 
       ease: Power3.easeInOut, 
       delay: this.props.delay, 
       onComplete:() => this.storeNewPosition(newPosition) 
      }) 
     } 
     else { 
      setTimeout(() => { // Fix for 'rect' having wrong dimensions 
       this.storeNewPosition(newPosition) 
      }, 50) 
     } 
    } 

    render() { 
     return cloneElement(this.props.children, { 
      ...this.props.children.props, 
      ref: element => this.element = element, 
      style: {...this.props.children.props.style || {}, position: 'absolute'}, 
     }) 
    } 

} 
Problemi correlati