[animation] React-단일 구성 요소의 마운트 및 마운트 해제 애니메이션
이 간단한 일은 쉽게 해낼 수 있어야하지만, 그것이 얼마나 복잡한 지에 대해 머리를 뽑아 내고 있습니다.
제가하고 싶은 것은 React 컴포넌트의 마운트와 언 마운트를 애니메이션화하는 것뿐입니다. 지금까지 시도한 내용과 각 솔루션이 작동하지 않는 이유는 다음과 같습니다.
ReactCSSTransitionGroup
-CSS 클래스를 전혀 사용하지 않고 모든 JS 스타일이므로 작동하지 않습니다.ReactTransitionGroup
-이 하위 수준 API는 훌륭하지만 애니메이션이 완료되면 콜백을 사용해야하므로 CSS 전환 만 사용하면 여기서는 작동하지 않습니다. 다음 지점으로 이어지는 애니메이션 라이브러리는 항상 있습니다.- GreenSock-라이선스가 업무용 IMO에 너무 제한적입니다.
- React Motion-이것은 훌륭해 보이지만
TransitionMotion
내가 필요한 것에 대해 매우 혼란스럽고 지나치게 복잡합니다. - 물론 머티리얼 UI처럼 속임수를 쓸 수 있습니다. 요소는 렌더링되지만 숨겨진 상태로 유지되는 (
left: -10000px
)하지만 그 길을 가고 싶지 않습니다. 나는 그것이 해키라고 생각 하고 내 구성 요소가 마운트 해제되어 DOM을 정리하지 않고 정리 하고 싶습니다.
구현 하기 쉬운 것을 원합니다 . 마운트시 스타일 세트에 애니메이션을 적용합니다. 마운트 해제시 동일한 (또는 다른) 스타일 세트에 애니메이션을 적용합니다. 끝난. 또한 여러 플랫폼에서 고성능이어야합니다.
여기 벽돌 벽에 부딪 혔어요. 내가 뭔가를 놓치고 이것을 할 수있는 쉬운 방법이 있다면 알려주세요.
답변
이것은 약간 길지만이 애니메이션을 달성하기 위해 모든 네이티브 이벤트와 메서드를 사용했습니다. 아니요 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>
답변
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>
답변
다음은 구성 요소의 마운트 해제 단계를 지연시키기 위해이 게시물을 기반으로 하는 새로운 후크 API (TypeScript 포함)를 사용하는 솔루션입니다 .
function useDelayUnmount(isMounted: boolean, delayTime: number) {
const [ shouldRender, setShouldRender ] = useState(false);
useEffect(() => {
let timeoutId: number;
if (isMounted && !shouldRender) {
setShouldRender(true);
}
else if(!isMounted && shouldRender) {
timeoutId = setTimeout(
() => setShouldRender(false),
delayTime
);
}
return () => clearTimeout(timeoutId);
}, [isMounted, delayTime, shouldRender]);
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 링크.
답변
작업하는 동안이 문제에 대응했고, 단순 해 보이지만 실제로는 React에 없습니다. 다음과 같은 것을 렌더링하는 일반적인 시나리오에서 :
this.state.show ? {childen} : null;
로 this.state.show
변경 아이들은 멀리 / 언 마운트 권리가 장착되어있다.
내가 취한 한 가지 접근 방식은 래퍼 구성 요소를 만들고 Animate
다음과 같이 사용하는 것입니다.
<Animate show={this.state.show}>
{childen}
</Animate>
이제 this.state.show
변경 사항으로 소품 변경을 감지 getDerivedStateFromProps(componentWillReceiveProps)
하고 중간 렌더링 단계를 만들어 애니메이션을 수행 할 수 있습니다.
Static Stage 부터 시작합니다자식이 마운트되거나 마운트 해제 될 때 합니다.
우리가 감지되면 show
플래그 변경, 우리는 입력 준비 단계 우리가 같은 필요한 특성 계산 height
과 width
에서를 ReactDOM.findDOMNode.getBoundingClientRect()
.
그런 다음 Animate State에 들어가면 CSS 전환을 사용하여 높이, 너비 및 불투명도를 0에서 계산 된 값으로 (또는 마운트 해제시 0으로) 변경할 수 있습니다.
전환이 끝나면 onTransitionEnd
API를 사용 하여 다시 Static
단계 로 변경
합니다.
단계가 원활하게 전송되는 방법에 대한 자세한 내용이 있지만 전체적인 아이디어 일 수 있습니다.
관심있는 사람이 있다면 React 라이브러리 https://github.com/MingruiZhang/react-animate-mount 를 만들어 솔루션을 공유했습니다. 모든 피드백 환영 🙂
답변
Transition
from을 사용 하는 react-transition-group
것이 마운트 / 마운트 해제를 추적하는 가장 쉬운 방법 이라고 생각 합니다. 매우 유연합니다. 사용이 얼마나 쉬운 지 보여주기 위해 몇 가지 클래스를 사용하고 있지만, addEndListener
prop을 활용하여 자신 만의 JS 애니메이션을 확실히 연결할 수 있습니다 .
샌드 박스 : 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);
답변
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>
)
답변
리 액트 모션을 고려하는 사람들에게 단일 구성 요소를 마운트 및 마운트 해제 할 때 애니메이션을 적용하는 것은 설정하는 데 부담이 될 수 있습니다.
이 프로세스를 훨씬 쉽게 시작할 수 있도록하는 react-motion-ui-pack 이라는 라이브러리 가 있습니다. 리 액트 모션을 둘러싼 래퍼입니다. 즉, 라이브러리에서 모든 이점을 얻을 수 있습니다 (즉, 애니메이션을 중단 할 수 있고 동시에 여러 언 마운트를 수행 할 수 있음).
용법:
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>
Enter는 구성 요소의 최종 상태를 정의합니다. leave는 컴포넌트가 마운트 해제 될 때 적용되는 스타일입니다.
UI 팩을 몇 번 사용한 후에는 react-motion 라이브러리가 더 이상 어렵지 않을 수 있습니다.