React-显示和隐藏幻灯片动画

时间:2020-06-04 20:00:01

标签: javascript reactjs

我已经从事React项目已有一段时间了。我有一个上面有元素的板子。这些元素包含图像。我在这里有它的工作版本:https://geoboard.luukwuijster.dev/。这个版本只是使用普通的JavaScript和jQuery,但是它确实说明了我想在新的React版本中实现的目标。当我上传图片(通过使用CTRL + V粘贴图片)时,我希望它具有动画效果。它应该滑入然后滑出。在旧版本中,我仅使用jQuery的slideIn和slideOut,但现在我想知道如何在React中实现这一点。

我已经尝试了以下操作:

  • CSSTransition。我尝试通过将元素的高度设置为零并在高度上进行过渡来使用CSSTransition。这是行不通的,因为元素的高度是可变的,因此它应该是自动的。过渡不适用于高度:自动。
  • Animate.CSS我尝试过在元素上放置slideInDown,但是由于Animcate.CSS使用的是Transform,因此无法正常工作。元素只是在上方弹出几个像素并向下滑动。
  • jQuery的slideIn和slideOut。这在某种程度上是可行的,但还是会出错。我使用ref来获取元素,但是在向数组中添加新元素时,它会为最后一个元素(而非第一个)设置动画。设置动画时也会出现一点故障。

这是我的代码:

Board.tsx

import React from 'react';
import { Config } from 'util/config';
import { container } from 'tsyringe';
import { AppState } from 'store';
import { connect } from 'react-redux';

import { BoardState } from 'store/board/types';
import { BoardHubService } from 'services/hubs/boardHub.service';
import { BoardElementViewModel } from 'models/BoardElementViewModel';
import { BoardViewModel } from 'models/BoardViewModel';
import { BoardElement } from './boardElement/boardElement';
import { HttpService } from 'services/http.service';
import { setActiveBoard } from 'store/board/actions';

import './board.scss'
import { mapToType } from 'helpers/helpers';

interface BoardProps {
    activeBoardState: BoardState;
    setActiveBoard: typeof setActiveBoard;
}

interface LocalBoardState {
    boardElements: Array<BoardElementViewModel>
}

class Board extends React.Component<BoardProps, LocalBoardState> {

    private config: Config;

    private httpService: HttpService;
    private boardHubService: BoardHubService;

    constructor(props: any) {
        super(props);

        this.config = container.resolve(Config);
        this.boardHubService = container.resolve(BoardHubService);

        this.httpService = container.resolve(HttpService);

        this.state = {
            boardElements: []
        }
    }

    async componentDidMount() {
        // If there was any active board on page load...
        if (this.props.activeBoardState.boardId) {

            await this.loadBoardElements();
        }

        this.boardHubService.getConnection().on('SwitchedBoard', (response: BoardViewModel) => {
            this.setState({
                boardElements: response.elements
            });

            console.log('SwitchedBoard', this.props.activeBoardState);

            this.updateSiteTitle();
        });

        this.boardHubService.getConnection().on('ReceiveElement', (response: BoardElementViewModel) => {

            let elements = this.state.boardElements;
            elements.unshift(response);

            this.setState(() => ({
                boardElements: elements
            }))
        });

        this.boardHubService.getConnection().on('RemoveElement', (response: string) => {

            let elements = this.state.boardElements;
            let element = mapToType<BoardElementViewModel>(elements.find(x => x.id === response));
            elements.splice(elements.indexOf(element), 1);

            this.setState(() => ({
                boardElements: elements
            }))
        });
    }

    /**
     * Load the elements from the board that was already active on page load.
     */
    private async loadBoardElements() {
        await this.httpService.getWithAuthorization<Array<BoardElementViewModel>>(`/boards/${this.props.activeBoardState.boardId}/elements`)
            .then((response: Array<BoardElementViewModel>) => {
                this.setState({
                    boardElements: response
                });
            })
            .catch((e) => console.warn(e));
    }

    private updateSiteTitle() {
        if (this.props.activeBoardState.boardId != null) {
            document.title = `${this.props.activeBoardState.name} | ${this.config.siteName}`;
        }
        else {
            document.title = this.config.siteName;
        }
    }

    render() {
        return (
            <>
                {this.props.activeBoardState.boardId != null
                    ?
                    <div className="board-elements">

                        {this.state.boardElements.map((element: BoardElementViewModel, index) => {
                            return (
                                <BoardElement
                                    key={index}
                                    id={element.id}
                                    // TODO: Use number from server
                                    number={element.elementNumber}
                                    user={element.user}
                                    direction={element.direction}
                                    note={element.note}
                                    imageId={element.imageId}
                                    createdAt={element.createdAt}
                                />
                            )
                        })}
                    </div>

                    :
                    <div className="select-board-instruction">
                        <h1>Please select or create a board.</h1>
                    </div>
                }
            </>
        )
    }

}

const mapStateToProps = (state: AppState) => ({
    activeBoardState: state.activeBoard
});

export default connect(mapStateToProps, { setActiveBoard })(Board);

BoardElement.tsx

import React from 'react';
import { UserViewModel } from 'models/UserViewModel';
import { Direction } from 'models/Direction';

import './boardElement.scss';
import { dateToReadableString } from 'helpers/helpers';
import { Config } from 'util/config';
import { container } from 'tsyringe';
import { HttpService } from 'services/http.service';
import { CSSTransition, Transition } from 'react-transition-group';

import $ from 'jquery'

interface BoardElementProps {
    id: string;
    number: number;
    user: UserViewModel;
    // TODO: Use direction Enum
    direction?: Direction;
    note?: string;
    imageId?: string;
    createdAt: Date;
}

export class BoardElement extends React.Component<BoardElementProps> {

    private config: Config;
    private httpService: HttpService;
    private ref: any;

    constructor(props: BoardElementProps) {
        super(props);

        this.state = {
            show: false,
            height: 0
        }

        this.config = container.resolve(Config);
        this.httpService = container.resolve(HttpService);
    }

    getReadableDirection(direction: Direction) {
        // TODO: Richtingen vertalen
        switch (direction) {
            case Direction.North: return 'Noord';
            case Direction.NorthEast: return 'Noordoost';
            case Direction.East: return 'Oost';
            case Direction.SouthEast: return 'Zuidoost';
            case Direction.South: return 'Zuid';
            case Direction.SouthWest: return 'Zuidwest';
            case Direction.West: return 'West';
            case Direction.NorthWest: return 'Noordwest';
        }
    }

    removeElement() {
        $(this.ref).slideUp(200);

        setTimeout(() => {
            this.httpService.deleteWithAuthorization(`/boards/elements/${this.props.id}`).then(() => {

            }, (error) => {
                console.warn(error);
            });
        }, 250);
    }

    componentDidMount() {
        setTimeout(() => {
            $(this.ref).slideDown(200);
        }, 500);
    }

    render() {

        let style: any = { display: "none" };

        return (

            <div ref={element => this.ref = element} style={style}>
                <div className="board-element" data-element-id={this.props.id}>
                    <div className="board-element-header">
                        <span className="board-element-number">{this.props.number}</span>
                        <span className="board-element-creator">{this.props.user.username}</span>
                        <i className="fas fa-trash ml-auto delete-icon" onClick={() => this.removeElement()}></i>
                    </div>
                    <div className="board-element-body">
                        {this.props.imageId
                            ? <img className="board-element-image" src={`${this.config.apiUrl}/content/${this.props.imageId}`} />
                            : <p className="board-element-message">{this.props.note}</p>
                        }
                    </div>
                    <div className="board-element-footer">

                        {this.props.direction &&
                            <div className="board-element-direction">
                                <i className="fas fa-location-arrow direction mr-2"></i>{this.getReadableDirection(this.props.direction)}
                            </div>
                        }

                        <time className="board-element-timestamp" dateTime={this.props.createdAt.toString()}>{dateToReadableString(this.props.createdAt)}</time>
                    </div>
                </div>
            </div>

        )
    }
}

更新:

https://gyazo.com/6dfb3db8cbd29da0f4f52af33c545421

此剪辑可能会更好地说明我的问题。仔细看一下元素角上的数字。当我粘贴新图像时,动画不会应用于新图像,而是应用于数组中的最后一张图像。

0 个答案:

没有答案