Edit: added working example
https://lab.award.is/react-shared-element-transition-example/
(Some problems in Safari for macOS for me)
The idea is for the elements to be animated, wrapped in a container that retains its position when mounted. I created a simple React component called SharedElement that does just that.
So, step by step for your example ( Overview view and Detailview ):
- The
Overview view is displayed. Each element (squares) inside the Survey is wrapped in a SharedElement with a unique identifier (for example, item-0, item-1, etc.). The SharedElement component saves the position for each item in the static variable Store (by the identifier you gave them). - Go to
Detailview . A detailed view is enclosed in another SharedElement , which has the same identifier as the item you clicked on, for example, item-4. - Now this time, SharedElement sees that an item with the same identifier is already registered in its store. It will clone the new element, apply the position of the old elements to it (the one from Detailview), and animate the new position (I did this using GSAP). When the animation is complete, it overwrites the new position for the item in the store.
Using this method, it actually does not depend on the React Router (no special life cycle methods, but componentDidMount ), and it will work even when you first land on the first page of the review and go to the review page.
I will share my implementation with you, but remember that she has some known errors. For example. you have to deal with z-indeces and overwhelm yourself; and it does not process unregistered item positions from the store. I am sure that if someone can spend some time on this, you can make a great little plugin out of it.
Implementation:
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 - Pay attention to the identifier in SharedElement
import React, { Component } from 'react' import './Overview.css' import items from './items'
DetailView.js - pay attention to the identifier in 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() {