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:
ReactCSSTransitionGroup
- Saya tidak menggunakan kelas CSS sama sekali, itu semua gaya JS, jadi ini tidak akan berhasil.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:- GreenSock - Lisensi terlalu ketat untuk penggunaan bisnis IMO.
- React Motion - Ini tampak hebat, tetapi
TransitionMotion
sangat membingungkan dan terlalu rumit untuk apa yang saya butuhkan. - 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.
animation
reactjs
css-animations
gsap
react-motion
ffxsam.dll
sumber
sumber
transform: scale
.thing { color: #fff; }
) dengan gaya JS (const styles = { thing: { color: '#fff' } }
))Jawaban:
Ini agak panjang tetapi saya telah menggunakan semua peristiwa dan metode asli untuk mencapai animasi ini. Tidak
ReactCSSTransitionGroup
,ReactTransitionGroup
dan lain-lain.Hal-hal yang telah saya gunakan
onTransitionEnd
peristiwaBagaimana ini bekerja
mounted
) dan dengan gaya default (opacity: 0
)componentDidMount
(componentWillReceiveProps
untuk pembaruan lebih lanjut) untuk mengubah gaya (opacity: 1
) dengan waktu tunggu (untuk membuatnya asinkron).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>
sumber
onTransitionEnd
? Saya tidak melihatnya di dokumen React.componentWillReceiveProps
bisa mengembalikan sesuatu? Di mana saya bisa membaca lebih banyak tentang itu?Parent
komponen Anda, Anda mereferensikanthis.transitionEnd
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
render
metode komponen lain :return <div> <ReactTransitionGroup> <Thing /> </ReactTransitionGroup> </div>
sumber
componentWillLeave()
dancomponentWillEnter()
dipanggilAnimatedMount
?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 .
sumber
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.show
perubahan, anak-anak segera dipasang / dilepas.Salah satu pendekatan yang saya ambil adalah membuat komponen pembungkus
Animate
dan menggunakannya seperti<Animate show={this.state.show}> {childen} </Animate>
sekarang sebagai
this.state.show
perubahan, kita dapat melihat perubahan prop dengangetDerivedStateFromProps(componentWillReceiveProps)
dan membuat tahap render perantara untuk melakukan animasi.Kami mulai dengan Static Stage ketika anak-anak dipasang atau dilepas.
Setelah kami mendeteksi
show
perubahan bendera, kami memasuki Tahap Persiapan tempat kami menghitung properti yang diperlukan sepertiheight
danwidth
dariReactDOM.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
onTransitionEnd
api untuk kembali keStatic
tahap.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 :)
sumber
Saya pikir menggunakan
Transition
fromreact-transition-group
mungkin 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);
sumber
show
prop keH1
dan melakukan semua logika di dalam komponen bergaya. Seperti ...animation: ${({ show }) => show ? entranceKeyframes : exitKeyframes} 300ms ease-out forwards;
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> )
sumber
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.
sumber
Menganimasikan transisi masuk dan keluar jauh lebih mudah dengan react-move .
contoh pada codesandbox
sumber
Ini dapat dilakukan dengan mudah menggunakan
CSSTransition
komponen darireact-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; }
sumber
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);
sumber
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 (
isLoading
salah) Spinner dirender dengan propshouldUnmount
.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-fadeIn
kelas menjiwai itu memudar di. Ketika Spinner menerima propshouldUnmount
itu membuat denganSpinner-fadeOut
kelas sebaliknya, menghidupkan itu memudar.Namun saya juga ingin komponen dilepas setelah memudar.
Pada titik ini saya mencoba menggunakan
onAnimationEnd
acara sintetis React, mirip dengan solusi @ pranesh-ravi di atas, tetapi tidak berhasil. Sebagai gantinya saya biasasetTimeout
mengatur 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); } }
sumber
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> ); } }
sumber
Saya tahu ada banyak jawaban di sini, tetapi saya masih belum menemukan jawaban yang sesuai dengan kebutuhan saya. Saya ingin:
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-bootstrap
untuk nama kelas dan untuk<Container/>
, tetapi keduanya dapat dengan mudah diganti dengan CSS khusus dan yang lama biasa<div>
.sumber
Jika saya menggunakan
Velocity
atauAnimeJS
perpustakaan untuk menganimasikan node secara langsung (bukancss
atausetTimeout
), maka saya menemukan bahwa saya dapat mendesain ahook
untuk memberikan statuson
dan fungsi animasionToggle
untuk memulai animasi (mis. Slidedown, fade).Pada dasarnya apa yang dilakukan hook adalah mengaktifkan dan menonaktifkan animasi, dan kemudian memperbarui yang
on
sesuai. Dengan demikian kita bisa mendapatkan status animasinya secara akurat. Tanpa melakukan itu akan membalas secara ad-hocduration
./** * 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() }) }
sumber
Anda dapat menggunakan React SyntheticEvent untuk itu.
Dengan acara seperti onAnimationEnd atau onTransitionEnd Anda dapat melakukannya.
React Docs: https://reactjs.org/docs/events.html#animation-events
Contoh Kode: https://dev.to/michalczaplinski/super-easy-react-mount-unmount-animations-with-hooks-4foj
sumber
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-components
atau css biasa. Ini sangat berguna ketika Anda ingin melacak pemasangan dan pelepasan komponen Anda dan membuat animasi yang sesuai. GunakanTransition
dengan komponen bergaya danCSSTransition
saat Anda menggunakan nama kelas css biasa.sumber