这个简单的东西应该很容易实现,但我会把头发拉出来,看它有多复杂。
我想做的就是为安装和放大器设置动画。卸载React组件,就是这样。这是我迄今为止所尝试过的以及为什么每个解决方案都无法工作:
ReactCSSTransitionGroup
- 我根本不使用CSS类,它是所有JS样式,所以这不会起作用。ReactTransitionGroup
- 这个较低级别的API很棒,但它要求您在动画完成时使用回调,因此只使用CSS过渡不会在这里工作。总有动画库,这导致了下一点:TransitionMotion
非常混乱,而且过于复杂,无法满足我的需求。left: -10000px
)但我宁愿不去那条路线。我认为这很麻烦,我希望我的组件卸载所以他们清理并且不会弄乱DOM。我想要实现 easy 的东西。在mount上,动画一组样式;在卸载时,为相同(或另一组)样式设置动画。完成。它还必须在多个平台上具有高性能。
我在这里碰了一堵砖墙。如果我遗漏了某些内容,并且有一种简单的方法可以做到这一点,请告诉我。
答案 0 :(得分:78)
这有点长,但我已经使用了所有原生事件和方法来实现这个动画。没有ReactCSSTransitionGroup
,ReactTransitionGroup
等等。
我用过的东西
onTransitionEnd
活动如何运作
mounted
)和默认样式(opacity: 0
)componentDidMount
(componentWillReceiveProps
进行进一步更新)以更改样式(opacity: 1
)并使其超时(以使其异步)。opacity: 0
),onTransitionEnd
,从DOM中卸载元素。继续循环。
通过代码,你会明白的。如果需要澄清,请发表评论。
希望这有帮助。
class App extends React.Component{
constructor(props) {
super(props)
this.transitionEnd = this.transitionEnd.bind(this)
this.mountStyle = this.mountStyle.bind(this)
this.unMountStyle = this.unMountStyle.bind(this)
this.state ={ //base css
show: true,
style :{
fontSize: 60,
opacity: 0,
transition: 'all 2s ease',
}
}
}
componentWillReceiveProps(newProps) { // check for the mounted props
if(!newProps.mounted)
return this.unMountStyle() // call outro animation when mounted prop is false
this.setState({ // remount the node when the mounted prop is true
show: true
})
setTimeout(this.mountStyle, 10) // call the into animation
}
unMountStyle() { // css for unmount animation
this.setState({
style: {
fontSize: 60,
opacity: 0,
transition: 'all 1s ease',
}
})
}
mountStyle() { // css for mount animation
this.setState({
style: {
fontSize: 60,
opacity: 1,
transition: 'all 1s ease',
}
})
}
componentDidMount(){
setTimeout(this.mountStyle, 10) // call the into animation
}
transitionEnd(){
if(!this.props.mounted){ // remove the node on transition end when the mounted prop is false
this.setState({
show: false
})
}
}
render() {
return this.state.show && <h1 style={this.state.style} onTransitionEnd={this.transitionEnd}>Hello</h1>
}
}
class Parent extends React.Component{
constructor(props){
super(props)
this.buttonClick = this.buttonClick.bind(this)
this.state = {
showChild: true,
}
}
buttonClick(){
this.setState({
showChild: !this.state.showChild
})
}
render(){
return <div>
<App onTransitionEnd={this.transitionEnd} mounted={this.state.showChild}/>
<button onClick={this.buttonClick}>{this.state.showChild ? 'Unmount': 'Mount'}</button>
</div>
}
}
ReactDOM.render(<Parent />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-with-addons.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
答案 1 :(得分:14)
利用从Pranesh的答案中获得的知识,我想出了一个可配置和可重用的替代解决方案:
const AnimatedMount = ({ unmountedStyle, mountedStyle }) => {
return (Wrapped) => class extends Component {
constructor(props) {
super(props);
this.state = {
style: unmountedStyle,
};
}
componentWillEnter(callback) {
this.onTransitionEnd = callback;
setTimeout(() => {
this.setState({
style: mountedStyle,
});
}, 20);
}
componentWillLeave(callback) {
this.onTransitionEnd = callback;
this.setState({
style: unmountedStyle,
});
}
render() {
return <div
style={this.state.style}
onTransitionEnd={this.onTransitionEnd}
>
<Wrapped { ...this.props } />
</div>
}
}
};
用法:
import React, { PureComponent } from 'react';
class Thing extends PureComponent {
render() {
return <div>
Test!
</div>
}
}
export default AnimatedMount({
unmountedStyle: {
opacity: 0,
transform: 'translate3d(-100px, 0, 0)',
transition: 'opacity 250ms ease-out, transform 250ms ease-out',
},
mountedStyle: {
opacity: 1,
transform: 'translate3d(0, 0, 0)',
transition: 'opacity 1.5s ease-out, transform 1.5s ease-out',
},
})(Thing);
最后,在另一个组件的render
方法中:
return <div>
<ReactTransitionGroup>
<Thing />
</ReactTransitionGroup>
</div>
答案 2 :(得分:7)
我在工作中解决了这个问题,而且看起来很简单,它实际上不在React中。在正常情况下,您呈现以下内容:
this.state.show ? {childen} : null;
随着this.state.show
的更改,子代会立即安装/卸载。
我采取的一种方法是创建包装器组件Animate
并像
<Animate show={this.state.show}>
{childen}
</Animate>
现在this.state.show
发生变化时,我们可以感知getDerivedStateFromProps(componentWillReceiveProps)
的道具变化,并创建中间渲染阶段来执行动画。
我们先从静态舞台开始,说明儿童已安装或卸载。
一旦我们检测到show
标志的变化,就会进入 Prep Stage ,在这里我们从height
计算必要的属性,例如width
和ReactDOM.findDOMNode.getBoundingClientRect()
。
然后进入动画状态,我们可以使用css过渡将高度,宽度和不透明度从0更改为计算值(如果卸载则更改为0)。
在过渡结束时,我们使用onTransitionEnd
api更改回
Static
阶段。
关于阶段如何平稳转移还有更多细节,但这可能是一个整体想法:)
如果有兴趣的人,我创建了一个React库https://github.com/MingruiZhang/react-animate-mount来共享我的解决方案。欢迎任何反馈:)
答案 3 :(得分:3)
从npm安装framer-motion。
import { motion, AnimatePresence } from "framer-motion"
export const MyComponent = ({ isVisible }) => (
<AnimatePresence>
{isVisible && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>
)
答案 4 :(得分:2)
这是我使用新的hooks API(带有TypeScript)based on this post的解决方案,用于延迟组件的卸载阶段:
function useDelayUnmount(isMounted: boolean, delayTime: number) {
const [ shouldRender, setShouldRender ] = useState(false);
useEffect(() => {
let timeoutId: NodeJS.Timeout;
if (isMounted && !shouldRender) {
setShouldRender(true);
}
else if(!isMounted && shouldRender) {
timeoutId = setTimeout(
() => setShouldRender(false),
delayTime
);
}
return () => clearTimeout(timeoutId);
});
return shouldRender;
}
用法:
const Parent: React.FC = () => {
const [ isMounted, setIsMounted ] = useState(true);
const shouldRenderChild = useDelayUnmount(isMounted, 500);
const mountedStyle = {opacity: 1, transition: "opacity 500ms ease-in"};
const unmountedStyle = {opacity: 0, transition: "opacity 500ms ease-in"};
const handleToggleClicked = () => {
setIsMounted(!isMounted);
}
return (
<>
{shouldRenderChild &&
<Child style={isMounted ? mountedStyle : unmountedStyle} />}
<button onClick={handleToggleClicked}>Click me!</button>
</>
);
}
CodeSandbox链接。
答案 5 :(得分:2)
我认为使用Transition
中的react-transition-group
可能是跟踪安装/卸载的最简单方法。它非常灵活。我正在使用一些类来演示它的易用性,但是您可以肯定地使用addEndListener
道具来连接自己的JS动画-我也很幸运地使用GSAP。
沙箱:https://codesandbox.io/s/k9xl9mkx2o
这是我的代码。
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { Transition } from "react-transition-group";
import styled from "styled-components";
const H1 = styled.h1`
transition: 0.2s;
/* Hidden init state */
opacity: 0;
transform: translateY(-10px);
&.enter,
&.entered {
/* Animate in state */
opacity: 1;
transform: translateY(0px);
}
&.exit,
&.exited {
/* Animate out state */
opacity: 0;
transform: translateY(-10px);
}
`;
const App = () => {
const [show, changeShow] = useState(false);
const onClick = () => {
changeShow(prev => {
return !prev;
});
};
return (
<div>
<button onClick={onClick}>{show ? "Hide" : "Show"}</button>
<Transition mountOnEnter unmountOnExit timeout={200} in={show}>
{state => {
let className = state;
return <H1 className={className}>Animate me</H1>;
}}
</Transition>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
答案 6 :(得分:1)
如果onMount添加了另一个className,其中包含了转换,并且onUnMount你删除了这个类名怎么办?
答案 7 :(得分:1)
对于那些考虑反应运动的人来说,在安装和卸载时为单个组件设置动画可能会让人难以置信。
有一个名为 react-motion-ui-pack 的库可以让这个过程更容易开始。它是react-motion的包装器,这意味着您可以从库中获得所有好处(即,您可以中断动画,同时进行多次卸载)。
<强>用法:强>
import Transition from 'react-motion-ui-pack'
<Transition
enter={{ opacity: 1, translateX: 0 }}
leave={{ opacity: 0, translateX: -100 }}
component={false}
>
{ this.state.show &&
<div key="hello">
Hello
</div>
}
</Transition>
输入定义组件的最终状态应该是什么; leave是卸载组件时应用的样式。
您可能会发现,一旦您使用了UI包几次,react-motion库可能就不再那么令人生畏了。
答案 8 :(得分:1)
使用react-move动画进入和退出过渡更容易。
答案 9 :(得分:1)
您可以使用 React Transition Group 执行此操作。它为您提供 CSS 类,因此您可以在这些 CSS 类中编写动画代码。
按照这个简单的例子
import {CSSTransition } from 'react-transition-group';//This should be imported
import './AnimatedText.css';
const AnimatedText = () => {
const [showText, setShowText] = useState(false); //By default text will be not shown
//Handler to switch states
const switchHandler = () =>{
setShowText(!showText);
};
return (
//in : pass your state here, it will used by library to toggle. It should be boolean
//timeout: your amination total time(it should be same as mentioned in css)
//classNames: give class name of your choice, library will prefix it with it's animation classes
//unmountOnExit: Component will be unmounted when your state changes to false
<CSSTransition in={showText} timeout={500} classNames='fade' unmountOnExit={true}>
<h1>Animated Text</h1>
</CSSTransition>
<button onClick={switchHandler}>Show Text</button>
);
};
export default AnimatedText;
现在,让我们在 CSS 文件(AnimatedText.css)中编写动画,记住 classNames 属性(在本例中为淡入淡出)
//fade class should be prefixed
/*****Fade In effect when component is mounted*****/
//This is when your animation starts
fade-enter {
opacity: 0;
}
//When your animation is active
.fade-enter.fade-enter-active {
opacity: 1;
transition: all 500ms ease-in;
}
/*****Fade In effect when component is mounted*****/
/*****Fade Out effect when component is unmounted*****/
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
transition: all 500ms ease-out;
}
/*****Fade Out effect when component is unmounted*****/
还有一个出现类,可以在你的组件第一次加载时使用。查看文档了解更多详情
答案 10 :(得分:0)
如果您正在寻找简单的钩子示例:
import React, { useEffect, useReducer } from "react";
import ReactDOM from "react-dom";
const ANIMATION_TIME = 2 * 1000;
function Component() {
const [isMounted, toggleMounted] = useReducer((p) => !p, true);
const [isAnimateAnmount, toggleAnimateUnmount] = useReducer((p) => !p, false);
const [isVisible, toggleVisible] = useReducer((p) => (p ? 0 : 1), 0);
useEffect(() => {
if (isAnimateAnmount) {
toggleVisible();
toggleAnimateUnmount();
setTimeout(() => {
toggleMounted();
}, ANIMATION_TIME);
}
}, [isAnimateAnmount]);
useEffect(() => {
toggleVisible();
}, [isMounted]);
return (
<>
<button onClick={toggleAnimateUnmount}>toggle</button>
<div>{isMounted ? "Mounted" : "Unmounted"}</div>
{isMounted && (
<div
style={{
fontSize: 60,
opacity: isVisible,
transition: "all 2s ease"
}}
>
Example
</div>
)}
</>
);
}
答案 11 :(得分:0)
您始终可以使用React生命周期方法,但是无论您使用的是styled-components
还是纯CSS,react-transition-group都是迄今为止我所遇到的最方便的动画库。当您要跟踪组件的安装和卸载并相应地渲染动画时,它特别有用。
将Transition
与样式组件一起使用,将CSSTransition
与纯CSS类名一起使用。
答案 12 :(得分:0)
您可以为此使用 React SyntheticEvent 。
使用诸如 onAnimationEnd 或 onTransitionEnd 之类的事件,您可以实现这一目标。
反应文档:https://reactjs.org/docs/events.html#animation-events
代码示例:https://dev.to/michalczaplinski/super-easy-react-mount-unmount-animations-with-hooks-4foj
答案 13 :(得分:0)
如果我使用Velocity
或AnimeJS
库直接为节点设置动画(而不是css
或setTimeout
),那么我发现可以设计一个{{1} },以提供动画状态hook
和功能on
,以启动动画(例如,向下滑动,淡入淡出)。
该钩子的基本作用是打开和关闭动画,然后此后相应地更新onToggle
。因此,我们可以准确地获得动画的状态。如果不这样做,将临时答复on
。
duration
用法如下,
/**
* A hook to provide animation status.
* @class useAnimate
* @param {object} _ props
* @param {async} _.animate Promise to perform animation
* @param {object} _.node Dom node to animate
* @param {bool} _.disabled Disable animation
* @returns {useAnimateObject} Animate status object
* @example
* const { on, onToggle } = useAnimate({
* animate: async () => { },
* node: node
* })
*/
import { useState, useCallback } from 'react'
const useAnimate = ({
animate, node, disabled,
}) => {
const [on, setOn] = useState(false)
const onToggle = useCallback(v => {
if (disabled) return
if (v) setOn(true)
animate({ node, on: v }).finally(() => {
if (!v) setOn(false)
})
}, [animate, node, disabled, effect])
return [on, onToggle]
}
export default useAnimate
动画实现可能是
const ref = useRef()
const [on, onToggle] = useAnimate({
animate: animateFunc,
node: ref.current,
disabled
})
const onClick = () => { onToggle(!on) }
return (
<div ref={ref}>
{on && <YOUROWNCOMPONENT onClick={onClick} /> }
</div>
)
答案 14 :(得分:0)
我知道这里有很多答案,但是我仍然找不到适合我需要的答案。我想要:
经过数小时的摆弄,我有一个可行的解决方案,我会说90%。我已经在下面的代码的注释块中写了限制。我仍然希望有更好的解决方案,但这是我找到的最好的解决方案,包括此处的其他解决方案。
const TIMEOUT_DURATION = 80 // Just looked like best balance of silky smooth and stop delaying me.
// Wrap this around any views and they'll fade in and out when mounting /
// unmounting. I tried using <ReactCSSTransitionGroup> and <Transition> but I
// could not get them to work. There is one major limitation to this approach:
// If a component that's mounted inside of <Fade> has direct prop changes,
// <Fade> will think that it's a new component and unmount/mount it. This
// means the inner component will fade out and fade in, and things like cursor
// position in forms will be reset. The solution to this is to abstract <Fade>
// into a wrapper component.
const Fade: React.FC<{}> = ({ children }) => {
const [ className, setClassName ] = useState('fade')
const [ newChildren, setNewChildren ] = useState(children)
const effectDependency = Array.isArray(children) ? children : [children]
useEffect(() => {
setClassName('fade')
const timerId = setTimeout(() => {
setClassName('fade show')
setNewChildren(children)
}, TIMEOUT_DURATION)
return () => {
clearTimeout(timerId)
}
}, effectDependency)
return <Container fluid className={className + ' p-0'}>{newChildren}</Container>
}
如果您有要淡入/淡出的组件,请将其包裹在<Fade>
Ex。中。 <Fade><MyComponent/><Fade>
。
请注意,这使用react-bootstrap
作为类名和<Container/>
,但是都可以很容易地用自定义CSS和常规的旧<div>
代替。
答案 15 :(得分:0)
可以使用CSSTransition
中的react-transition-group
组件轻松完成此操作,就像您提到的库一样。诀窍是您需要包装CSSTransition组件,而没有像通常那样的显示/隐藏机制。即{show && <Child>}...
否则,您将隐藏动画,它将无法正常工作。示例:
ParentComponent.js
import React from 'react';
import {CSSTransition} from 'react-transition-group';
function ParentComponent({show}) {
return (
<CSSTransition classes="parentComponent-child" in={show} timeout={700}>
<ChildComponent>
</CSSTransition>
)}
ParentComponent.css
// animate in
.parentComponent-child-enter {
opacity: 0;
}
.parentComponent-child-enter-active {
opacity: 1;
transition: opacity 700ms ease-in;
}
// animate out
.parentComponent-child-exit {
opacity: 1;
}
.parentComponent-child-exit-active {
opacity: 0;
transition: opacity 700ms ease-in;
}
答案 16 :(得分:0)
我也非常需要单一组件Animation。我对使用React Motion感到厌倦,但是我为这样一个琐碎的问题拉扯头发..(我的东西)。经过一番谷歌搜索后,我在他们的git repo上看到了这个帖子。希望它能对某人有所帮助。
Referenced From & also the credit。 到目前为止,这对我有效。我的用例是在加载和卸载时进行动画和卸载的模式。
class Example extends React.Component {
constructor() {
super();
this.toggle = this.toggle.bind(this);
this.onRest = this.onRest.bind(this);
this.state = {
open: true,
animating: false,
};
}
toggle() {
this.setState({
open: !this.state.open,
animating: true,
});
}
onRest() {
this.setState({ animating: false });
}
render() {
const { open, animating } = this.state;
return (
<div>
<button onClick={this.toggle}>
Toggle
</button>
{(open || animating) && (
<Motion
defaultStyle={open ? { opacity: 0 } : { opacity: 1 }}
style={open ? { opacity: spring(1) } : { opacity: spring(0) }}
onRest={this.onRest}
>
{(style => (
<div className="box" style={style} />
))}
</Motion>
)}
</div>
);
}
}
答案 17 :(得分:0)
这是我的2美分: 感谢@deckele的解决方案。我的解决方案基于他的,这是有状态的组件版本,可以完全重用。
这里是我的沙盒:https://codesandbox.io/s/302mkm1m。
这是我的snippet.js:
import ReactDOM from "react-dom";
import React, { Component } from "react";
import style from "./styles.css";
class Tooltip extends Component {
state = {
shouldRender: false,
isMounted: true,
}
shouldComponentUpdate(nextProps, nextState) {
if (this.state.shouldRender !== nextState.shouldRender) {
return true
}
else if (this.state.isMounted !== nextState.isMounted) {
console.log("ismounted!")
return true
}
return false
}
displayTooltip = () => {
var timeoutId;
if (this.state.isMounted && !this.state.shouldRender) {
this.setState({ shouldRender: true });
} else if (!this.state.isMounted && this.state.shouldRender) {
timeoutId = setTimeout(() => this.setState({ shouldRender: false }), 500);
() => clearTimeout(timeoutId)
}
return;
}
mountedStyle = { animation: "inAnimation 500ms ease-in" };
unmountedStyle = { animation: "outAnimation 510ms ease-in" };
handleToggleClicked = () => {
console.log("in handleToggleClicked")
this.setState((currentState) => ({
isMounted: !currentState.isMounted
}), this.displayTooltip());
};
render() {
var { children } = this.props
return (
<main>
{this.state.shouldRender && (
<div className={style.tooltip_wrapper} >
<h1 style={!(this.state.isMounted) ? this.mountedStyle : this.unmountedStyle}>{children}</h1>
</div>
)}
<style>{`
@keyframes inAnimation {
0% {
transform: scale(0.1);
opacity: 0;
}
60% {
transform: scale(1.2);
opacity: 1;
}
100% {
transform: scale(1);
}
}
@keyframes outAnimation {
20% {
transform: scale(1.2);
}
100% {
transform: scale(0);
opacity: 0;
}
}
`}
</style>
</main>
);
}
}
class App extends Component{
render(){
return (
<div className="App">
<button onClick={() => this.refs.tooltipWrapper.handleToggleClicked()}>
click here </button>
<Tooltip
ref="tooltipWrapper"
>
Here a children
</Tooltip>
</div>
)};
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);