用 HTML5 抓住一只“奶龙”——Drag & Drop 拖拽全攻略

201 阅读3分钟

前端的快乐,有一半来自写出好玩的交互。另一半,来自抓一只 奶龙 到手上。

没错,今天我们要用 HTML5 Drag & Drop API,做一个能把奶龙从一个盒子拖到另一个盒子的小游戏。
如果你会了这个,不仅能轻松玩转拖拽,还能用在 文件上传、任务排序、小游戏 这些实用场景里。


1. 先上效果,骗你点进来

20250806-1158-04.7730077.gif

看吧,奶龙被你抓来抓去的感觉,是不是很爽?
不过放心,它不会告你,它只是个可爱的小图片罢了。


2. 为什么要学拖拽?

  • 比点击有趣
    拖拽是一种“你看我就懂”的交互,不用解释,用户自然会玩。

  • 场景多到飞起

    • Google Drive 文件拖拽上传
    • Trello、Jira 拖拽任务排序
    • 把奶龙从一个窝拖到另一个窝(今日主线)
  • 提升用户体验
    iPad 当年火得一塌糊涂,不就是因为“戳一戳、拖一拖”傻瓜又好玩嘛。


3. HTML5 拖拽原理

HTML5 自带了拖拽 API,只要在元素上加一句话:

<div draggable="true"></div>

它就像被赋予了“灵魂”一样,能被你拽走了。
默认情况下,图片和选中文本也是可拖拽的。


4. 拖拽的流程像谈恋爱

整个拖拽过程,其实就像一场短暂的恋爱:

  1. dragstart → 初见倾心:我想抓你
  2. drag → 热恋期:我带你飞
  3. dragend → 分手快乐:拜拜了您嘞
  4. dragenter → 进入新恋情:你来了
  5. dragover → 在对方面前晃悠:想不想在一起?(这里必须 e.preventDefault(),否则没戏)
  6. dragleave → 走了:算了算了
  7. drop → 领证:你就是我的了

5. 为什么拖不动奶龙?因为少了 e.preventDefault()

这里是个很多人会踩的坑:
如果你在 dragover 事件里没有调用 e.preventDefault(),奶龙是 放不下去 的,drop 事件根本不会触发。

原因:

  • 浏览器默认不让你随便把东西“丢”到别的元素里
  • 必须明确告诉它:“我允许”,才会放下去
  • 而这个“允许”的暗号,就是 e.preventDefault()
function dragOver(e) {
  e.preventDefault(); // 允许奶龙在这里落脚
}

记住:

  • 不写 → drop 根本没戏
  • 写了 → 奶龙就乖乖躺到新窝里

6. 奶龙抓捕现场

6.1 HTML 结构

<div class="empty">
  <div class="fill" draggable="true"></div>
</div>
<div class="empty"></div>
<div class="empty"></div>
<div class="empty"></div>
<div class="empty"></div>

6.2 CSS 打造奶龙的窝

body {
  background-color: steelblue;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  margin: 0;
}

.empty {
  height: 150px;
  width: 150px;
  margin: 10px;
  border: 3px solid #000;
  background: white;
}

.fill {
  background-image: url('你的奶龙图片链接');
  background-size: cover;
  width: 145px;
  height: 145px;
  cursor: pointer;
}

.hold {
  border: 5px solid #ccc;
}

.hovered {
  background-color: #333;
  border-color: white;
  border-style: dashed;
}

/* 小屏幕适配 */
@media (max-width: 800px) {
  body {
    flex-direction: column;
  }
}

6.3 JavaScript:奶龙的搬家逻辑

const fill = document.querySelector('.fill');
const empties = document.querySelectorAll('.empty');

// 抓住奶龙
function dragStart(e) {
  if (!e.target.classList.contains('fill')) return;
  fill.classList.add('hold');
  setTimeout(() => fill.className = 'invisible', 0);
}

// 松开奶龙
function dragEnd() {
  fill.className = 'fill';
}

// 奶龙走到新窝门口
function dragEnter(e) {
  e.preventDefault();
  this.classList.add('hovered');
}

// 在新窝门口晃悠(必须允许)
function dragOver(e) {
  e.preventDefault(); // 核心,允许 drop
}

// 离开新窝
function dragLeave() {
  this.className = 'empty';
}

// 放下奶龙
function drop() {
  this.className = 'empty';
  this.append(fill);
}

// 绑定事件
fill.addEventListener('dragstart', dragStart);
fill.addEventListener('dragend', dragEnd);
empties.forEach(empty => {
  empty.addEventListener('dragover', dragOver);
  empty.addEventListener('dragenter', dragEnter);
  empty.addEventListener('dragleave', dragLeave);
  empty.addEventListener('drop', drop);
});

7. 小技巧

  1. preventDefault() 必须写
    不写 → 拖拽白费力
    写了 → drop 顺利执行
  2. 样式反馈很重要
    dragenterdragleave 里加样式,让用户知道这里是“合法窝”。
  3. 多端适配
    @media 让手机和平板下变成垂直排列,拖拽体验更好。

8. 总结

HTML5 拖拽 API 的流程很简单:

  1. 元素加 draggable="true"
  2. 监听拖拽源 + 目标事件
  3. dragover 阻止默认行为
  4. 样式反馈,让交互更直观