反应中的动画页面转换
在过去的几周里,我一直在使用React处理应用程序。 到目前为止一切工作正常,但现在我想添加一些转换。 这些转换比我设法找到的任何示例都要复杂一些。
我有2页,一个概述和一个我想要转换的详细页面。
我正在使用react-router来管理我的路由:
<Route path='/' component={CoreLayout}>
<Route path=':pageSlug' component={Overview} />
<Route path=':pageSlug/:detailSlug' component={DetailView} />
</Route>
概述如下所示:
Detailview看起来像这样:
转换的想法是,您点击概述的其中一个元素。 这个被点击的元素移动到detailView上应该具有的位置。 过渡应该由路线改变(我认为)发起,并且也应该能够相反地发生。
我已经尝试在布局上使用ReactTransitionGroup
,它的渲染方法如下所示:
render () {
return (
<div className='layout'>
<ReactTransitionGroup>
React.cloneElement(this.props.children, { key: this.props.location.pathname })
</ReactTransitionGroup>
</div>
)
}
这将使子组件能够接收特殊的生命周期挂钩。 但是我想在这些钩子中以某种方式访问子组件,并且仍然继续使用React方法。
有人能指出我下一步采取的正确方向吗? 或者,也许可以指出我可能错过某个地方的例子吗? 在之前的项目中,我使用Ember和液体火焰来获得这些类型的转换,对于React,有没有类似的东西?
我正在使用react / react-redux / react-router / react-router-redux。
编辑:添加了一个工作示例
https://lab.award.is/react-shared-element-transition-example/
(Safari中针对macOS的一些问题对我来说)
这个想法是让元素被动画包装在一个容器中,该容器在安装时存储它的位置。 我创建了一个名为SharedElement
的简单React组件,它完成了这个工作。
因此,您的示例( Overview
视图和Detailview
)一步一步地执行:
Overview
视图被挂载。 Overview中的每个项目(正方形)都包含在SharedElement
中, SharedElement
带有一个唯一的ID(例如item-0,item-1等)。 SharedElement
组件将每个项目的位置存储在一个静态Store
变量中(通过您给它们的ID)。 Detailview
。 Detailview包装到另一个SharedElement
,该ID与您单击的项目具有相同的ID,例如item-4。 使用这种技术,它实际上独立于React路由器(没有特殊的生命周期方法,但是componentDidMount
),并且在登陆Overview页面并导航到Overview页面时甚至可以工作。
我将与您分享我的实施,但请注意它有一些已知的错误。 例如,你必须处理z-indeces并自己溢出; 并且它不处理来自商店的注销元素位置。 我很确定,如果有人可以花一些时间在这个上,你可以制作一个很棒的小插件。
实施:
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 - 记下SharedElement上的ID
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 - 记下SharedElement上的ID
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'},
})
}
}
链接地址: http://www.djcxy.com/p/31789.html