Edit: Hinzugefügt ein funktionierendes Beispiel
https://lab.award.is/react-shared-element-transition-example/
(Einige Probleme in Safari für Mac OS für mich)
Die Idee, die Elemente zu haben, ist animiert werden eingewickelt in einen Behälter, der seine Positionen bei der Montage speichert. Ich habe eine einfache React Component namens SharedElement
erstellt, die genau das tut.
Schritt So Schritt für Ihr Beispiel (Overview
Ansicht und Detailview
):
- Die
Overview
Ansicht montiert wird. Jeder Artikel (die Quadrate) innerhalb der Übersicht ist in die SharedElement
mit einer eindeutigen ID verpackt (zum Beispiel Artikel-0, Artikel-1 usw.). Die Komponente SharedElement
speichert die Position für jedes Element in einer statischen Variablen Store
(anhand der von Ihnen angegebenen ID).
- Sie navigieren zum
Detailview
. Die Detailansicht wird in eine andere SharedElement
verpackt, die die gleiche ID wie der Artikel hat, auf den Sie geklickt haben, also zum Beispiel item-4.
- Jetzt sieht das SharedElement, dass ein Element mit derselben ID bereits in seinem Speicher registriert ist. Es klont das neue Element, wendet die alte Elementposition an (die aus der Detailansicht) und animiert an die neue Position (ich habe es mit GSAP gemacht). Wenn die Animation abgeschlossen ist, überschreibt sie die neue Position für den Artikel im Geschäft.
Mit dieser Technik ist es tatsächlich unabhängig vom Router reagieren (keine besonderen Lebenszyklus Methoden aber componentDidMount
), und es wird auch funktionieren, wenn sie auf der Übersichtsseite ersten und die Navigation zu der Übersichtsseite zu landen.
Ich werde meine Implementierung mit Ihnen teilen, aber beachten Sie, dass es einige bekannte Bugs hat. Z.B. Du musst mit Z-Indices umgehen und dich selbst überschwemmen; und es behandelt nicht Registrierung Elementpositionen aus dem Speicher noch nicht. Ich bin mir ziemlich sicher, wenn jemand etwas Zeit damit verbringen kann, kannst du daraus ein tolles kleines Plugin machen.
Die Umsetzung:
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 - Beachten Sie die ID auf der 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 - Beachten Sie die ID auf der 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'},
})
}
}
können Sie die GreenSock Animation Bibliothek mit den Lifecycle-Haken verwenden, um einige wirklich zu machen coole Übergänge. –
Haben Sie Fortschritte dabei gemacht? Ich habe versucht, so etwas zu implementieren und eine sehr einfache Lösung gefunden, aber es funktioniert. Ich wurde von Androids Shared Element Transitions und der Implementierung in Exponentialjs/Ex-Navigation (React Native) inspiriert. Es wäre gut zu wissen, ob Sie eine gute und wiederverwendbare Lösung gefunden haben, bevor ich meine Sachen poste. – tommy
FYI react-router v4 (immer noch in Alpha, vielleicht Beta) hat die Custom-Lifecycle-Hooks abgeschafft und benutzt einfach die Standard-Komponenten-Lebenszyklus-Hooks, mit denen Sie das tun können, was Sie zu tun versuchen –