React - анімація монтажу та демонтажу одного компонента


97

Щось таке просте має бути легко здійснено, але я вириваю волосся, наскільки це складно.

Все, що я хочу зробити, - це анімувати монтаж та демонтаж компонента React, ось і все. Ось те, що я намагався дотепер, і чому кожне рішення не буде працювати:

  1. ReactCSSTransitionGroup - Я взагалі не використовую класи CSS, це всі стилі JS, тому це не спрацює.
  2. ReactTransitionGroup- Цей API нижчого рівня чудовий, але він вимагає використання зворотного виклику, коли анімація завершена, тому просто використання переходів CSS тут не буде працювати. Завжди є бібліотеки анімації, що веде до наступного пункту:
  3. GreenSock - ліцензування є занадто обмежувальним для комерційного використання ІМО.
  4. React Motion - це здається чудовим, але TransitionMotionнадзвичайно заплутаним і надто складним для того, що мені потрібно.
  5. Звичайно, я можу просто обдурити, як це робить Інтерфейс користувача, де елементи відображаються, але залишаються прихованими ( left: -10000px), але я волів би не йти цим шляхом. Я вважаю, що це хакерство, і я хочу, щоб мої компоненти демонтувались, щоб вони очищали і не захаращували DOM.

Я хочу щось, що легко реалізувати. На кріпленні, анімуйте набір стилів; на демонтажі, анімуйте той самий (або інший) набір стилів. Готово. Це також має бути високою продуктивністю на декількох платформах.

Я вдарився цегляною стіною тут. Якщо мені чогось не вистачає, і це простий спосіб зробити це, дайте мені знати.


Про яку анімацію ми тут говоримо?
Пранеш Раві,

Просто щось просте, наприклад, непрозорість CSS зникає іtransform: scale
ffxsam

Пункти 1 і 2 мене бентежать. Яку анімацію ви використовуєте? JS-переходи або CSS-переходи?
Пранеш Раві

1
Не плутайте стилі / класи CSS (наприклад .thing { color: #fff; }) зі стилями JS ( const styles = { thing: { color: '#fff' } }))
ffxsam

Але проблема полягає в тому, що коли ви намагаєтеся змінити стиль за допомогою JavaScript, ви фактично замінюєте стиль елемента, який не дасть жодного переходу.
Пранеш Раві,

Відповіді:


102

Це трохи довго, але я використав усі власні події та методи для досягнення цієї анімації. Ні 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.
ffxsam

@ffxsam facebook.github.io/react/docs/events.html Він знаходиться під подіями переходу.
Пранеш Раві,

1
Звідки ви дізналися, що це зробило, документація нічого не пояснює. Інше питання: звідки ви знали, що componentWillReceivePropsможете щось повернути? Де я можу прочитати більше про це?
ffxsam

1
@ffxsam onTransitionEnd - власна подія JavaScript. Ви можете прогуглити про це. facebook.github.io/react/docs/… дасть вам уявлення про компонентWillReceiveProps.
Pranesh Ravi

7
До речі, я думаю, що у вашому коді є помилка. У своєму Parentкомпоненті ви посилаєтесьthis.transitionEnd
ffxsam

14

Використовуючи знання, отримані з відповіді Пранеша, я придумав альтернативне рішення, яке можна налаштувати та повторно використати:

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>

1
А як ви встановлюєте / демонтуєте @ffxsam?

Як componentWillLeave()і componentWillEnter()як зателефонувати AnimatedMount?
Rokit

Не працює у мене, ось моя пісочниця: codesandbox.io/s/p9m5625v6m
Веб-жінка

11

Ось моє рішення з використанням нового 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 .


1
елегантне рішення, було б чудово, якщо б ви додали кілька коментарів :)
Веб-жінка

також навіщо використовувати розширення typecrypt, оскільки воно добре працює у розширенні javascript?
Webwoman

також ваша консоль повертає "не вдається знайти простір імен NodeJS тайм-аут"
веб-жінка

1
@Webwoman Дякую за ваші коментарі. Я не можу відтворити вашу повідомлення про проблему з "таймаутом NodeJS", див. Моє посилання CodeSandbox під відповіддю. Щодо TypeScript, я особисто вважаю за краще використовувати його замість JavaScript, хоча обидва, звичайно, життєздатні.
deckele

9

Я протистояв цій проблемі під час своєї роботи, і, здавалося б, просто, вона насправді не в 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зміни прапора, ми переходимо до Prep Stage, де обчислюємо необхідні властивості, як heightі widthз ReactDOM.findDOMNode.getBoundingClientRect().

Потім, ввівши стан Animate, ми можемо використовувати перехід css для зміни висоти, ширини та непрозорості від 0 до обчислених значень (або до 0, якщо демонтуємо).

В кінці переходу ми використовуємо onTransitionEndapi, щоб повернутися до Staticстадії.

Є набагато більше подробиць про те, як сцени переносяться плавно, але це може бути загальною ідеєю :)

Якщо хтось цікавиться, я створив бібліотеку React https://github.com/MingruiZhang/react-animate-mount, щоб поділитися своїм рішенням. Будь-який відгук вітаємо :)


Дякуємо за відгук, вибачте за грубу відповідь раніше. Я додав більше деталей та схему до своєї відповіді, сподіваюся, це може бути кориснішим для інших.
Mingrui Zhang

1
@MingruiZhang Приємно бачити, що ви сприйняли коментарі позитивно і покращили свою відповідь. Це дуже освіжає бачити. Гарна робота.
Баги

6

Я думаю, що використання Transitionвід react-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);

1
Якщо ви використовуєте стилізовані компоненти, ви можете просто передати showprop H1і виконати всю логіку всередині стилізованого компонента. Як ...animation: ${({ show }) => show ? entranceKeyframes : exitKeyframes} 300ms ease-out forwards;
Алекс

2

Рух Фреймера

Встановіть 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>
)

1

Для тих, хто розглядає реакцію-рух, анімація окремого компонента при його монтажі та демонтажі може бути надзвичайно складною для налаштування.

Існує бібліотека з реакцією-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 визначає, яким повинен бути кінцевий стан компонента; залишити - це стиль, який застосовується, коли компонент демонтується.

Ви можете виявити, що після того, як ви використовували пакет інтерфейсів кілька разів, бібліотека response-motion може бути вже не такою страшною.


Проект більше не підтримується (2018)
Micros,


1

Це легко зробити, використовуючи CSSTransitionкомпонент з react-transition-group, який точно так само, як і бібліотеки, про які ви згадали. Фокус у тому, що вам потрібно обернути компонент CSSTransition без механізму show / hide, як це зазвичай робиться .ie. {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;
}

0

Ось мої 2cents: дякую @deckele за його рішення. Моє рішення базується на його версії, це компонентна версія stateful, повністю багаторазова.

ось моя пісочниця: 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);

0

Ось як я це вирішив у 2019 році, роблячи навантажувальну блешню. Я використовую функціональні компоненти React.

У мене є батьківський компонент програми, який має дочірній компонент Spinner .

Додаток має статус для завантаження програми чи ні. Коли програма завантажується, Spinner відображається нормально. Коли програма не завантажується ( isLoadingневірно), Spinner відображається за допомогою prop 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);
    }
}

0

Я також гостро потребував однокомпонентної анімації. Я втомився від React Motion, але я витягував волосся за такий тривіальний випуск .. (я річ). Після деякого гуглювання я натрапив на цю публікацію на їхньому git-репо. Сподіваюся, це комусь допомагає ..

Посилання з & також кредит . Зараз це працює для мене. Моїм випадком використання було модальне анімування та демонтаж на випадок завантаження та розвантаження.

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>
    );
  }
}


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для назв класів та for <Container/>, але обидва вони можуть бути легко замінені на власний CSS та звичайний старий <div>.


0

Якщо я використовую Velocityабо AnimeJSбібліотеку для безпосередньої анімації вузла (замість cssабо setTimeout), то я з'ясував, що можу розробити a, 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()
  })
}

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.