悟夕导航

HTML5 拖放:不用第三方库,也能让元素“想拖就拖”的教程

54 0 0

核心就三句话:

  1. 给元素加 draggable="true"
  2. dragstart 里把数据塞进“小书包”(dataTransfer)
  3. 在目标区域阻止默认并取书包(drop + preventDefault)
    下面带你 15 分钟完成“卡片排序 + 图片上传”两个实战,代码复制就能跑。

1. 拖放流程 5 步图解(先背再敲)

步骤触发事件做什么
① 开始拖dragstart写数据 + 加视觉反馈
② 经过dragover必须 preventDefault(),否则无法落点
③ 进入dragenter高亮提示“可以松手”
④ 放下drop读数据 + 改 DOM
⑤ 结束dragend清场、去高亮
记忆口诀:“start 写,over 阻,enter 亮,drop 读,end 清”

2. 实战 1:Trello 式卡片排序

<ul id="list">
  <li draggable="true">任务 A</li>
  <li draggable="true">任务 B</li>
  <li draggable="true">任务 C</li>
</ul>
li { cursor: move; user-select: none; }
li.dragging { opacity: .5; }
li.dragover { border-top: 2px solid #007bff; }
const list = document.getElementById('list');
let dragged = null;

/* === 拖拽源事件 === */
list.addEventListener('dragstart', e => {
  if (e.target.tagName !== 'LI') return;
  dragged = e.target;
  e.target.classList.add('dragging');
  e.dataTransfer.effectAllowed = 'move';
  e.dataTransfer.setData('text/html', e.target.innerHTML);
});

list.addEventListener('dragend', e => {
  if (e.target.tagName === 'LI') e.target.classList.remove('dragging');
});

/* === 放置目标事件 === */
list.addEventListener('dragover', e => e.preventDefault()); // 必须阻默认

list.addEventListener('dragenter', e => {
  if (e.target.tagName === 'LI') e.target.classList.add('dragover');
});

list.addEventListener('dragleave', e => {
  if (e.target.tagName === 'LI') e.target.classList.remove('dragover');
});

list.addEventListener('drop', e => {
  if (e.target.tagName === 'LI') {
    e.target.classList.remove('dragover');
    // 用 insertBefore 实现“插前面”排序
    const after = e.target.nextSibling;
    list.insertBefore(dragged, after);
  }
});
逻辑:把被拖元素插到“落点”前面,完成排序;无第三方库,移动端同样可用 。

3. 实战 2:拖拽图片 → 即刻预览 → 上传

<div id="dropBox" style="width:300px;height:200px;border:2px dashed #ccc;">
  把图片拖到这儿
</div>
<ul id="preview"></ul>
const dropBox = document.getElementById('dropBox');

// ① 阻止默认拖放行为,不然浏览器直接打开文件
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(evt => {
  dropBox.addEventListener(evt, e => e.preventDefault(), false);
});

// ② 进入/离开 视觉反馈
dropBox.addEventListener('dragenter', e => dropBox.style.background = '#eee');
dropBox.addEventListener('dragleave', e => dropBox.style.background = '');

// ③ 核心:drop 读取文件
dropBox.addEventListener('drop', e => {
  dropBox.style.background = '';
  const files = e.dataTransfer.files; // FileList 对象
  [...files].filter(f => f.type.startsWith('image/')).forEach(file => {
    const reader = new FileReader();
    reader.onload = ev => {
      const li = document.createElement('li');
      li.innerHTML = `<img src="${ev.target.result}" width="150">`;
      document.getElementById('preview').appendChild(li);
    };
    reader.readAsDataURL(file);
  });
});
关键:文件列表藏在 dataTransfer.files,用它就能预览/上传,无需 <input type="file">

4. 数据传递“小书包”——dataTransfer 常用 API

方法 / 属性说明
setData(format, data)写数据,格式任意字符串,常用 'text/html''text/plain'
getData(format)读数据(只能在 drop 事件里读)
clearData([format])清数据
files文件列表(拖拽系统文件时)
effectAllowed / dropEffect控制鼠标样式(copy/move/link/none

5. 事件触发顺序 & 坑位提醒

  1. 顺序固定:dragstartdragdragenterdragoverdropdragend
  2. dragover 每 100ms 触发一次,别在里面做重计算。
  3. 想改拖拽“幽灵图”用 setDragImage(img, x, y);默认是元素快照。
  4. 如果拖的是链接图片,浏览器默认会打开;记得 preventDefault()

6. 兼容性 & 体积

  • 拖拽 API 从 IE10 开始支持,移动端同样可用(手指长按即触发 drag)。
  • 无需任何库,原生实现体积 = 0KB,性能拉满。

7. 一句话总结

“draggable 打开开关,dragstart 写数据,dragover 阻止默认,drop 读数据,dragend 清尾巴。”

背完这句,下次产品说“做个拖拽排序/上传”——直接甩原生代码,十分钟收工!

0
快来点个赞吧

发表评论

隐私评论

评论列表

来写一个评论吧