×

React开发小应用:实现BOX拖拽功能

作者:andy0012021.06.10来源:Web前端之家浏览:5537评论:1
关键词:react

分享React开发小应用:实现BOX拖拽功能。

因为工作中会用到 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>
<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>React开发小应用:实现BOX拖拽功能 - Web前端之家www.jiangweishan.com</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>
    <div class="col1 col">
      <div id="item1" draggable="true">item1</div>
      <div id="item2" draggable="true">item2</div>
      <div 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>React开发小应用:实现BOX拖拽功能 - Web前端之家www.jiangweishan.com</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: 'Web前端之家-首页 www.jiangweishan.com',
        username: '小夏',
        point: 10
      }, {
        id: 1,
        status: STATUS_TODO,
        title: 'Web前端之家-教程 www.jiangweishan.com',
        username: '橘子',
        point: 5
      }, {
        id: 2,
        status: STATUS_TODO,
        title: 'Web前端之家-vue开发 www.jiangweishan.com',
        username: '┑( ̄Д  ̄)┍',
        point: 2
      }, {
        id: 3,
        status: STATUS_TODO,
        title: 'Web前端之家-react开发 www.jiangweishan.com',
        username: '┑( ̄Д  ̄)┍',
        point: 2
      }, {
        id: 4,
        status: STATUS_TODO,
        title: 'Web前端之家-canvas www.jiangweishan.com',
        username: '┑( ̄Д  ̄)┍',
        point: 2
      }, {
        id: 5,
        status: STATUS_TODO,
        title: 'Web前端之家-SEO www.jiangweishan.com',
        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>

您的支持是我们创作的动力!
温馨提示:本文作者系 ,经Web前端之家编辑修改或补充,转载请注明出处和本文链接:
https://jiangweishan.com/article/react20210610a3.html

网友评论文明上网理性发言 已有1人参与

发表评论:

评论列表

  • qianduan qianduan  发布于 2021-06-10 13:55:51   回复该评论
  • 这个跟H5做的很像,看上去用react实现起来要简单些。