React - tạo hiệu ứng gắn kết và ngắt kết nối của một thành phần duy nhất


97

Một cái gì đó đơn giản thế này có thể dễ dàng hoàn thành, nhưng tôi đang giật mình vì nó phức tạp như thế nào.

Tất cả những gì tôi muốn làm là tạo hiệu ứng cho việc gắn và tháo một thành phần React, thế là xong. Đây là những gì tôi đã thử cho đến nay và tại sao mỗi giải pháp không hoạt động:

  1. ReactCSSTransitionGroup - Tôi hoàn toàn không sử dụng các lớp CSS, tất cả đều là kiểu JS, vì vậy điều này sẽ không hoạt động.
  2. ReactTransitionGroup- API cấp thấp hơn này rất tuyệt, nhưng nó yêu cầu bạn sử dụng lệnh gọi lại khi hoạt ảnh hoàn tất, vì vậy chỉ sử dụng chuyển tiếp CSS sẽ không hoạt động ở đây. Luôn có các thư viện hoạt ảnh, dẫn đến điểm tiếp theo:
  3. GreenSock - Việc cấp phép quá hạn chế đối với IMO sử dụng cho doanh nghiệp.
  4. React Motion - Điều này có vẻ tuyệt vời, nhưng TransitionMotioncực kỳ khó hiểu và quá phức tạp đối với những gì tôi cần.
  5. Tất nhiên, tôi có thể thực hiện các thủ thuật như Material UI, trong đó các phần tử được hiển thị nhưng vẫn bị ẩn ( left: -10000px) nhưng tôi không muốn đi theo con đường đó. Tôi coi đó là hack và tôi muốn các thành phần của mình ngắt kết nối để chúng dọn dẹp và không làm lộn xộn DOM.

Tôi muốn một cái gì đó dễ thực hiện. Trên mount, tạo hoạt ảnh cho một tập hợp các kiểu; khi ngắt kết nối, tạo hiệu ứng cho cùng một bộ kiểu (hoặc một bộ khác). Làm xong. Nó cũng phải có hiệu suất cao trên nhiều nền tảng.

Tôi đã đụng phải một bức tường gạch ở đây. Nếu tôi thiếu điều gì đó và có một cách dễ dàng để thực hiện việc này, hãy cho tôi biết.


Chúng ta đang nói đến loại hoạt hình nào ở đây?
Pranesh Ravi

Chỉ cần một cái gì đó đơn giản, giống như một phai CSS opacity trong và mộttransform: scale
ffxsam

Điểm 1 và 2 làm tôi bối rối. Bạn đang sử dụng loại hình động nào? Chuyển tiếp JS hay chuyển tiếp CSS?
Pranesh Ravi

1
Làm phong cách CSS không nhầm lẫn / classes (ví dụ .thing { color: #fff; }) với phong cách JS ( const styles = { thing: { color: '#fff' } }))
ffxsam

Nhưng vấn đề là, khi bạn cố gắng thay đổi kiểu bằng javascript, bạn thực sự đang thay thế kiểu của một phần tử sẽ không mang lại bất kỳ chuyển đổi nào.
Pranesh Ravi

Câu trả lời:


102

Điều này hơi dài dòng nhưng tôi đã sử dụng tất cả các sự kiện và phương pháp gốc để đạt được hoạt ảnh này. Không ReactCSSTransitionGroup, ReactTransitionGroupvà v.v.

Những thứ tôi đã sử dụng

  • Các phương thức vòng đời phản ứng
  • onTransitionEnd biến cố

Cách thức hoạt động

  • Gắn phần tử dựa trên mount prop được truyền ( mounted) và với kiểu mặc định ( opacity: 0)
  • Sau khi gắn kết hoặc cập nhật, sử dụng componentDidMount( componentWillReceivePropsđể cập nhật thêm) để thay đổi kiểu ( opacity: 1) với thời gian chờ (để làm cho nó không đồng bộ).
  • Trong khi ngắt kết nối, hãy chuyển một giá đỡ cho thành phần để xác định việc ngắt kết nối, thay đổi lại kiểu ( opacity: 0) onTransitionEnd, xóa bỏ kết nối phần tử khỏi DOM.

Tiếp tục chu trình.

Xem qua mã, bạn sẽ hiểu. Nếu cần làm rõ thêm, vui lòng để lại bình luận.

Hi vọng điêu nay co ich.

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>


Cảm ơn vì điều đó! Bạn đã học về ở onTransitionEndđâu? Tôi không thấy nó trong tài liệu React.
ffxsam

@ffxsam facebook.github.io/react/docs/events.html Nó đang trong các sự kiện chuyển đổi.
Pranesh Ravi

1
Làm thế nào bạn biết những gì nó đã làm mặc dù, tài liệu không giải thích bất cứ điều gì. Một câu hỏi khác: làm thế nào bạn biết componentWillReceivePropscó thể trả lại một cái gì đó? Tôi có thể đọc thêm về điều đó ở đâu?
ffxsam

1
@ffxsam onTransitionEnd là một sự kiện JavaScript gốc. Bạn có thể google về nó. facebook.github.io/react/docs/… sẽ cung cấp cho bạn một ý tưởng về componentWillReceiveProps.
Pranesh Ravi

7
BTW Tôi nghĩ rằng có một sai lầm trong mã của bạn. Trong bạn Parentthành phần, bạn tham khảothis.transitionEnd
ffxsam

14

Sử dụng kiến ​​thức thu được từ câu trả lời của Pranesh, tôi đã đưa ra một giải pháp thay thế có thể cấu hình và tái sử dụng:

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

Sử dụng:

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

Và cuối cùng, trong renderphương thức của một thành phần khác :

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

1
Và làm cách nào để bạn gắn / ngắt kết nối @ffxsam?

Làm thế nào componentWillLeave()componentWillEnter()được gọi vào AnimatedMount?
Rokit

Không hoạt động với tôi, đây là hộp cát của tôi: Codeandbox.io/s/p9m5625v6m
Webwoman

11

Đây là giải pháp của tôi bằng cách sử dụng API hooks mới (với TypeScript), dựa trên bài đăng này , để trì hoãn giai đoạn ngắt kết nối của thành phần:

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

Sử dụng:

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

Liên kết CodeSandbox .


1
giải pháp thanh lịch, sẽ thật tuyệt nếu bạn thêm một số nhận xét :)
Webwoman

cũng tại sao lại sử dụng tiện ích mở rộng của stylescrypt vì nó hoạt động tốt trong tiện ích mở rộng của javascript?
Webwoman

bảng điều khiển của bạn cũng trả về "không thể tìm thấy không gian tên NodeJS hết thời gian chờ"
Webwoman

1
@Webwoman Cảm ơn ý kiến ​​của bạn. Tôi không thể tạo lại sự cố được báo cáo của bạn với "NodeJS timeout", hãy xem liên kết CodeSandbox của tôi bên dưới câu trả lời. Về TypeScript, cá nhân tôi thích sử dụng nó hơn JavaScript, mặc dù tất nhiên cả hai đều khả thi.
deckele

9

Tôi đã phản bác lại vấn đề này trong quá trình làm việc của mình và đơn giản như có vẻ như, nó thực sự không có trong React. Trong một kịch bản bình thường, nơi bạn hiển thị một cái gì đó như:

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

khi các this.state.showthay đổi con được gắn / ngắt kết nối ngay lập tức.

Một cách tiếp cận mà tôi đã thực hiện là tạo một thành phần trình bao bọc Animatevà sử dụng nó như

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

bây giờ khi this.state.showthay đổi, chúng ta có thể nhận biết các thay đổi về mặt bằng getDerivedStateFromProps(componentWillReceiveProps)và tạo các giai đoạn kết xuất trung gian để thực hiện các hoạt ảnh.

Một chu kỳ giai đoạn có thể trông như thế này

Chúng tôi bắt đầu với Giai đoạn tĩnh khi các con được gắn hoặc tháo lắp.

Khi chúng tôi phát hiện showcờ thay đổi, chúng tôi bước vào Giai đoạn Chuẩn bị , nơi chúng tôi tính toán các thuộc tính cần thiết như heightwidthtừ đó ReactDOM.findDOMNode.getBoundingClientRect().

Sau đó, vào Animate State, chúng ta có thể sử dụng chuyển tiếp css để thay đổi chiều cao, chiều rộng và độ mờ từ 0 thành các giá trị được tính toán (hoặc thành 0 nếu ngắt kết nối).

Khi kết thúc quá trình chuyển đổi, chúng tôi sử dụng onTransitionEndapi để thay đổi trở lại Staticgiai đoạn.

Có nhiều chi tiết hơn về cách các giai đoạn chuyển giao suôn sẻ nhưng đây có thể là ý tưởng tổng thể :)

Nếu có ai quan tâm, tôi đã tạo một thư viện React https://github.com/MingruiZhang/react-animate-mount để chia sẻ giải pháp của mình. Mọi phản hồi đều được chào đón :)


Cảm ơn bạn đã phản hồi, xin lỗi vì câu trả lời thô thiển trước đó. Tôi đã thêm chi tiết và sơ đồ vào câu trả lời của mình, hy vọng điều này có thể hữu ích hơn cho những người khác.
Mingrui Zhang,

1
@MingruiZhang Thật vui khi thấy bạn đã tích cực nhận xét và cải thiện câu trả lời của mình. Thật là sảng khoái khi xem. Làm tốt lắm.
Lỗi vào

6

Tôi nghĩ rằng sử dụng Transitionfrom react-transition-groupcó lẽ là cách dễ nhất để theo dõi việc gắn / tháo lắp. Nó cực kỳ linh hoạt. Tôi đang sử dụng một số lớp để cho thấy nó dễ sử dụng như thế nào nhưng bạn chắc chắn có thể kết nối các hoạt ảnh JS của riêng mình bằng cách sử dụng addEndListenerprop - điều mà tôi cũng đã rất may mắn khi sử dụng GSAP.

Hộp cát: https://codesandbox.io/s/k9xl9mkx2o

Và đây là mã của tôi.

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
Nếu bạn sử dụng các thành phần được tạo kiểu, bạn có thể chỉ cần chuyển showprop tới H1và thực hiện tất cả logic bên trong thành phần được tạo kiểu. Thích ...animation: ${({ show }) => show ? entranceKeyframes : exitKeyframes} 300ms ease-out forwards;
Aleks

2

Chuyển động Framer

Cài đặt framer-motion từ 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

Đối với những người xem xét chuyển động phản ứng, việc tạo hoạt ảnh cho một thành phần duy nhất khi nó gắn kết và ngắt kết nối có thể quá sức để thiết lập.

Có một thư viện tên là react-motion-ui-pack giúp quá trình này bắt đầu dễ dàng hơn rất nhiều. Đó là một trình bao bọc xung quanh chuyển động phản ứng, có nghĩa là bạn nhận được tất cả các lợi ích từ thư viện (tức là bạn có thể làm gián đoạn hoạt ảnh, có nhiều lần ngắt kết nối xảy ra cùng một lúc).

Sử dụng:

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 xác định trạng thái kết thúc của thành phần phải là gì; rời là kiểu được áp dụng khi thành phần không được gắn kết.

Bạn có thể thấy rằng một khi bạn đã sử dụng gói giao diện người dùng một vài lần, thư viện chuyển động phản ứng có thể không còn khó khăn nữa.


Dự án không còn được duy trì (2018)
Micros


1

Điều này có thể được thực hiện dễ dàng bằng cách sử dụng CSSTransitionthành phần từ react-transition-groupđó, giống như các thư viện bạn đã đề cập. Mẹo là bạn cần bao bọc thành phần CSSTransition mà không có cơ chế hiển thị / ẩn như bạn thường làm .ie {show && <Child>}...Nếu không, bạn đang ẩn hoạt ảnh và nó sẽ không hoạt động. Thí dụ:

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

Đây là 2 xu của tôi: cảm ơn @deckele vì giải pháp của anh ấy. Giải pháp của tôi dựa trên của anh ấy, đó là phiên bản thành phần của stateful, hoàn toàn có thể tái sử dụng.

đây là hộp cát của tôi: https://codesandbox.io/s/302mkm1m .

đây là đoạn mã của tôi.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

Đây là cách tôi giải quyết vấn đề này vào năm 2019, trong khi tạo một vòng quay tải. Tôi đang sử dụng các thành phần chức năng của React.

Tôi có một thành phần Ứng dụng mẹ có một thành phần Spinner con .

Ứng dụng có trạng thái cho biết ứng dụng đang tải hay không. Khi ứng dụng đang tải, Spinner được hiển thị bình thường. Khi ứng dụng không tải ( isLoadinglà sai) Spinner được hiển thị với giá đỡ 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 có trạng thái cho dù nó bị ẩn hay không. Lúc đầu, với các đạo cụ và trạng thái mặc định, Spinner được hiển thị bình thường. Các Spinner-fadeInlớp học sinh động nó mờ dần. Khi Spinner nhận prop shouldUnmountnó ám với Spinner-fadeOutlớp thay vào đó, hiệu ứng động nó mờ dần ra.

Tuy nhiên, tôi cũng muốn thành phần ngắt kết nối sau khi mờ dần.

Tại thời điểm này, tôi đã thử sử dụng onAnimationEndsự kiện tổng hợp React, tương tự như giải pháp của @ pranesh-ravi ở trên, nhưng nó không hoạt động. Thay vào đó, tôi đã sử dụng setTimeoutđể đặt trạng thái thành ẩn với độ trễ bằng độ dài của hoạt ảnh. Spinner sẽ cập nhật sau độ trễ với isHidden === truevà không có gì được hiển thị.

Chìa khóa ở đây là cha mẹ không ngắt kết nối đứa trẻ, nó nói với đứa trẻ khi nào cần ngắt kết nối và đứa trẻ sẽ tự ngắt kết nối sau khi nó xử lý công việc tháo lắp của nó.

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

Tôi cũng rất cần Animation một thành phần. Tôi đã mệt mỏi khi sử dụng React Motion nhưng tôi đang vò đầu bứt tóc vì một vấn đề tầm thường như vậy .. (tôi nói vậy). Sau một số googling, tôi đã xem bài đăng này trên git repo của họ. Hy vọng nó sẽ giúp ai đó ..

Được giới thiệu từ & cả tín dụng . Điều này làm việc cho tôi cho đến bây giờ. Trường hợp sử dụng của tôi là một phương thức để tạo hiệu ứng và ngắt kết nối trong trường hợp tải và dỡ.

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

Tôi biết có rất nhiều câu trả lời ở đây, nhưng tôi vẫn không tìm thấy câu trả lời phù hợp với nhu cầu của mình. Tôi muốn:

  • Các thành phần chức năng
  • Một giải pháp sẽ cho phép các thành phần của tôi dễ dàng mờ dần vào / ra khi chúng được gắn / tháo lắp.

Sau nhiều giờ mày mò, tôi có một giải pháp hiệu quả mà tôi có thể nói là 90%. Tôi đã viết giới hạn trong một khối bình luận trong đoạn mã dưới đây. Tôi vẫn thích một giải pháp tốt hơn, nhưng đây là giải pháp tốt nhất mà tôi tìm thấy, bao gồm cả các giải pháp khác ở đây.

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

Nếu bạn có một thành phần bạn muốn làm mờ dần trong / ngoài, hãy bọc nó trong <Fade>Ex. <Fade><MyComponent/><Fade>.

Lưu ý rằng điều này sử dụng react-bootstrapcho tên lớp và cho <Container/>, nhưng cả hai đều có thể dễ dàng thay thế bằng CSS tùy chỉnh và một cái cũ thông thường <div>.


0

Nếu tôi sử dụng Velocityhoặc AnimeJSthư viện để tạo hoạt ảnh trực tiếp cho nút (thay vì csshoặc setTimeout), thì tôi phát hiện ra mình có thể thiết kế một hookđể cung cấp trạng thái hoạt ảnh onvà chức năng onToggleđể bắt đầu hoạt ảnh (ví dụ: trượt xuống, mờ dần).

Về cơ bản những gì hook làm là bật và tắt hoạt ảnh và sau đó cập nhật hình ảnh ontương ứng. Do đó chúng tôi có thể nhận được chính xác trạng thái của hoạt ảnh. Nếu không làm như vậy sẽ trả lời một cách đặc biệt 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

Cách sử dụng như sau,

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

và việc triển khai hoạt ảnh có thể là

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.