ตอบสนอง - ทำให้เมานต์เคลื่อนไหวและยกเลิกการต่อเชื่อมส่วนประกอบเดียว


103

สิ่งที่เรียบง่ายนี้ควรทำได้อย่างง่ายดาย แต่ฉันกำลังดึงผมออกว่ามันซับซ้อนแค่ไหน

สิ่งที่ฉันต้องการทำคือทำให้การติดตั้งและการยกเลิกการต่อเชื่อมคอมโพเนนต์ React เป็นแบบเคลื่อนไหว นี่คือสิ่งที่ฉันได้ลองทำไปแล้วและสาเหตุที่แต่ละวิธีใช้ไม่ได้ผล:

  1. ReactCSSTransitionGroup - ฉันไม่ได้ใช้คลาส CSS เลยมันเป็นสไตล์ JS ทั้งหมดดังนั้นจึงใช้ไม่ได้
  2. ReactTransitionGroup- API ระดับล่างนี้ยอดเยี่ยม แต่คุณต้องใช้การโทรกลับเมื่อภาพเคลื่อนไหวเสร็จสมบูรณ์ดังนั้นเพียงแค่ใช้การเปลี่ยน CSS จะไม่ทำงานที่นี่ มีไลบรารีแอนิเมชั่นอยู่เสมอซึ่งนำไปสู่ประเด็นต่อไป:
  3. GreenSock - การออกใบอนุญาตมีข้อ จำกัด มากเกินไปสำหรับการใช้งานทางธุรกิจ IMO
  4. React Motion - ดูเหมือนจะดี แต่TransitionMotionก็สับสนและซับซ้อนเกินไปสำหรับสิ่งที่ฉันต้องการ
  5. แน่นอนว่าฉันสามารถใช้กลอุบายได้เช่นเดียวกับ UI ของวัสดุโดยที่องค์ประกอบต่างๆถูกแสดงผล แต่ยังคงซ่อนอยู่ ( left: -10000px) แต่ฉันไม่อยากไปเส้นทางนั้น ฉันคิดว่ามันแฮ็คและฉันต้องการให้ส่วนประกอบของฉันยกเลิกการต่อเชื่อมเพื่อทำความสะอาดและไม่เกะกะ DOM

ฉันต้องการสิ่งที่ง่ายต่อการนำไปใช้ เมื่อเมานต์ให้สร้างชุดสไตล์ให้เคลื่อนไหว เมื่อยกเลิกการต่อเชื่อมให้เคลื่อนไหวชุดรูปแบบเดียวกัน (หรืออื่น) เสร็จแล้ว นอกจากนี้ยังต้องมีประสิทธิภาพสูงในหลายแพลตฟอร์ม

ฉันเคยชนกำแพงอิฐที่นี่ หากฉันทำอะไรหายไปและมีวิธีง่ายๆในการดำเนินการนี้โปรดแจ้งให้เราทราบ


เรากำลังพูดถึงแอนิเมชั่นประเภทใดที่นี่?
Pranesh Ravi

สิ่งง่ายๆเช่นความทึบของ CSS จะจางลงและtransform: scale
ffxsam

จุดที่ 1 และ 2 ทำให้ฉันสับสน คุณใช้ภาพเคลื่อนไหวประเภทใด การเปลี่ยน JS หรือการเปลี่ยน CSS?
Pranesh Ravi

1
อย่าสับสนระหว่างสไตล์ / คลาส CSS (เช่น.thing { color: #fff; }) กับสไตล์ JS ( const styles = { thing: { color: '#fff' } }))
ffxsam

แต่ปัญหาคือเมื่อคุณพยายามเปลี่ยนสไตล์โดยใช้จาวาสคริปต์คุณกำลังแทนที่รูปแบบขององค์ประกอบซึ่งจะไม่ทำให้เกิดการเปลี่ยนแปลงใด ๆ
Pranesh Ravi

คำตอบ:


106

มันยาวไปหน่อย แต่ฉันใช้เหตุการณ์และวิธีการดั้งเดิมทั้งหมดเพื่อให้ได้แอนิเมชั่นนี้ ไม่มี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ซึ่งอยู่ภายใต้เหตุการณ์การเปลี่ยนแปลง
Pranesh Ravi

1
คุณรู้ได้อย่างไรว่ามันทำอะไรเอกสารไม่ได้อธิบายอะไรเลย คำถามอื่น: คุณรู้ได้อย่างไรว่าcomponentWillReceivePropsสามารถคืนสินค้าได้? ฉันสามารถอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ที่ไหน
ffxsam

1
@ffxsam onTransitionEnd เป็นเหตุการณ์ JavaScript ดั้งเดิม คุณสามารถ Google เกี่ยวกับเรื่องนี้ facebook.github.io/react/docs/…จะให้ความคิดเกี่ยวกับ componentWillReceiveProps
Pranesh Ravi

7
BTW ฉันคิดว่ามีข้อผิดพลาดในรหัสของคุณ ในParentส่วนประกอบของคุณคุณอ้างอิงthis.transitionEnd
ffxsam

19

นี่คือวิธีแก้ปัญหาของฉันโดยใช้ hooks 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


3
ทางออกที่สวยงามจะดีมากถ้าคุณได้เพิ่มความคิดเห็น :)
Webwoman

เหตุใดจึงต้องใช้นามสกุลของ typescrypt เนื่องจากทำงานได้ดีในส่วนขยายของ javascript
Webwoman

และคอนโซลของคุณส่งคืน "ไม่พบเนมสเปซ NodeJS timeout"
Webwoman

1
@Webwoman ขอบคุณสำหรับความคิดเห็นของคุณ ฉันไม่สามารถสร้างปัญหาที่คุณรายงานใหม่ด้วย "NodeJS timeout" ได้โปรดดูลิงก์ CodeSandbox ของฉันด้านล่างคำตอบ เกี่ยวกับ TypeScript โดยส่วนตัวแล้วฉันชอบใช้มันผ่าน JavaScript แม้ว่าทั้งสองจะทำงานได้แน่นอน
Deckele

15

ด้วยการใช้ความรู้ที่ได้รับจากคำตอบของ Pranesh ฉันได้หาวิธีแก้ปัญหาอื่นที่สามารถกำหนดค่าได้และนำกลับมาใช้ใหม่ได้:

const AnimatedMount = ({ unmountedStyle, mountedStyle }) => {
  return (Wrapped) => class extends Component {
    constructor(props) {
      super(props);
      this.state = {
        style: unmountedStyle,
      };
    }

    componentWillEnter(callback) {
      this.onTransitionEnd = callback;
      setTimeout(() => {
        this.setState({
          style: mountedStyle,
        });
      }, 20);
    }

    componentWillLeave(callback) {
      this.onTransitionEnd = callback;
      this.setState({
        style: unmountedStyle,
      });
    }

    render() {
      return <div
        style={this.state.style}
        onTransitionEnd={this.onTransitionEnd}
      >
        <Wrapped { ...this.props } />
      </div>
    }
  }
};

การใช้งาน:

import React, { PureComponent } from 'react';

class Thing extends PureComponent {
  render() {
    return <div>
      Test!
    </div>
  }
}

export default AnimatedMount({
  unmountedStyle: {
    opacity: 0,
    transform: 'translate3d(-100px, 0, 0)',
    transition: 'opacity 250ms ease-out, transform 250ms ease-out',
  },
  mountedStyle: {
    opacity: 1,
    transform: 'translate3d(0, 0, 0)',
    transition: 'opacity 1.5s ease-out, transform 1.5s ease-out',
  },
})(Thing);

และสุดท้ายในrenderวิธีการของส่วนประกอบอื่น:

return <div>
  <ReactTransitionGroup>
    <Thing />
  </ReactTransitionGroup>
</div>

1
และคุณจะเมานต์ / ยกเลิกการต่อเชื่อม @ffxsam ได้อย่างไร?

วิธีการcomponentWillLeave()และcomponentWillEnter()ได้รับการเรียกว่าAnimatedMount?
Rokit

ไม่ได้ผลสำหรับฉันที่นี่แซนด์บ็อกซ์ของฉัน: codeandbox.io/s/p9m5625v6m
Webwoman

10

ฉันตอบโต้ปัญหานี้ในระหว่างการทำงานของฉันและเรียบง่ายอย่างที่ดูเหมือนว่ามันไม่ได้อยู่ในปฏิกิริยา ในสถานการณ์ปกติที่คุณแสดงสิ่งต่างๆเช่น:

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

เมื่อมีthis.state.showการเปลี่ยนแปลงเด็ก ๆ จะติดตั้ง / ยกเลิกการต่อเชื่อมทันที

แนวทางหนึ่งที่ฉันใช้คือการสร้างส่วนประกอบของกระดาษห่อหุ้มAnimateและใช้มันเช่น

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

ขณะนี้เมื่อมีthis.state.showการเปลี่ยนแปลงเราสามารถรับรู้การเปลี่ยนแปลงของเสาgetDerivedStateFromProps(componentWillReceiveProps)และสร้างขั้นตอนการเรนเดอร์ระดับกลางเพื่อแสดงภาพเคลื่อนไหว

รอบเวทีอาจมีลักษณะเช่นนี้

เราเริ่มต้นด้วยStatic Stageเมื่อเด็ก ๆ ติดตั้งหรือไม่ได้ต่อเชื่อม

เมื่อเราตรวจสอบshowการเปลี่ยนแปลงธงที่เราป้อนเตรียมเวทีที่เราคำนวณคุณสมบัติที่จำเป็นเช่นheightและจากwidthReactDOM.findDOMNode.getBoundingClientRect()

จากนั้นเข้าสู่Animate Stateเราสามารถใช้การเปลี่ยน css เพื่อเปลี่ยนความสูงความกว้างและความทึบจาก 0 เป็นค่าที่คำนวณได้ (หรือเป็น 0 หากยกเลิกการต่อเชื่อม)

ในตอนท้ายของการเปลี่ยนแปลงเราใช้onTransitionEndapi เพื่อเปลี่ยนกลับไปที่ Staticขั้นตอน

มีรายละเอียดเพิ่มเติมเกี่ยวกับการถ่ายโอนขั้นตอนอย่างราบรื่น แต่นี่อาจเป็นแนวคิดโดยรวม :)

หากใครสนใจฉันได้สร้างไลบรารี React https://github.com/MingruiZhang/react-animate-mountเพื่อแบ่งปันวิธีแก้ปัญหาของฉัน ยินดีรับข้อเสนอแนะใด ๆ :)


ขอบคุณสำหรับความคิดเห็นขออภัยสำหรับคำตอบที่หยาบคายก่อนหน้านี้ ฉันได้เพิ่มรายละเอียดและแผนภาพให้กับคำตอบของฉันหวังว่านี่จะเป็นประโยชน์กับคนอื่น ๆ
Mingrui Zhang

2
@MingruiZhang เป็นเรื่องดีที่คุณได้แสดงความคิดเห็นในเชิงบวกและปรับปรุงคำตอบของคุณ มันสดชื่นมากที่ได้เห็น การทำงานที่ดี.
Bugs

6

ฉันคิดว่าการใช้Transitionfrom react-transition-groupน่าจะเป็นวิธีที่ง่ายที่สุดในการติดตามการติดตั้ง / ยกเลิกการต่อเชื่อม มีความยืดหยุ่นอย่างไม่น่าเชื่อ ฉันใช้คลาสบางคลาสเพื่อแสดงให้เห็นว่ามันใช้งานง่ายแค่ไหน แต่คุณสามารถเชื่อมต่อแอนิเมชั่น JS ของคุณเองได้โดยใช้addEndListenerprop ซึ่งฉันก็โชคดีมากที่ได้ใช้ 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);

3
หากคุณใช้ส่วนประกอบที่มีสไตล์คุณก็สามารถส่งshowเสาไปH1และทำตรรกะทั้งหมดภายในองค์ประกอบที่มีสไตล์ได้ Like ...animation: ${({ show }) => show ? entranceKeyframes : exitKeyframes} 300ms ease-out forwards;
Aleks


1

สำหรับผู้ที่พิจารณาการเคลื่อนไหวแบบตอบสนองการสร้างภาพเคลื่อนไหวในองค์ประกอบเดียวเมื่อติดตั้งและยกเลิกการต่อเชื่อมอาจเป็นเรื่องยากที่จะตั้งค่า

มีไลบรารีที่เรียกว่า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 กำหนดว่าสถานะสิ้นสุดของส่วนประกอบควรเป็นอย่างไร ลาคือรูปแบบที่ใช้เมื่อยกเลิกการต่อเชื่อมส่วนประกอบ

คุณอาจพบว่าเมื่อคุณใช้ UI แพ็คสองสามครั้งไลบรารีการเคลื่อนไหวตอบสนองอาจไม่น่ากลัวอีกต่อไป


โครงการไม่ได้รับการดูแลอีกต่อไป (2018)
Micros


1

ซึ่งสามารถทำได้อย่างง่ายดายโดยใช้CSSTransitionส่วนประกอบreact-transition-groupซึ่งเหมือนกับไลบรารีที่คุณกล่าวถึง เคล็ดลับคือคุณต้องห่อส่วนประกอบ CSSTransition โดยไม่มีกลไกแสดง / ซ่อนเหมือนที่คุณทำโดยทั่วไป .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

ที่นี่ 2 เซ็นต์ของฉัน: ขอบคุณ @deckele สำหรับวิธีแก้ปัญหาของเขา วิธีแก้ปัญหาของฉันขึ้นอยู่กับของเขาซึ่งเป็นเวอร์ชันส่วนประกอบของ stateful ซึ่งสามารถใช้ซ้ำได้อย่างสมบูรณ์

ที่นี่ Sandbox ของฉัน: https://codesandbox.io/s/302mkm1m 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 ในขณะที่สร้างสปินเนอร์โหลด ฉันใช้ส่วนประกอบการทำงานของปฏิกิริยา

ฉันมีส่วนประกอบของแอพหลักที่มีส่วนประกอบลูกสปินเนอร์

แอปมีสถานะว่าแอปกำลังโหลดหรือไม่ เมื่อแอปกำลังโหลดSpinnerจะแสดงผลตามปกติ เมื่อ app ไม่โหลด ( isLoadingเป็นเท็จ) ปินเนอร์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ระดับเคลื่อนไหวมันจางหายไปใน. เมื่อปินเนอร์ได้รับไม้ค้ำยันshouldUnmountจะแสดงผลกับSpinner-fadeOutระดับแทนการเคลื่อนไหวมันออกซีดจาง

อย่างไรก็ตามฉันต้องการให้คอมโพเนนต์ยกเลิกการต่อเชื่อมหลังจากเลือนหายไป

ณ จุดนี้ฉันลองใช้onAnimationEndเหตุการณ์สังเคราะห์ปฏิกิริยาซึ่งคล้ายกับโซลูชันของ @ 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 แต่ฉันกำลังดึงเส้นขนของฉันสำหรับปัญหาที่ไม่สำคัญเช่นนี้ .. (ฉันว่า) หลังจาก googling ฉันเจอโพสต์นี้ใน git repo ของพวกเขา หวังว่าจะช่วยใครสักคน ..

อ้างอิงจาก & เครดิตด้วย สิ่งนี้ใช้ได้กับฉัน ณ ตอนนี้ กรณีการใช้งานของฉันเป็นโมดอลเพื่อทำให้เคลื่อนไหวและยกเลิกการต่อเชื่อมในกรณีที่โหลดและยกเลิกการโหลด

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สำหรับชื่อคลาสและสำหรับ<Container/>แต่ทั้งสองอย่างสามารถแทนที่ได้อย่างง่ายดายด้วย CSS ที่กำหนดเองและแบบเก่า<div>ทั่วไป


0

หากฉันใช้VelocityหรือAnimeJSไลบรารีเพื่อทำให้โหนดเคลื่อนไหวโดยตรง (แทนcssหรือsetTimeout) ฉันพบว่าฉันสามารถออกแบบ a hookเพื่อให้สถานะภาพเคลื่อนไหวonและฟังก์ชันonToggleเริ่มต้นการเคลื่อนไหวได้ (เช่น slidedown, fade)

โดยทั่วไปสิ่งที่เบ็ดทำคือการเปิดและปิดภาพเคลื่อนไหวและหลังจากนั้นให้อัปเดตonตามนั้น ดังนั้นเราจึงสามารถรับสถานะของภาพเคลื่อนไหวได้อย่างแม่นยำ โดยไม่ต้องทำเช่นนั้นจะตอบใน durationAd-hoc

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


0

คุณสามารถใช้React SyntheticEventได้

กับเหตุการณ์เช่น onAnimationEndหรือonTransitionEndคุณสามารถประสบความสำเร็จที่

ตอบสนองเอกสาร: https://reactjs.org/docs/events.html#animation-events

ตัวอย่างโค้ด: https://dev.to/michalczaplinski/super-easy-react-mount-unmount-animations-with-hooks-4foj


0

คุณสามารถใช้วิธี React lifecycle ได้เสมอ แต่ react-transition-group เป็นไลบรารีที่สะดวกที่สุดสำหรับภาพเคลื่อนไหวที่ฉันเจอไม่ว่าคุณจะใช้styled-componentsหรือ css ธรรมดา มีประโยชน์อย่างยิ่งเมื่อคุณต้องการติดตามการติดตั้งและการยกเลิกการต่อเชื่อมส่วนประกอบของคุณและแสดงภาพเคลื่อนไหวตามนั้น ใช้Transitionกับองค์ประกอบสไตล์และCSSTransitionเมื่อคุณใช้ชื่อคลาส css ธรรมดา

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.