Что-то такое простое должно быть легко выполнено, но я выдергиваю волосы из-за того, насколько это сложно.
Все, что я хочу сделать, это анимировать монтирование и размонтирование компонента React, вот и все. Вот что я пробовал до сих пор и почему каждое решение не работает:
ReactCSSTransitionGroup
- Я вообще не использую классы CSS, это все стили JS, так что это не сработает.ReactTransitionGroup
- Этот API нижнего уровня великолепен, но он требует, чтобы вы использовали обратный вызов после завершения анимации, поэтому просто использование переходов CSS здесь не сработает. Всегда есть библиотеки анимации, что приводит к следующему пункту:- GreenSock - Лицензирование слишком ограничено для использования в бизнесе IMO.
- React Motion - это кажется отличным, но
TransitionMotion
чрезвычайно запутанным и слишком сложным для того, что мне нужно. - Конечно, я могу просто обманывать, как это делает Material UI, когда элементы отображаются, но остаются скрытыми (
left: -10000px
), но я бы предпочел не идти по этому пути. Я считаю это хакерским и хочу, чтобы мои компоненты размонтировались, чтобы они очистили и не загромождали DOM.
Я хочу что-то, что легко реализовать. На маунте оживите набор стилей; при размонтировании анимировать тот же (или другой) набор стилей. Выполнено. Он также должен иметь высокую производительность на нескольких платформах.
Здесь я ударился о кирпичную стену. Если я чего-то упускаю и есть простой способ сделать это, дайте мне знать.
transform: scale
.thing { color: #fff; }
) со стилями JS (const styles = { thing: { color: '#fff' } }
))Ответы:
Это немного длинновато, но я использовал все собственные события и методы для создания этой анимации. Нет
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>
источник
onTransitionEnd
? Я не вижу этого в документации по React.componentWillReceiveProps
можно что-то вернуть? Где я могу прочитать об этом подробнее?Parent
компоненте вы ссылаетесьthis.transitionEnd
Используя знания, полученные из ответа Пранеша, я придумал альтернативное решение, которое можно настраивать и использовать повторно:
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>
источник
componentWillLeave()
иcomponentWillEnter()
тебя вызываютAnimatedMount
?Вот мое решение с использованием нового 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)
и создавать промежуточные этапы рендеринга для выполнения анимации.Мы начинаем со статической сцены, когда дети садятся или снимаются.
Как только мы обнаруживаем
show
изменение флага, мы переходим на этап подготовки, где вычисляем необходимые свойства, такие какheight
иwidth
изReactDOM.findDOMNode.getBoundingClientRect()
.Затем, войдя в Animate State, мы можем использовать css-переход, чтобы изменить высоту, ширину и непрозрачность от 0 до вычисленных значений (или до 0 при демонтировании).
В конце перехода мы используем
onTransitionEnd
api, чтобы вернуться наStatic
сцену.Есть гораздо больше деталей о том, как этапы переходят плавно, но это может быть общая идея :)
Если кому-нибудь интересно, я создал библиотеку React https://github.com/MingruiZhang/react-animate-mount, чтобы поделиться своим решением. Любая обратная связь приветствуется :)
источник
Я думаю, что использование
Transition
fromreact-transition-group
- это, вероятно, самый простой способ отследить монтаж / демонтаж. Это невероятно гибко. Я использую некоторые классы, чтобы показать, насколько легко их использовать, но вы определенно можете подключить свои собственные JS-анимации, используяaddEndListener
опору, с которой мне тоже очень повезло, используя 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);
источник
show
опоруH1
и выполнять всю логику внутри стилизованного компонента. Как ...animation: ${({ show }) => show ? entranceKeyframes : exitKeyframes} 300ms ease-out forwards;
Framer motion
Установите framer-motion из npm.
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-пакет, библиотека реагирования и движения перестала быть такой сложной.
источник
Анимировать переходы при входе и выходе намного проще с помощью функции « реакция-перемещение» .
пример на codeandbox
источник
Это можно легко сделать с помощью
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; }
источник
Вот мои 2cents: спасибо @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);
источник
Вот как я решил эту проблему в 2019 году при создании счетчика загрузки. Я использую функциональные компоненты React.
У меня есть родительский компонент приложения , у которого есть дочерний компонент Spinner .
В приложении есть состояние того, загружается приложение или нет. Когда приложение загружается, Spinner отображается нормально. Когда приложение не загружается (
isLoading
false), Spinner отображается с помощью свойстваshouldUnmount
.App.js :
import React, {useState} from 'react'; import Spinner from './Spinner'; const App = function() { const [isLoading, setIsLoading] = useState(false); return ( <div className='App'> {isLoading ? <Spinner /> : <Spinner shouldUnmount />} </div> ); }; export default App;
Спиннер имеет статус , скрыт он или нет. Вначале, со свойствами и состоянием по умолчанию, Spinner отображается нормально.
Spinner-fadeIn
Класс одушевляет это угасание. Когда Spinner получает опоруshouldUnmount
он делает сSpinner-fadeOut
классом вместо этого, оживляя его выведение.Однако я также хотел, чтобы компонент размонтировался после исчезновения.
На этом этапе я попытался использовать
onAnimationEnd
синтетическое событие React, подобное решению @ pranesh-ravi выше, но оно не сработало. Вместо этого яsetTimeout
устанавливал скрытое состояние с задержкой, равной длине анимации. Spinner обновится после задержки сisHidden === true
, и ничего не будет отображаться.Ключевым моментом здесь является то, что родитель не размонтирует дочерний элемент, он сообщает дочернему элементу, когда нужно размонтировать, а потомок размонтирует себя после того, как позаботится о своем размонтировании.
Spinner.js :
import React, {useState} from 'react'; import './Spinner.css'; const Spinner = function(props) { const [isHidden, setIsHidden] = useState(false); if(isHidden) { return null } else if(props.shouldUnmount) { setTimeout(setIsHidden, 500, true); return ( <div className='Spinner Spinner-fadeOut' /> ); } else { return ( <div className='Spinner Spinner-fadeIn' /> ); } }; export default Spinner;
Spinner.css:
.Spinner { position: fixed; display: block; z-index: 999; top: 50%; left: 50%; margin: -40px 0 0 -20px; height: 40px; width: 40px; border: 5px solid #00000080; border-left-color: #bbbbbbbb; border-radius: 40px; } .Spinner-fadeIn { animation: rotate 1s linear infinite, fadeIn .5s linear forwards; } .Spinner-fadeOut { animation: rotate 1s linear infinite, fadeOut .5s linear forwards; } @keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } } @keyframes fadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } @keyframes rotate { 100% { transform: rotate(360deg); } }
источник
Я также остро нуждался в однокомпонентной анимации. Я устал использовать React Motion, но я тянул за волосы из-за такой тривиальной проблемы ... (я вещь). После некоторого поиска в Google я наткнулся на этот пост в их репозитории git. Надеюсь, это кому-то поможет ..
Ссылка From & также кредит . На данный момент это работает для меня. Мой вариант использования был модальным для анимации и размонтирования в случае загрузки и выгрузки.
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> ); } }
источник
Я знаю, что здесь есть много ответов, но я все еще не нашел того, который подходит для моих нужд. Я хочу:
После многих часов возни у меня есть решение, которое работает, я бы сказал, на 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
имена классов и for<Container/>
, но оба могут быть легко заменены пользовательским CSS и обычным старым<div>
.источник
Если я использую библиотеку
Velocity
илиAnimeJS
для непосредственной анимации узла (вместоcss
илиsetTimeout
), то я обнаружил, что могу спроектировать объект,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> )
и анимированная реализация может быть,
import anime from 'animejs' const animateFunc = (params) => { const { node, on } = params const height = on ? 233 : 0 return new Promise(resolve => { anime({ targets: node, height, complete: () => { resolve() } }).play() }) }
источник
Для этого вы можете использовать React SyntheticEvent .
Это можно сделать с помощью таких событий, как onAnimationEnd или onTransitionEnd .
React Docs: https://reactjs.org/docs/events.html#animation-events
Пример кода: https://dev.to/michalczaplinski/super-easy-react-mount-unmount-animations-with-hooks-4foj
источник
Вы всегда можете использовать методы жизненного цикла React, но react-transition-group - безусловно, самая удобная библиотека для анимаций, с которой я столкнулся, независимо от того, используете ли вы
styled-components
или простой CSS. Это особенно полезно, когда вы хотите отслеживать монтирование и размонтирование вашего компонента и соответственно визуализировать анимацию. ИспользуйтеTransition
со стилями-компонентами иCSSTransition
когда вы используете простые имена классов css.источник