React 实现拖拽功能

实现效果:(可戳 https://codepen.io/wenr/pen/EGEQxp 查看)

因为工作中会用到 JIRA 所以想实现一下相似的功能,顺便学习一下 H5 的拖拽。不支持拖拽改变顺序,感觉有点麻烦,而且没必要。感觉相关的博文好少的,大部分都是直接上代码,没有解释。

 

图片默认可以拖动,其他元素的拖动效果同图片。正常的 div 是不能被拖动的,鼠标点击选择后移动没有效果,需要加  draggable="true" 使得元素可以被拖动。

 

拖拽相关的几个事件,有被拖动元素的事件,也有拖动进入的容器元素的事件。

 

被拖拽元素的事件:ondragstart,ondragend 

放置元素的事件:ondragenter、ondragover、ondragleave、ondrop 

顾名思义,不需要解释。

需要注意是  ondragover 的默认事件 Reset the current drag operation to "none". 所以想让一个元素可放置,需要重写 ondragover 

element.ondragover = event => { 
    event.preventDefault();
    // ...
}

当一个元素是可放置的,拖拽经过时鼠标会变成加号(cursor: copy;)

 

有一个对象  dataTransfer 可以用来存储拖拽数据。

dragEle.ondragstart = e => e.dataTransfer.setData('item', e.target.id);

拖拽开始时触发,把被拖拽元素的 id 存入  e.dataTransfer 

然后在 ondrop 的时候 可以获取到这个值 (ondragenter、ondragover、ondragleave 获取不到...)

putEle.ondrop = function(e) {
     let id = e.dataTransfer.getData('item');
     // ...
}

 

简单的应用:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
    .wrapper {display: flex;border: 1px solid orangered;padding: 10px;}
    .col {border: 1px solid #808080;height: 500px;width: 200px;margin: 0 10px;padding: 10px;}
    .item {border: 1px solid #808080;margin: 5px 0;}
    </style>
</head>
<body>
    <div class="wrapper">
        <div class="col1 col">
            <div class="item" id="item1" draggable="true">item1</div>
            <div class="item" id="item2" draggable="true">item2</div>
            <div class="item" id="item3" draggable="true">item3</div>
        </div>
        <div class="col2 col"></div>
        <div class="col3 col"></div>
        <div class="col4 col"></div>
    </div>
    <script>
        let cols = document.getElementsByClassName('col');
        for (let col of cols) {
            col.ondragenter = e => { 
                console.log('放置元素 ondragenter', '<' + e.dataTransfer.getData('item') + '>'); 
            }
            col.ondragover = e => {
                e.preventDefault();
                console.log('放置元素 ondragover', '<' + e.dataTransfer.getData('item') + '>');
            }
            col.ondragleave = e => { 
                console.log('放置元素 ondragleave', '<' + e.dataTransfer.getData('item') + '>'); 
            }
            col.ondrop = function(e) {
                console.log('放置元素 ondrop', '<' + e.dataTransfer.getData('item') + '>');
                this.append(document.getElementById(e.dataTransfer.getData('item')));
            }
        }
        let items = document.getElementsByClassName('item');
        for (let item of items) {
            item.ondragstart = e => {
                console.log('拖拽元素 ondragstart');
                e.dataTransfer.setData('item', e.target.id);
            }
            item.ondragend = e => {
              console.log('拖拽元素 ondragend');
            }
        }
    </script>
</body>
</html>

 

 

文章开头部分的 React 写的 demo

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
        <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
        <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
        <style>
            .item {
                border: 1px solid #1da921;
                width: 180px;
                border-radius: 5px;
                box-shadow: 0 0 5px 0 #b3b3b3;
                margin: 5px auto;
                background: #fff;
            }
            .item.active {
                border-style: dashed;
            }
            .item-header {
                font-size: 12px;
                color: #9e9e9e;
                padding: 3px 5px;
            }
            .item-main {
                padding: 5px;
                font-size: 14px;
                color: #424242;
                height: 36px;
                overflow: hidden;
                text-overflow: ellipsis;
                display: -webkit-box;
                -webkit-box-orient: vertical;
                -webkit-line-clamp: 2;
            }
            .item-header-point {
                background: #ccc;
                float: right;
                padding: 0 4px;
                min-width: 10px;
                text-align: center;
                color: #fff;
                border-radius: 50%;
            }
            .col {
                border: 1px solid #d2d2d2;
                flex-grow: 1;
                width: 180px;
                height: 100%;
                margin: 0 2px;
                background: #eee;
                flex-grow: 1;
                display: flex;
                flex-direction: column;
            }
            .col-header {
                height: 40px;
                line-height: 40px;
                background: #1DA921;
                color: #fff;
                text-align: center;
            }
            .col-main {
                overflow: auto;
                flex-grow: 1;
            }
            .col-main.active {
                background: #00ad23;
                opacity: 0.1;
            }
            .task-wrapper {
                display: flex;
                height: 400px;
                width: 700px;
            }
        </style>
    </head>
    <body>
        <div id="app"></div>
        <script type="text/babel">
            const STATUS_TODO = 'STATUS_TODO';
            const STATUS_DOING = 'STATUS_DOING';
            const STATUS_DONE = 'STATUS_DONE';
            
            const STATUS_CODE = {
                STATUS_TODO: '待处理',
                STATUS_DOING: '进行中',
                STATUS_DONE: '已完成'
            }
            let tasks = [{
                id: 0,
                status: STATUS_TODO,
                title: '每周七天阅读五次,每次阅读完要做100字的读书笔记',
                username: '小夏',
                point: 10
            }, {
                id: 1,
                status: STATUS_TODO,
                title: '每周七天健身4次,每次健身时间需要大于20分钟',
                username: '橘子🍊',
                point: 5
            }, {
                id: 2,
                status: STATUS_TODO,
                title: '单词*100',
                username: '┑( ̄Д  ̄)┍',
                point: 2
            }, {
                id: 3,
                status: STATUS_TODO,
                title: '单词*150',
                username: '┑( ̄Д  ̄)┍',
                point: 2
            }, {
                id: 4,
                status: STATUS_TODO,
                title: '单词*200',
                username: '┑( ̄Д  ̄)┍',
                point: 2
            }, {
                id: 5,
                status: STATUS_TODO,
                title: '单词*250',
                username: '┑( ̄Д  ̄)┍',
                point: 2
            }]
            
            class TaskItem extends React.Component {
                handleDragStart = (e) => {
                    this.props.onDragStart(this.props.id);
                }
                render() {
                    let { id, title, point, username, active, onDragEnd } = this.props;
                    return (
                        <div 
                            onDragStart={this.handleDragStart}
                            onDragEnd={onDragEnd}
                            id={`item-${id}`} 
                            className={'item' + (active ? ' active' : '')}
                            draggable="true"
                        >
                            <header className="item-header">
                                <span className="item-header-username">{username}</span>
                                <span className="item-header-point">{point}</span>
                            </header>
                            <main className="item-main">{title}</main>
                        </div>
                    );
                }
            }
            
            class TaskCol extends React.Component {
                state = {
                    in: false
                }
                handleDragEnter = (e) => {
                    e.preventDefault();
                    if (this.props.canDragIn) {
                        this.setState({
                            in: true
                        })
                    }
                }
                handleDragLeave = (e) => {
                    e.preventDefault();
                    if (this.props.canDragIn) {
                        this.setState({
                            in: false
                        })
                    }
                }
                handleDrop = (e) => {
                    e.preventDefault();
                    this.props.dragTo(this.props.status);
                    this.setState({
                        in: false
                    })
                }
                render() {
                    let { status, children } = this.props;
                    return (
                        <div 
                            id={`col-${status}`} 
                            className={'col'}
                            onDragEnter={this.handleDragEnter}
                            onDragLeave={this.handleDragLeave}
                            onDragOver={this.handleDragEnter}
                            onDrop={this.handleDrop}
                            draggable="true"
                        >
                            <header className="col-header">
                                {STATUS_CODE[status]}
                            </header>
                            <main className={'col-main' + (this.state.in ? ' active' : '')}>
                                {children}
                            </main>
                        </div>
                    );
                }
            }
            
            class App extends React.Component {
                state = {
                    tasks: tasks,
                    activeId: null
                }
                /**
                 * 传入被拖拽任务项的 id
                 */
                onDragStart = (id) => {
                    this.setState({
                        activeId: id
                    })
                }
                
                dragTo = (status) => {
                    let { tasks,  activeId} = this.state;
                    let task = tasks[activeId];
                    if (task.status !== status) {
                        task.status = status;
                        this.setState({
                            tasks: tasks
                        })
                    }
                    this.cancelSelect();
                }
                
                cancelSelect = () => {
                    this.setState({
                        activeId: null
                    })
                }
                
                render() {
                    let { tasks, activeId } = this.state;
                    let { onDragStart, onDragEnd, cancelSelect } = this;
                    return (
                        <div className="task-wrapper">
                            {
                                Object.keys(STATUS_CODE).map(status => 
                                    <TaskCol 
                                        status={status} 
                                        key={status} 
                                        dragTo={this.dragTo}
                                        canDragIn={activeId != null && tasks[activeId].status !== status}>
                                        { tasks.filter(t => t.status === status).map(t => 
                                            <TaskItem
                                                key={t.id}
                                                active={t.id === activeId}
                                                id={t.id}
                                                title={t.title} 
                                                point={t.point} 
                                                username={t.username}
                                                onDragStart={onDragStart}
                                                onDragEnd={cancelSelect}
                                            />)
                                        }
                                    </TaskCol>
                                )
                            }
                        </div>
                    )
                }
            }
            
            ReactDOM.render(
                <App />,
                document.getElementById('app')
            );
        </script>
    </body>
</html>

 

over..

posted @ 2019-01-05 18:06  我不吃饼干呀  阅读(37137)  评论(4编辑  收藏  举报