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
):
- 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).
- 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.
- 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'},
})
}
}
è possibile utilizzare la libreria Greensock animazione con i ganci del ciclo di vita per fare alcuni davvero belle transizioni. –
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
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 –