Skip to content

draggable 属性

draggable 是 HTML5 Drag and Drop API 的核心属性,用于标记一个元素是否可被拖拽。通过设置 draggable="true",开发者可以让任何 HTML 元素参与拖放操作,极大提升了 Web 应用的交互能力。本节将详细介绍 draggable 属性的使用方法、取值规则以及常见应用场景。

前置知识

阅读本节前,建议先了解:媒体事件

基础概念

什么是 draggable 属性

draggable 是一个 HTML 全局属性(Global Attribute),适用于所有 HTML 元素。它决定了用户是否可以拖动该元素。在 HTML5 之前,只有图片和链接(<a> 标签)默认支持拖拽行为。HTML5 通过引入 draggable 属性,将拖拽能力扩展到了任意元素。

draggable 的三个取值

说明示例场景
true元素可拖拽自定义拖拽列表项
false元素不可拖拽阻止默认可拖拽的图片
auto使用浏览器默认行为链接和图片默认可拖
html
<!-- 可拖拽的 div -->
<div draggable="true">拖拽我</div>

<!-- 禁止图片拖拽(图片默认可拖) -->
<img draggable="false" src="photo.jpg" alt="禁止拖拽的图片">

<!-- 使用浏览器默认行为 -->
<div draggable="auto">遵循浏览器默认设置</div>

默认可拖拽的元素

浏览器中有两种元素默认可拖拽,无需显式设置 draggable="true"

元素默认行为说明
<img>可拖拽图片默认支持拖拽到桌面或地址栏
<a>(带 href)可拖拽链接默认支持拖拽到地址栏
html
<!-- 这两个元素默认可拖拽,不需要 draggable="true" -->
<img src="logo.png" alt="Logo">
<a href="https://example.com">可拖拽的链接</a>

<!-- 如果要禁止它们的默认拖拽行为 -->
<img draggable="false" src="logo.png" alt="禁止拖拽">
<a href="https://example.com" draggable="false">不可拖拽的链接</a>

语法与配置

基本语法

html
<!-- 语法格式 -->
<element draggable="true|false|auto"></element>

HTML 属性方式

html
<!-- 可拖拽元素 -->
<div draggable="true" id="drag-target" style="padding: 10px; background: #e0e0e0;">
  拖拽我
</div>

<!-- 多个可拖拽元素 -->
<ul>
  <li draggable="true" data-id="1">项目一</li>
  <li draggable="true" data-id="2">项目二</li>
  <li draggable="true" data-id="3">项目三</li>
</ul>

JavaScript 动态设置

javascript
// 通过属性设置
const element = document.getElementById('my-element');
element.setAttribute('draggable', 'true');

// 通过 DOM 属性设置
element.draggable = true;

// 禁用拖拽
element.draggable = false;

// 查询拖拽状态
if (element.draggable) {
  console.log('该元素可拖拽');
}

// 获取页面所有可拖拽元素
const draggableElements = document.querySelectorAll('[draggable="true"]');
console.log(`找到 ${draggableElements.length} 个可拖拽元素`);

详细说明

draggable 属性的继承与覆盖

draggable 属性的值不会从父元素继承。每个需要拖拽的元素都必须单独设置。

html
<!-- 父元素设置 draggable,子元素不会自动可拖拽 -->
<div draggable="true">
  <p>这段文字不可拖拽(p 元素未设置 draggable)</p>
  <span draggable="true">这个 span 可拖拽</span>
</div>

draggable 与 contenteditable 的交互

当元素同时具有 draggablecontenteditable 属性时,需要注意交互冲突:

html
<!-- 同时设置拖拽和编辑 -->
<div draggable="true" contenteditable="true">
  这个元素既可以拖拽也可以编辑
</div>
javascript
// 解决冲突:在拖拽开始时阻止编辑
const editableDrag = document.querySelector('[draggable="true"][contenteditable="true"]');

editableDrag.addEventListener('dragstart', (e) => {
  // 拖拽时禁用编辑
  editableDrag.contentEditable = 'false';
  e.dataTransfer.setData('text/plain', editableDrag.textContent);
});

editableDrag.addEventListener('dragend', () => {
  // 拖拽结束后恢复编辑
  editableDrag.contentEditable = 'true';
});

拖拽手柄模式

并非整个元素都需要可拖拽,可以只设置拖拽手柄:

html
<div class="card">
  <div class="drag-handle" draggable="true" data-card-id="1">
    <span class="grip-icon">≡</span>
  </div>
  <div class="card-content">
    <h3>卡片标题</h3>
    <p>卡片内容区域,不可拖拽</p>
  </div>
</div>
javascript
// 拖拽手柄实现
document.querySelectorAll('.drag-handle').forEach(handle => {
  handle.addEventListener('dragstart', (e) => {
    // 将整个卡片作为拖拽数据
    const card = handle.closest('.card');
    e.dataTransfer.setData('text/plain', card.dataset.cardId);
    e.dataTransfer.effectAllowed = 'move';

    // 添加拖拽样式
    setTimeout(() => {
      card.style.opacity = '0.4';
    }, 0);
  });

  handle.addEventListener('dragend', (e) => {
    const card = handle.closest('.card');
    card.style.opacity = '1';
  });
});

不同元素类型的拖拽设置

html
<!-- 文本内容拖拽 -->
<div draggable="true" id="text-drag">拖拽这段文字</div>

<!-- 图片拖拽 -->
<img draggable="true" src="product.jpg" alt="可拖拽的商品图片">

<!-- 自定义卡片拖拽 -->
<div draggable="true" class="todo-item" data-id="101">
  <span class="status">待办</span>
  <span class="title">完成项目报告</span>
</div>

<!-- 表格行拖拽 -->
<table>
  <tr draggable="true" data-row="1">
    <td>数据一</td>
    <td>数据二</td>
  </tr>
  <tr draggable="true" data-row="2">
    <td>数据三</td>
    <td>数据四</td>
  </tr>
</table>

<!-- 禁用默认可拖拽的链接 -->
<a href="https://example.com" draggable="false">
  这个链接不能拖拽
</a>

实战示例

完整可拖拽列表示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>draggable 属性演示</title>
  <style>
    body { font-family: Arial, sans-serif; padding: 20px; }
    .drag-item {
      padding: 12px 16px;
      margin: 6px 0;
      background: #f0f0f0;
      border: 2px solid #ddd;
      border-radius: 4px;
      cursor: grab;
      user-select: none;
    }
    .drag-item:active { cursor: grabbing; }
    .drag-item.dragging { opacity: 0.5; background: #e0e0ff; }
    .drop-zone {
      min-height: 200px;
      padding: 16px;
      border: 3px dashed #aaa;
      border-radius: 8px;
      background: #fafafa;
    }
    .drop-zone.drag-over {
      border-color: #4CAF50;
      background: #f0f9f0;
    }
  </style>
</head>
<body>
  <h2>draggable 属性演示</h2>

  <h3>待拖拽项</h3>
  <div class="drag-item" draggable="true" data-id="1">项目 A</div>
  <div class="drag-item" draggable="true" data-id="2">项目 B</div>
  <div class="drag-item" draggable="true" data-id="3">项目 C</div>

  <h3>放置区域</h3>
  <div class="drop-zone" id="dropZone">
    将项目拖到这里
  </div>

  <script>
    // 获取所有可拖拽元素
    const dragItems = document.querySelectorAll('.drag-item');
    const dropZone = document.getElementById('dropZone');

    // 为每个可拖拽元素绑定事件
    dragItems.forEach(item => {
      item.addEventListener('dragstart', (e) => {
        // 设置拖拽数据
        e.dataTransfer.setData('text/plain', item.dataset.id);
        e.dataTransfer.effectAllowed = 'move';
        // 添加拖拽中样式
        item.classList.add('dragging');
      });

      item.addEventListener('dragend', (e) => {
        // 移除拖拽中样式
        item.classList.remove('dragging');
      });
    });

    // 放置区域事件
    dropZone.addEventListener('dragover', (e) => {
      e.preventDefault();
      e.dataTransfer.dropEffect = 'move';
      dropZone.classList.add('drag-over');
    });

    dropZone.addEventListener('dragleave', () => {
      dropZone.classList.remove('drag-over');
    });

    dropZone.addEventListener('drop', (e) => {
      e.preventDefault();
      dropZone.classList.remove('drag-over');
      const id = e.dataTransfer.getData('text/plain');
      const item = document.querySelector(`[data-id="${id}"]`);
      if (item) {
        dropZone.appendChild(item);
      }
    });
  </script>
</body>
</html>

拖拽状态检测工具

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>拖拽状态检测</title>
  <style>
    .info-panel {
      padding: 16px;
      background: #f5f5f5;
      border-radius: 8px;
      font-family: monospace;
      margin-top: 16px;
    }
  </style>
</head>
<body>
  <h2>元素拖拽状态检测</h2>

  <div id="test1" draggable="true" style="padding:10px; background:#ddd; margin:8px 0;">
    可拖拽元素 (draggable="true")
  </div>
  <div id="test2" draggable="false" style="padding:10px; background:#ddd; margin:8px 0;">
    不可拖拽元素 (draggable="false")
  </div>
  <div id="test3" style="padding:10px; background:#ddd; margin:8px 0;">
    未设置 draggable 属性
  </div>

  <button onclick="checkDraggable()">检测拖拽状态</button>

  <div class="info-panel" id="result"></div>

  <script>
    function checkDraggable() {
      const elements = [
        { el: document.getElementById('test1'), name: 'test1' },
        { el: document.getElementById('test2'), name: 'test2' },
        { el: document.getElementById('test3'), name: 'test3' }
      ];

      let result = '';
      elements.forEach(({ el, name }) => {
        const isDraggable = el.draggable;
        const hasAttr = el.hasAttribute('draggable');
        const attrValue = el.getAttribute('draggable');

        result += `${name}: draggable=${isDraggable}, 属性存在=${hasAttr}, 属性值="${attrValue}"\n`;
      });

      document.getElementById('result').textContent = result;
    }
  </script>
</body>
</html>

注意事项

浏览器兼容性

浏览器支持情况备注
Chrome完全支持所有版本
Firefox完全支持所有版本
Safari部分支持需注意 dragend 事件
Edge完全支持Chromium 内核
iOS Safari不支持移动端不支持拖放
Android Chrome有限支持部分设备可用

常见问题

1. 拖拽在移动端不工作

HTML5 Drag and Drop API 在大多数移动浏览器中不受支持。移动端需要使用触摸事件(Touch Events)实现类似功能。

javascript
// 移动端触摸拖拽的替代方案
let touchStartY, touchCurrentY, draggedElement;

element.addEventListener('touchstart', (e) => {
  draggedElement = e.target;
  touchStartY = e.touches[0].clientY;
  draggedElement.style.position = 'fixed';
  draggedElement.style.zIndex = '1000';
});

element.addEventListener('touchmove', (e) => {
  e.preventDefault();
  touchCurrentY = e.touches[0].clientY;
  draggedElement.style.top = touchCurrentY + 'px';
});

element.addEventListener('touchend', (e) => {
  // 判断放置位置并执行操作
  draggedElement.style.position = '';
  draggedElement.style.zIndex = '';
});

2. 拖拽时文本被选中

javascript
// 防止拖拽时选中文字
element.addEventListener('dragstart', (e) => {
  e.dataTransfer.effectAllowed = 'move';
  // 部分浏览器需要延迟清除选中
  window.getSelection().removeAllRanges();
});

3. Firefox 需要 setData

Firefox 要求 dragstart 事件中必须调用 setData(),否则拖拽不会生效。

javascript
element.addEventListener('dragstart', (e) => {
  // Firefox 必须调用 setData
  e.dataTransfer.setData('text/plain', '');
});

最佳实践

推荐做法

  1. 使用 user-select: none 防止拖拽时文本被选中
  2. 添加视觉反馈 让用户明确知道元素可拖拽
  3. 使用 data-* 属性 存储拖拽相关的元数据
  4. 设置 effectAllowed 控制拖拽效果类型
css
/* 拖拽元素的基础样式 */
[draggable="true"] {
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
}

[draggable="true"]:active {
  cursor: grabbing;
}

[draggable="true"]:hover {
  background: rgba(0, 0, 0, 0.05);
}

拖拽属性配置建议

场景draggableeffectAllowed说明
列表排序truemove移动元素位置
文件上传区true(浏览器)copy复制文件到上传区
跨容器拖拽truemovecopy根据场景选择
只读展示false禁止拖拽

下一节

继续学习:拖放事件处理

参考链接