9. 元素拖拽

发布于:2025-06-28 ⋅ 阅读:(16) ⋅ 点赞:(0)

元素拖拽

API 介绍

1. 拖放过程

整个拖放过程中,存在两个关键元素:拖拽元素、放置元素 拖拽元素:被拖拽的元素

  • drag:元素被拖拽时触发,从开始拖拽到拖拽结束前整个过程会一直持续的触发
  • dragstart:元素被拖拽开始时触发
  • drop:拖拽元素被放置到放置元素内时触发,如果没有在放置元素内松手,则不会触发

放置元素:

  • dragenter:有拖拽元素进入时触发
  • dragover:有拖拽元素在该元素上时触发,在离开前会持续触发
  • dragleave:拖拽元素离开时触发
  • dragend:拖拽元素放置时触发

在这里插入图片描述

2. 可拖拽元素

在 HTML 中,文本、图片和链接是默认可以拖放的元素。其他元素都是默认不可拖动的,如果需要让其他非默认可拖动的 HTML 元素变得可拖动,比如<div><span>等,你需要明确地为这些元素设置 draggable="true" 属性。这样,这些元素就能够接受拖放操作了。

3. 放置元素

所有 HTML 元素在默认情况下都不接受拖拽元素的放置,除非通过特定的事件处理来允许。

要使一个 HTML 元素能够接受被拖动的元素,需要对这个元素进行一些特定的设置和事件绑定:

  • dragover 事件:当被拖动的元素在另一个元素上方移动时,会触发 dragover 事件。为了接受拖放,必须在 dragover 事件处理器中调用 event.preventDefault()方法,这会阻止浏览器的默认行为,也就是不接受任何被拖放的元素。

4. DataTransfer

DataTransfer 对象用于保存拖动并放下(drag and drop)过程中的数据。它可以保存一项或多项数据,这些数据项可以是一种或者多种数据类型

  • dropEffect:获取当前选定的拖放操作类型,或设置为一个新的类型。值必须为 none、copy、link、move
  • effectAllowed:提供所有可用的操作类型。值必须为 none、copy、copyLink、copyMove、link、copyMove、move、all、uninitialized
  • files:包含数据传输中的所有本地文件列表
  • items(只读):提供一个包含所有拖动数据列表的 DataTransferItemList 对象
  • types(只读):一个提供 dragstart 事件中设置的格式的 strings 数组
<template>
  <div id="drag-content" ref="contentRef" @dragstart="dragstart" @dragover="dragover" @dragenter="dragenter" @drop="drop" @dragend="dragend">
    <div class="left" data-drop="move">
      <div data-effect="copy" draggable="true" style="background: rgb(26, 231, 156)">语文</div>
      <div data-effect="copy" draggable="true" style="background: rgb(107, 219, 15)">数学</div>
      <div data-effect="copy" draggable="true" style="background: rgb(208, 133, 13)">英语</div>
      <div data-effect="copy" draggable="true" style="background: rgb(30, 98, 246)">物理</div>
      <div data-effect="copy" draggable="true" style="background: rgb(210, 40, 113)">化学</div>
      <div data-effect="copy" draggable="true" style="background: rgb(210, 224, 26)">生物</div>
    </div>
    <div class="right">
      <table>
        <thead>
          <tr>
            <td>星期一</td>
            <td>星期二</td>
            <td>星期三</td>
            <td>星期四</td>
            <td>星期五</td>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td data-drop="copy"></td>
            <td data-drop="copy"></td>
            <td data-drop="copy"></td>
            <td data-drop="copy"></td>
            <td data-drop="copy"></td>
          </tr>
          <tr>
            <td data-drop="copy"></td>
            <td data-drop="copy"></td>
            <td data-drop="copy"></td>
            <td data-drop="copy"></td>
            <td data-drop="copy"></td>
          </tr>
          <tr>
            <td data-drop="copy"></td>
            <td data-drop="copy"></td>
            <td data-drop="copy"></td>
            <td data-drop="copy"></td>
            <td data-drop="copy"></td>
          </tr>
          <tr>
            <td data-drop="copy"></td>
            <td data-drop="copy"></td>
            <td data-drop="copy"></td>
            <td data-drop="copy"></td>
            <td data-drop="copy"></td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue'
const contentRef = ref(null)

const state = reactive({
  source: null as HTMLElement | null,
  dropNode: null
})

const dragover = (e) => {
  // 接受拖放元素
  e.preventDefault()
}

const dragstart = (e) => {
  if (e.target.dataset.effect === 'move') {
    e.dataTransfer.effectAllowed = 'move'
  }
  state.source = e.target
  // 设置拖拽元素的样式
  e.target.style.opacity = '0.2'
}

const dragenter = (e) => {
  const dropNode = getDropNode(e.target)
  // 判断放置元素是否可以接收拖拽元素,当 data-effect 和 data-drop 的值相等时,说明可以
  if (dropNode && dropNode.dataset.drop === (state.source as HTMLElement).dataset.effect) {
    // 给放置元素添加样式
    dropNode.classList.add('hover-background')
  }
}

// 获取最近的可接受拖拽元素的父节点
const getDropNode = (node) => {
  while (node) {
    //  判断元素是否设置了data-drop属性,如果设置了,说明此元素是一个放置元素
    if (node.dataset && node.dataset.drop) {
      return node
    }
    node = node.parentNode
  }
  return node
}

const clearHoverClass = () => {
  document.querySelectorAll('.hover-background').forEach((ele) => ele.classList.remove('hover-background'))
}

const drop = (e) => {
  // 清除hover时的样式
  clearHoverClass()
  // 获取最近的放置节点
  const dropNode = getDropNode(e.target)
  if (dropNode && dropNode.dataset.drop === (state.source as HTMLElement).dataset.effect) {
    // 如果是新增课程
    if (dropNode.dataset.drop === 'copy') {
      dropNode.innerHTML = ''
      const cloned = (state.source as HTMLElement).cloneNode(true)
      if (cloned instanceof HTMLElement) {
        cloned.dataset.effect = 'move'
        cloned.style.opacity = '1'
      }
      dropNode.appendChild(cloned)
      // 移除课程
    } else {
      ;(state.source as HTMLElement).remove()
    }
  }
}

const dragend = (e) => {
  e.target.style.opacity = '1'
}
</script>

<style scoped lang="scss">
#drag-content {
  display: flex;
  .left {
    width: 80px;
    line-height: 32px;
    margin-right: 20px;
    div {
      padding: 10px;
      margin-bottom: 10px;
      text-align: center;
      color: white;
    }
  }

  .right {
    flex: 1;
    table {
      margin: 10px;
      td {
        width: 120px;
        height: 65px;
        div {
          padding: 10px;
          text-align: center;
          color: white;
        }
      }
    }
  }

  .hover-background {
    background-color: aqua;
  }
}
</style>

网站公告

今日签到

点亮在社区的每一天
去签到