原生js实现拖拽上传(拖拽时高亮上传区域)

209 阅读2分钟

drop相关事件说明-MDN

演示

录屏2024-10-29 13.47.45.gif

代码(.html)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Drag Upload</title>
  <style>
    html,
    body {
      height: 100%;
      width: 100%;
    }

    .drag-upload-box {
      width: 200px;
      height: 130px;
      border-radius: 6px;
      border: 2px dashed #ccc;
      overflow: hidden;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
    }

    .drag-upload-box.guide {
      border-color: orange;
    }

    .drag-upload-box.available {
      border-color: green;
    }

    .drag-upload-box > * {
      /* 防止拖拽到 子元素导致触发 dragleave*/
      pointer-events: none;
    }

    .drag-upload-box .empty {
      color: #ccc;
    }

    .drag-upload-box .file-name:empty + .empty {
      display: unset;
    }

    .drag-upload-box .file-name + .empty {
      display: none;
    }

    .input-file {
      display: none;
    }

    .obstacle {
      width: 100%;
      height: 200px;
      background: #ccc;
    }
  </style>
</head>

<body>
  <div id="root">
    <div id="upload" class="drag-upload-box">
      <input id="file" class="input-file" type="file">
      <span class="file-name"></span>
      <span class="tips empty">点击/拖拽到此处上传文件</span>
    </div>
    <div class="obstacle no1">
      其他元素
    </div>
  </div>

</body>
<script>
  const $ = selector => document.querySelector(selector)
  const addEvent = (element, event, handler) => element.addEventListener(event, handler)

  // 文件上传input
  const fileInput = $('#file')
  // 拖放区域
  const dragBox = $('#upload')
  const fileName = dragBox.querySelector('.file-name')
  const tips = dragBox.querySelector('.tips')

  // 两个标识;来判断当前鼠标是否在上传文件/body中。
  let inUploadBox = false
  let inBody = false

  // 绑定拖拽事件
  addEvent(dragBox, 'dragenter', handlerEvents)
  addEvent(dragBox, 'dragover', handlerEvents)
  addEvent(dragBox, 'dragleave', handlerEvents)
  addEvent(dragBox, 'drop', handlerEvents)
  // 点击上传
  addEvent(dragBox, 'click', (e) => {
    fileInput.click()
  })
  // 选择文件回调
  addEvent(fileInput, 'change', () => {
    getFile(fileInput.files[0])
  })

  const light = {
    guide() {
      this.classList.add('guide')
      this.classList.remove('available')
      tips.innerText = '拖拽到此处'
      inUploadBox = false
    },
    available() {
      this.classList.add('available')
      this.classList.remove('guide')
      tips.innerText = '松开鼠标'
      inUploadBox = true
    },
    clearLight() {
      this.removeClass('guide', 'available')
      tips.innerText = '点击/拖拽到此处上传文件'
      inUploadBox = false
    },
    removeClass() {
      this.classList.remove(...arguments)
      return this
    }
  }
  Object.assign(dragBox, light)

  function handlerEvents(e) {
    // 阻止事件传播触发 body 的dragover
    e.stopPropagation()
    e.preventDefault()
    // 禁用dragover默认事件,否则会在浏览器中打开文件
    switch (e.type) {
      // 进入拖放区域
      case 'dragenter':
        dragBox.available()
        break

      // 离开拖放区域
      case 'dragleave':
        if (inBody) {
          dragBox.guide()
        } else {
          dragBox.clearLight()
        }
        break

      // 在拖拽区域内松开鼠标(拖放完成/放入文件)
      case 'drop':
        getFile(e.dataTransfer.files[0])
        dragBox.clearLight()
        inBody = false
        break
      default:
        break
    }
  }

  // 获取到文件
  function getFile(file) {
    console.log(file)
    if (!file) {
      fileName.innerText = ''
      return
    }
    const fileSize = (file.size / (1024 * 1024)).toFixed(2) + 'MB'
    fileName.innerText = file.name + ' ' + fileSize
    // TODO: 上传、预览
  }

  /* 以上是正常拖拽上传部分,下面代码处理拖拽到body后的指引/处理 */
  const body = document.body
  addEvent(body, 'dragenter', handlerOutEvents)
  addEvent(body, 'dragover', handlerOutEvents)
  addEvent(body, 'dragleave', handlerOutEvents)
  addEvent(body, 'drop', handlerOutEvents)

  function handlerOutEvents(e) {
    e.stopPropagation()
    e.preventDefault()

    switch (e.type) {
      case 'dragenter':
        // 拖拽到 body && 未到上传文件区域;高亮指引到上传区域
        dragBox.guide()
        inBody = true
        break
      case 'dragover':
        break
      case 'dragleave':
        if (inUploadBox) {
          dragBox.removeClass('guide')
        }
        break
      case 'drop':
        dragBox.clearLight()
        inBody = false
        break
      default:
        break
    }
  }
</script>

</html>