React - animate mount dan unmount dari satu komponen

103

Sesuatu yang sesederhana ini seharusnya dapat dengan mudah dilakukan, namun saya berusaha menjelaskan betapa rumitnya hal itu.

Yang ingin saya lakukan adalah menganimasikan proses pemasangan & pelepasan komponen React, itu saja. Inilah yang telah saya coba sejauh ini dan mengapa setiap solusi tidak berfungsi:

  1. ReactCSSTransitionGroup - Saya tidak menggunakan kelas CSS sama sekali, itu semua gaya JS, jadi ini tidak akan berhasil.
  2. ReactTransitionGroup- API tingkat rendah ini bagus, tetapi mengharuskan Anda menggunakan callback saat animasinya selesai, jadi hanya menggunakan transisi CSS tidak akan berfungsi di sini. Selalu ada pustaka animasi, yang mengarah ke poin berikutnya:
  3. GreenSock - Lisensi terlalu ketat untuk penggunaan bisnis IMO.
  4. React Motion - Ini tampak hebat, tetapi TransitionMotionsangat membingungkan dan terlalu rumit untuk apa yang saya butuhkan.
  5. Tentu saja saya bisa melakukan tipu daya seperti Material UI, di mana elemen dirender tetapi tetap tersembunyi ( left: -10000px) tetapi saya lebih suka tidak pergi ke rute itu. Saya menganggapnya hacky, dan saya ingin komponen saya di-unmount sehingga mereka membersihkan dan tidak mengacaukan DOM.

Saya ingin sesuatu yang mudah diterapkan. Saat dipasang, animasikan satu set gaya; pada unmount, animasikan set gaya yang sama (atau lainnya). Selesai. Ini juga harus memiliki kinerja tinggi di berbagai platform.

Saya telah menabrak dinding bata di sini. Jika saya melewatkan sesuatu dan ada cara mudah untuk melakukannya, beri tahu saya.

ffxsam.dll
sumber
Jenis animasi apa yang kita bicarakan di sini?
Pranesh Ravi
Hanya sesuatu yang sederhana, seperti opasitas CSS yang memudar dantransform: scale
ffxsam
Poin 1 dan 2 membingungkan saya. Jenis animasi apa yang Anda gunakan? Transisi JS atau transisi CSS?
Pranesh Ravi
1
Jangan bingung antara gaya / kelas CSS (misalnya .thing { color: #fff; }) dengan gaya JS ( const styles = { thing: { color: '#fff' } }))
ffxsam
Tapi masalahnya, saat Anda mencoba mengubah gaya menggunakan javascript, sebenarnya Anda mengganti gaya elemen yang tidak akan memberikan transisi apa pun.
Pranesh Ravi

Jawaban:

104

Ini agak panjang tetapi saya telah menggunakan semua peristiwa dan metode asli untuk mencapai animasi ini. Tidak ReactCSSTransitionGroup, ReactTransitionGroupdan lain-lain.

Hal-hal yang telah saya gunakan

  • Metode siklus hidup React
  • onTransitionEnd peristiwa

Bagaimana ini bekerja

  • Pasang elemen berdasarkan mount prop passing ( mounted) dan dengan gaya default ( opacity: 0)
  • Setelah memasang atau memperbarui, gunakan componentDidMount( componentWillReceivePropsuntuk pembaruan lebih lanjut) untuk mengubah gaya ( opacity: 1) dengan waktu tunggu (untuk membuatnya asinkron).
  • Selama pelepasan, teruskan prop ke komponen untuk mengidentifikasi pelepasan, ubah gaya lagi ( opacity: 0) onTransitionEnd,, hapus pelepasan elemen dari DOM.

Lanjutkan siklusnya.

Lihat kodenya, Anda akan mengerti. Jika ada klarifikasi diperlukan, silakan tinggalkan komentar.

Semoga ini membantu.

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 Ravi
sumber
Terima kasih untuk ini! Di mana Anda belajar onTransitionEnd? Saya tidak melihatnya di dokumen React.
ffxsam
@ffxsam facebook.github.io/react/docs/events.html Berada di bawah peristiwa transisi.
Pranesh Ravi
1
Bagaimana Anda tahu apa yang dilakukannya, dokumentasi tidak menjelaskan apa pun. Pertanyaan lain: bagaimana Anda tahu componentWillReceivePropsbisa mengembalikan sesuatu? Di mana saya bisa membaca lebih banyak tentang itu?
ffxsam
1
@ffxsam onTransitionEnd adalah acara JavaScript asli. Anda bisa google tentang itu. facebook.github.io/react/docs/… akan memberi Anda gambaran tentang componentWillReceiveProps.
Pranesh Ravi
7
BTW Saya pikir ada kesalahan dalam kode Anda. Dalam Parentkomponen Anda, Anda mereferensikanthis.transitionEnd
ffxsam
15

Menggunakan pengetahuan yang diperoleh dari jawaban Pranesh, saya menemukan solusi alternatif yang dapat dikonfigurasi dan digunakan kembali:

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

Pemakaian:

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

Dan terakhir, dalam rendermetode komponen lain :

return <div>
  <ReactTransitionGroup>
    <Thing />
  </ReactTransitionGroup>
</div>
ffxsam.dll
sumber
1
Dan bagaimana Anda me-mount / unmount @ffxsam?
Bagaimana componentWillLeave()dan componentWillEnter()dipanggil AnimatedMount?
Rokit
Tidak berfungsi untuk saya, di sini kotak pasir saya: codesandbox.io/s/p9m5625v6m
Webwoman
15

Berikut adalah solusi saya menggunakan API kait baru (dengan TypeScript), berdasarkan posting ini , untuk menunda fase pelepasan komponen:

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

Pemakaian:

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

Link CodeSandbox .

deckele
sumber
2
solusi elegan, akan sangat bagus jika Anda menambahkan beberapa komentar :)
Webwoman
juga mengapa menggunakan ekstensi typescrypt karena berfungsi dengan baik di ekstensi javascript?
Webwoman
juga konsol Anda kembali "tidak dapat menemukan namespace NodeJS timeout"
Webwoman
1
@Webwoman Terima kasih atas komentar Anda. Saya tidak dapat membuat ulang masalah yang Anda laporkan dengan "NodeJS timeout", lihat tautan CodeSandbox saya di bawah jawabannya. Mengenai TypeScript, saya pribadi lebih suka menggunakannya daripada JavaScript, meskipun keduanya tentu saja dapat dijalankan.
deckele
10

Saya melawan masalah ini selama bekerja, dan sesederhana kelihatannya, sebenarnya tidak ada di React. Dalam skenario normal di mana Anda merender sesuatu seperti:

this.state.show ? {childen} : null;

sebagai this.state.showperubahan, anak-anak segera dipasang / dilepas.

Salah satu pendekatan yang saya ambil adalah membuat komponen pembungkus Animatedan menggunakannya seperti

<Animate show={this.state.show}>
  {childen}
</Animate>

sekarang sebagai this.state.showperubahan, kita dapat melihat perubahan prop dengan getDerivedStateFromProps(componentWillReceiveProps)dan membuat tahap render perantara untuk melakukan animasi.

Siklus tahapan mungkin terlihat seperti ini

Kami mulai dengan Static Stage ketika anak-anak dipasang atau dilepas.

Setelah kami mendeteksi showperubahan bendera, kami memasuki Tahap Persiapan tempat kami menghitung properti yang diperlukan seperti heightdan widthdari ReactDOM.findDOMNode.getBoundingClientRect().

Kemudian memasuki Animate State kita dapat menggunakan transisi css untuk mengubah tinggi, lebar dan opacity dari 0 ke nilai yang dihitung (atau ke 0 jika melepas).

Di akhir transisi, kami menggunakan onTransitionEndapi untuk kembali ke Statictahap.

Ada lebih banyak detail tentang bagaimana tahapan transfer dengan lancar tetapi ini bisa menjadi ide keseluruhan :)

Jika ada yang tertarik, saya membuat perpustakaan React https://github.com/MingruiZhang/react-animate-mount untuk membagikan solusi saya. Setiap umpan balik diterima :)

Mingrui Zhang
sumber
Terima kasih atas tanggapan Anda, maaf atas jawaban kasar tadi. Saya menambahkan lebih banyak detail dan diagram ke jawaban saya, semoga ini bisa lebih membantu orang lain.
Mingrui Zhang
2
@MingruiZhang Senang melihat Anda menerima komentar secara positif dan memperbaiki jawaban Anda. Sangat menyegarkan untuk dilihat. Kerja bagus.
Bug
6

Saya pikir menggunakan Transitionfrom react-transition-groupmungkin adalah cara termudah untuk melacak pemasangan / pelepasan. Ini sangat fleksibel. Saya menggunakan beberapa kelas untuk menunjukkan betapa mudahnya digunakan tetapi Anda pasti dapat menghubungkan animasi JS Anda sendiri dengan menggunakanaddEndListener prop - yang saya juga beruntung menggunakan GSAP.

Kotak pasir: https://codesandbox.io/s/k9xl9mkx2o

Dan inilah kode saya.

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);
Shalanah
sumber
1
Jika Anda menggunakan komponen bergaya, Anda cukup meneruskan showprop ke H1dan melakukan semua logika di dalam komponen bergaya. Seperti ...animation: ${({ show }) => show ? entranceKeyframes : exitKeyframes} 300ms ease-out forwards;
Aleks
3

Gerakan pembingkai

Instal framer-motion dari 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>
)
tony95
sumber
1

Bagi mereka yang mempertimbangkan react-motion, menganimasikan satu komponen saat dipasang dan dilepas bisa sangat melelahkan untuk disiapkan.

Ada perpustakaan yang disebut react-motion-ui-pack yang membuat proses ini jauh lebih mudah untuk dimulai. Ini adalah pembungkus di sekitar react-motion, yang berarti Anda mendapatkan semua manfaat dari perpustakaan (yaitu Anda dapat menghentikan animasi, memiliki beberapa unmount yang terjadi pada saat yang bersamaan).

Pemakaian:

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 mendefinisikan apa status akhir dari komponen tersebut; leave adalah gaya yang diterapkan saat komponen dilepas.

Anda mungkin menemukan bahwa setelah Anda menggunakan paket UI beberapa kali, library react-motion mungkin tidak lagi menakutkan.

Björn Holdt
sumber
Project tidak lagi dipertahankan (2018)
Micros
1

Ini dapat dilakukan dengan mudah menggunakan CSSTransitionkomponen dari react-transition-group, yang sama seperti pustaka yang Anda sebutkan. Caranya adalah Anda perlu untuk membungkus komponen CSSTransition tanpa mekanisme show / hide seperti Anda biasanya akan Yaitu {show && <Child>}...Jika Anda menyembunyikan animasi dan tidak akan bekerja. Contoh:

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;
}
Lauren
sumber
0

Ini 2cents saya: terima kasih kepada @deckele untuk solusinya. Solusi saya didasarkan pada dia, ini adalah versi komponen stateful, dapat digunakan kembali sepenuhnya.

di sini sandbox saya: https://codesandbox.io/s/302mkm1m .

di sini snippet.js saya:

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);
Wanita web
sumber
0

Inilah cara saya menyelesaikan ini pada tahun 2019, sambil membuat pemintal pemuatan. Saya menggunakan komponen fungsional React.

Saya memiliki komponen App induk yang memiliki komponen Spinner anak .

Aplikasi memiliki status apakah aplikasi sedang memuat atau tidak. Saat aplikasi dimuat, Spinner dirender secara normal. Saat aplikasi tidak memuat ( isLoadingsalah) Spinner dirender dengan 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 memiliki status apakah itu tersembunyi atau tidak. Pada awalnya, dengan props dan status default, Spinner dirender secara normal. The Spinner-fadeInkelas menjiwai itu memudar di. Ketika Spinner menerima prop shouldUnmountitu membuat dengan Spinner-fadeOutkelas sebaliknya, menghidupkan itu memudar.

Namun saya juga ingin komponen dilepas setelah memudar.

Pada titik ini saya mencoba menggunakan onAnimationEndacara sintetis React, mirip dengan solusi @ pranesh-ravi di atas, tetapi tidak berhasil. Sebagai gantinya saya biasa setTimeoutmengatur status menjadi tersembunyi dengan penundaan yang sama panjangnya dengan animasinya. Spinner akan memperbarui setelah penundaan denganisHidden === true , dan tidak ada yang akan dirender.

Kuncinya di sini adalah bahwa orang tua tidak melepas anak, ia memberi tahu anak kapan harus melepas, dan anak melepas sendiri setelah menangani urusan pelepasannya.

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);
    }
}
mjw
sumber
0

Saya juga sangat membutuhkan Animasi komponen tunggal. Saya lelah menggunakan React Motion tetapi saya menarik rambut saya untuk masalah sepele .. (i thing). Setelah googling beberapa saya menemukan posting ini di repo git mereka. Semoga bisa membantu seseorang ..

Direferensikan Dari & juga kredit . Ini bekerja untuk saya mulai sekarang. Kasus penggunaan saya adalah modal untuk menganimasikan dan melepas jika terjadi pemuatan dan pembongkaran.

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

Rahul Singh
sumber
0

Saya tahu ada banyak jawaban di sini, tetapi saya masih belum menemukan jawaban yang sesuai dengan kebutuhan saya. Saya ingin:

  • Komponen fungsional
  • Solusi yang memungkinkan komponen saya dengan mudah memudar masuk / keluar saat dipasang / dilepas.

Setelah berjam-jam mengutak-atik, saya memiliki solusi yang berhasil, saya katakan 90%. Saya telah menulis batasan di blok komentar pada kode di bawah ini. Saya masih menyukai solusi yang lebih baik, tetapi ini yang terbaik yang saya temukan, termasuk solusi lain di sini.

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

Jika Anda memiliki komponen yang ingin Anda fade in / out, bungkus dengan <Fade>Ex. <Fade><MyComponent/><Fade>.

Perhatikan bahwa ini digunakan react-bootstrapuntuk nama kelas dan untuk <Container/>, tetapi keduanya dapat dengan mudah diganti dengan CSS khusus dan yang lama biasa <div>.

Aaron Sullivan
sumber
0

Jika saya menggunakan Velocityatau AnimeJSperpustakaan untuk menganimasikan node secara langsung (bukan cssatau setTimeout), maka saya menemukan bahwa saya dapat mendesain a hookuntuk memberikan status ondan fungsi animasionToggle untuk memulai animasi (mis. Slidedown, fade).

Pada dasarnya apa yang dilakukan hook adalah mengaktifkan dan menonaktifkan animasi, dan kemudian memperbarui yang onsesuai. Dengan demikian kita bisa mendapatkan status animasinya secara akurat. Tanpa melakukan itu akan membalas secara ad-hoc 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

Penggunaannya adalah sebagai berikut,

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

dan implementasi yang beranimasi bisa jadi,

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

windmaomao.dll
sumber
0

Anda selalu dapat menggunakan metode siklus hidup React tetapi react-transisi-group sejauh ini merupakan perpustakaan paling nyaman untuk animasi yang pernah saya temui, Apakah Anda menggunakan styled-componentsatau css biasa. Ini sangat berguna ketika Anda ingin melacak pemasangan dan pelepasan komponen Anda dan membuat animasi yang sesuai. Gunakan Transitiondengan komponen bergaya dan CSSTransitionsaat Anda menggunakan nama kelas css biasa.

webmessi
sumber