Search K
Appearance
Appearance
draggable, dragstart, dragend, dragover, drop, dataTransfer, dropzone, dragenter, dragleave, setDragImage, effectAllowed, dropEffect
HTML5拖放API(Drag and Drop API)允许用户通过鼠标拖动元素到页面的其他位置,实现直观的交互体验。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML5拖放基础</title>
</head>
<body>
<h1>拖放API基础示例</h1>
<!-- 可拖动的元素 -->
<div id="draggable-box" draggable="true">
<p>拖动我到目标区域</p>
</div>
<!-- 拖放目标区域 -->
<div id="drop-zone">
<p>拖放目标区域</p>
</div>
<!-- 拖放结果显示 -->
<div id="result-area" role="log" aria-live="polite">
<p>拖放结果将显示在这里</p>
</div>
<script>
// 拖放事件处理将在这里实现
// 实际的JavaScript代码需要根据具体需求编写
</script>
</body>
</html><!-- 可拖动的元素 -->
<div draggable="true">可拖动的div</div>
<p draggable="true">可拖动的段落</p>
<img src="image.jpg" alt="图片" draggable="true">
<!-- 默认情况下不可拖动 -->
<div draggable="false">不可拖动的div</div>
<span draggable="false">不可拖动的span</span>
<!-- 使用默认行为 -->
<a href="#" draggable="auto">链接(默认可拖动)</a>
<img src="image.jpg" alt="图片" draggable="auto">HTML5拖放API提供了七个主要事件:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>拖放事件处理</title>
</head>
<body>
<h1>拖放事件处理示例</h1>
<!-- 可拖动的卡片 -->
<section class="draggable-items">
<h2>可拖动的卡片</h2>
<div class="card" draggable="true" data-id="card1">
<h3>卡片1</h3>
<p>这是第一张卡片的内容</p>
</div>
<div class="card" draggable="true" data-id="card2">
<h3>卡片2</h3>
<p>这是第二张卡片的内容</p>
</div>
<div class="card" draggable="true" data-id="card3">
<h3>卡片3</h3>
<p>这是第三张卡片的内容</p>
</div>
</section>
<!-- 拖放目标区域 -->
<section class="drop-zones">
<h2>拖放目标区域</h2>
<div class="drop-zone" data-zone="zone1">
<h3>区域1</h3>
<p>将卡片拖到这里</p>
</div>
<div class="drop-zone" data-zone="zone2">
<h3>区域2</h3>
<p>将卡片拖到这里</p>
</div>
</section>
<!-- 事件日志 -->
<section class="event-log">
<h2>事件日志</h2>
<div id="log" role="log" aria-live="polite"></div>
</section>
<script>
// 拖放事件处理程序
document.addEventListener('DOMContentLoaded', function() {
// 获取所有可拖动元素
const draggableElements = document.querySelectorAll('.card');
// 获取所有拖放目标
const dropZones = document.querySelectorAll('.drop-zone');
// 获取日志元素
const logElement = document.getElementById('log');
// 拖动开始事件处理
draggableElements.forEach(element => {
element.addEventListener('dragstart', handleDragStart);
element.addEventListener('drag', handleDrag);
element.addEventListener('dragend', handleDragEnd);
});
// 拖放目标事件处理
dropZones.forEach(zone => {
zone.addEventListener('dragenter', handleDragEnter);
zone.addEventListener('dragover', handleDragOver);
zone.addEventListener('dragleave', handleDragLeave);
zone.addEventListener('drop', handleDrop);
});
// 事件处理函数将在这里实现
// 实际的JavaScript代码需要根据具体需求编写
});
</script>
</body>
</html><!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>拖放事件最佳实践</title>
</head>
<body>
<h1>拖放事件最佳实践</h1>
<!-- 文件拖放区域 -->
<div class="file-drop-zone"
role="button"
tabindex="0"
aria-label="文件拖放区域,点击或拖放文件到此区域">
<p>拖放文件到此区域或点击选择文件</p>
<input type="file" id="file-input" multiple accept="image/*" hidden>
</div>
<!-- 拖放状态反馈 -->
<div class="drag-feedback" role="status" aria-live="polite">
<p id="drag-status">准备拖放</p>
</div>
<!-- 拖放结果显示 -->
<div class="drop-result">
<h2>拖放结果</h2>
<ul id="file-list" role="list"></ul>
</div>
<script>
// 拖放事件处理最佳实践
document.addEventListener('DOMContentLoaded', function() {
const dropZone = document.querySelector('.file-drop-zone');
const fileInput = document.getElementById('file-input');
const dragStatus = document.getElementById('drag-status');
const fileList = document.getElementById('file-list');
// 防止默认拖放行为
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
});
// 拖放状态处理
['dragenter', 'dragover'].forEach(eventName => {
dropZone.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, unhighlight, false);
});
// 文件拖放处理
dropZone.addEventListener('drop', handleDrop, false);
// 点击选择文件
dropZone.addEventListener('click', () => fileInput.click());
// 键盘访问支持
dropZone.addEventListener('keydown', function(e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
fileInput.click();
}
});
// 事件处理函数将在这里实现
// 实际的JavaScript代码需要根据具体需求编写
});
</script>
</body>
</html>DataTransfer对象是拖放操作的核心,用于在拖放过程中传输数据。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>拖放数据传输</title>
</head>
<body>
<h1>拖放数据传输示例</h1>
<!-- 数据源 -->
<section class="data-source">
<h2>数据源</h2>
<div class="item"
draggable="true"
data-type="text"
data-content="这是一段文本内容">
<p>文本数据</p>
</div>
<div class="item"
draggable="true"
data-type="url"
data-content="https://example.com">
<p>URL数据</p>
</div>
<div class="item"
draggable="true"
data-type="json"
data-content='{"name":"张三","age":25}'>
<p>JSON数据</p>
</div>
</section>
<!-- 数据接收器 -->
<section class="data-receiver">
<h2>数据接收器</h2>
<div class="receiver" data-accept="text">
<h3>文本接收器</h3>
<p>只接受文本数据</p>
</div>
<div class="receiver" data-accept="url">
<h3>URL接收器</h3>
<p>只接受URL数据</p>
</div>
<div class="receiver" data-accept="json">
<h3>JSON接收器</h3>
<p>只接受JSON数据</p>
</div>
</section>
<!-- 数据显示 -->
<section class="data-display">
<h2>接收的数据</h2>
<div id="received-data" role="log" aria-live="polite"></div>
</section>
<script>
// 数据传输处理
document.addEventListener('DOMContentLoaded', function() {
const items = document.querySelectorAll('.item');
const receivers = document.querySelectorAll('.receiver');
const dataDisplay = document.getElementById('received-data');
// 拖动开始时设置数据
items.forEach(item => {
item.addEventListener('dragstart', function(e) {
const dataType = this.dataset.type;
const content = this.dataset.content;
// 设置不同类型的数据
e.dataTransfer.setData('text/plain', content);
e.dataTransfer.setData('text/' + dataType, content);
e.dataTransfer.setData('application/json',
JSON.stringify({
type: dataType,
content: content,
timestamp: new Date().toISOString()
})
);
// 设置拖放效果
e.dataTransfer.effectAllowed = 'copy';
});
});
// 数据接收处理
receivers.forEach(receiver => {
receiver.addEventListener('dragover', function(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
});
receiver.addEventListener('drop', function(e) {
e.preventDefault();
const acceptType = this.dataset.accept;
const data = e.dataTransfer.getData('text/' + acceptType);
if (data) {
// 显示接收到的数据
const result = document.createElement('div');
result.innerHTML = `
<h4>接收到${acceptType}数据:</h4>
<p>${data}</p>
<small>时间:${new Date().toLocaleString()}</small>
`;
dataDisplay.appendChild(result);
}
});
});
});
</script>
</body>
</html><!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>复杂数据传输</title>
</head>
<body>
<h1>复杂数据传输示例</h1>
<!-- 任务列表 -->
<section class="task-list">
<h2>任务列表</h2>
<div class="task"
draggable="true"
data-task-id="1"
data-task-data='{"id":1,"title":"完成报告","priority":"high","deadline":"2024-01-15"}'>
<h3>完成报告</h3>
<p>优先级: 高</p>
<p>截止日期: 2024-01-15</p>
</div>
<div class="task"
draggable="true"
data-task-id="2"
data-task-data='{"id":2,"title":"代码审查","priority":"medium","deadline":"2024-01-20"}'>
<h3>代码审查</h3>
<p>优先级: 中</p>
<p>截止日期: 2024-01-20</p>
</div>
<div class="task"
draggable="true"
data-task-id="3"
data-task-data='{"id":3,"title":"文档更新","priority":"low","deadline":"2024-01-25"}'>
<h3>文档更新</h3>
<p>优先级: 低</p>
<p>截止日期: 2024-01-25</p>
</div>
</section>
<!-- 状态列 -->
<section class="status-columns">
<h2>任务状态</h2>
<div class="status-column" data-status="todo">
<h3>待办</h3>
<div class="task-container" role="list"></div>
</div>
<div class="status-column" data-status="inprogress">
<h3>进行中</h3>
<div class="task-container" role="list"></div>
</div>
<div class="status-column" data-status="done">
<h3>已完成</h3>
<div class="task-container" role="list"></div>
</div>
</section>
<!-- 任务详情 -->
<section class="task-details">
<h2>任务详情</h2>
<div id="task-info" role="region" aria-live="polite"></div>
</section>
<script>
// 复杂数据传输处理
document.addEventListener('DOMContentLoaded', function() {
const tasks = document.querySelectorAll('.task');
const statusColumns = document.querySelectorAll('.status-column');
const taskInfo = document.getElementById('task-info');
// 任务拖动处理
tasks.forEach(task => {
task.addEventListener('dragstart', function(e) {
const taskData = JSON.parse(this.dataset.taskData);
const taskId = this.dataset.taskId;
// 设置多种数据格式
e.dataTransfer.setData('text/plain', taskData.title);
e.dataTransfer.setData('text/task-id', taskId);
e.dataTransfer.setData('application/json', JSON.stringify(taskData));
e.dataTransfer.setData('text/html', this.outerHTML);
// 设置拖放效果
e.dataTransfer.effectAllowed = 'move';
});
});
// 状态列拖放处理
statusColumns.forEach(column => {
const container = column.querySelector('.task-container');
container.addEventListener('dragover', function(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
});
container.addEventListener('drop', function(e) {
e.preventDefault();
const taskData = JSON.parse(e.dataTransfer.getData('application/json'));
const taskId = e.dataTransfer.getData('text/task-id');
const newStatus = column.dataset.status;
// 更新任务状态
taskData.status = newStatus;
taskData.updateTime = new Date().toISOString();
// 创建新的任务元素
const newTask = document.createElement('div');
newTask.className = 'task';
newTask.draggable = true;
newTask.dataset.taskId = taskId;
newTask.dataset.taskData = JSON.stringify(taskData);
newTask.innerHTML = `
<h4>${taskData.title}</h4>
<p>优先级: ${taskData.priority}</p>
<p>截止日期: ${taskData.deadline}</p>
<p>状态: ${newStatus}</p>
`;
// 添加到新位置
this.appendChild(newTask);
// 显示任务详情
taskInfo.innerHTML = `
<h3>任务已更新</h3>
<p>任务ID: ${taskId}</p>
<p>标题: ${taskData.title}</p>
<p>新状态: ${newStatus}</p>
<p>更新时间: ${new Date(taskData.updateTime).toLocaleString()}</p>
`;
});
});
});
</script>
</body>
</html>HTML5拖放API提供了多种拖放效果:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>拖放效果控制</title>
</head>
<body>
<h1>拖放效果控制示例</h1>
<!-- 不同效果的拖动源 -->
<section class="drag-sources">
<h2>拖动源</h2>
<div class="source" draggable="true" data-effect="copy">
<h3>复制源</h3>
<p>拖动时显示复制效果</p>
</div>
<div class="source" draggable="true" data-effect="move">
<h3>移动源</h3>
<p>拖动时显示移动效果</p>
</div>
<div class="source" draggable="true" data-effect="link">
<h3>链接源</h3>
<p>拖动时显示链接效果</p>
</div>
<div class="source" draggable="true" data-effect="copyMove">
<h3>复制/移动源</h3>
<p>支持复制和移动操作</p>
</div>
</section>
<!-- 不同类型的拖放目标 -->
<section class="drop-targets">
<h2>拖放目标</h2>
<div class="target" data-accept="copy">
<h3>复制目标</h3>
<p>只接受复制操作</p>
</div>
<div class="target" data-accept="move">
<h3>移动目标</h3>
<p>只接受移动操作</p>
</div>
<div class="target" data-accept="link">
<h3>链接目标</h3>
<p>只接受链接操作</p>
</div>
<div class="target" data-accept="all">
<h3>全能目标</h3>
<p>接受所有操作</p>
</div>
</section>
<!-- 效果反馈 -->
<section class="effect-feedback">
<h2>效果反馈</h2>
<div id="effect-log" role="log" aria-live="polite"></div>
</section>
<script>
// 拖放效果控制
document.addEventListener('DOMContentLoaded', function() {
const sources = document.querySelectorAll('.source');
const targets = document.querySelectorAll('.target');
const effectLog = document.getElementById('effect-log');
// 拖动源处理
sources.forEach(source => {
source.addEventListener('dragstart', function(e) {
const effect = this.dataset.effect;
// 设置允许的效果
e.dataTransfer.effectAllowed = effect;
e.dataTransfer.setData('text/plain', this.textContent);
// 记录开始拖动
logEffect(`开始拖动,允许效果: ${effect}`);
});
source.addEventListener('dragend', function(e) {
const actualEffect = e.dataTransfer.dropEffect;
logEffect(`拖动结束,实际效果: ${actualEffect}`);
});
});
// 拖放目标处理
targets.forEach(target => {
target.addEventListener('dragover', function(e) {
e.preventDefault();
const acceptType = this.dataset.accept;
const allowedEffects = e.dataTransfer.effectAllowed;
// 根据目标类型设置拖放效果
if (acceptType === 'all') {
e.dataTransfer.dropEffect = 'copy';
} else if (allowedEffects.includes(acceptType)) {
e.dataTransfer.dropEffect = acceptType;
} else {
e.dataTransfer.dropEffect = 'none';
}
});
target.addEventListener('drop', function(e) {
e.preventDefault();
const dropEffect = e.dataTransfer.dropEffect;
const data = e.dataTransfer.getData('text/plain');
if (dropEffect !== 'none') {
logEffect(`拖放成功,效果: ${dropEffect}, 数据: ${data}`);
// 根据效果类型执行不同操作
switch (dropEffect) {
case 'copy':
this.style.backgroundColor = '#e8f5e8';
break;
case 'move':
this.style.backgroundColor = '#e8e8f5';
break;
case 'link':
this.style.backgroundColor = '#f5e8e8';
break;
}
}
});
});
// 记录效果日志
function logEffect(message) {
const logEntry = document.createElement('div');
logEntry.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
effectLog.appendChild(logEntry);
effectLog.scrollTop = effectLog.scrollHeight;
}
});
</script>
</body>
</html><!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>自定义拖动图像</title>
</head>
<body>
<h1>自定义拖动图像示例</h1>
<!-- 拖动元素 -->
<section class="drag-items">
<h2>拖动元素</h2>
<div class="item" draggable="true" data-image="default">
<h3>默认图像</h3>
<p>使用默认拖动图像</p>
</div>
<div class="item" draggable="true" data-image="custom">
<h3>自定义图像</h3>
<p>使用自定义拖动图像</p>
</div>
<div class="item" draggable="true" data-image="canvas">
<h3>Canvas图像</h3>
<p>使用Canvas生成的拖动图像</p>
</div>
</section>
<!-- 拖放目标 -->
<section class="drop-area">
<h2>拖放目标</h2>
<div class="drop-zone">
<p>将元素拖到这里</p>
</div>
</section>
<!-- 隐藏的自定义图像 -->
<div id="custom-drag-image" style="position: absolute; left: -1000px; top: -1000px;">
<div style="padding: 10px; background: #007bff; color: white; border-radius: 5px;">
🚀 正在拖动...
</div>
</div>
<!-- Canvas元素 -->
<canvas id="drag-canvas" width="100" height="50" style="position: absolute; left: -1000px; top: -1000px;"></canvas>
<script>
// 自定义拖动图像
document.addEventListener('DOMContentLoaded', function() {
const items = document.querySelectorAll('.item');
const dropZone = document.querySelector('.drop-zone');
const customImage = document.getElementById('custom-drag-image');
const canvas = document.getElementById('drag-canvas');
const ctx = canvas.getContext('2d');
// 准备Canvas图像
ctx.fillStyle = '#28a745';
ctx.fillRect(0, 0, 100, 50);
ctx.fillStyle = 'white';
ctx.font = '16px Arial';
ctx.textAlign = 'center';
ctx.fillText('拖动中', 50, 30);
// 拖动处理
items.forEach(item => {
item.addEventListener('dragstart', function(e) {
const imageType = this.dataset.image;
e.dataTransfer.setData('text/plain', this.textContent);
// 根据类型设置拖动图像
switch (imageType) {
case 'custom':
e.dataTransfer.setDragImage(customImage, 50, 25);
break;
case 'canvas':
e.dataTransfer.setDragImage(canvas, 50, 25);
break;
default:
// 使用默认图像
break;
}
});
});
// 拖放目标处理
dropZone.addEventListener('dragover', function(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
});
dropZone.addEventListener('drop', function(e) {
e.preventDefault();
const data = e.dataTransfer.getData('text/plain');
this.innerHTML = `<p>接收到: ${data}</p>`;
});
});
</script>
</body>
</html><!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传拖放</title>
</head>
<body>
<h1>文件上传拖放应用</h1>
<!-- 文件上传区域 -->
<section class="file-upload">
<h2>文件上传</h2>
<div class="upload-zone"
role="button"
tabindex="0"
aria-label="文件上传区域,拖放文件或点击选择">
<div class="upload-content">
<p>📁 拖放文件到此区域</p>
<p>或点击选择文件</p>
<p>支持多文件上传</p>
</div>
<input type="file" id="file-input" multiple accept="image/*,application/pdf,text/*" hidden>
</div>
<!-- 上传进度 -->
<div class="upload-progress" id="upload-progress" hidden>
<h3>上传进度</h3>
<div class="progress-bar">
<div class="progress-fill" id="progress-fill"></div>
</div>
<p id="progress-text">0%</p>
</div>
</section>
<!-- 文件列表 -->
<section class="file-list">
<h2>已选择的文件</h2>
<div id="file-items" role="list"></div>
</section>
<!-- 预览区域 -->
<section class="file-preview">
<h2>文件预览</h2>
<div id="preview-area"></div>
</section>
<script>
// 文件上传拖放实现
document.addEventListener('DOMContentLoaded', function() {
const uploadZone = document.querySelector('.upload-zone');
const fileInput = document.getElementById('file-input');
const fileItems = document.getElementById('file-items');
const previewArea = document.getElementById('preview-area');
const uploadProgress = document.getElementById('upload-progress');
const progressFill = document.getElementById('progress-fill');
const progressText = document.getElementById('progress-text');
// 防止默认拖放行为
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
uploadZone.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
});
// 高亮处理
['dragenter', 'dragover'].forEach(eventName => {
uploadZone.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
uploadZone.addEventListener(eventName, unhighlight, false);
});
// 文件拖放处理
uploadZone.addEventListener('drop', handleDrop, false);
// 点击选择文件
uploadZone.addEventListener('click', () => fileInput.click());
// 文件选择处理
fileInput.addEventListener('change', function(e) {
handleFiles(e.target.files);
});
// 事件处理函数
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
function highlight() {
uploadZone.classList.add('dragover');
}
function unhighlight() {
uploadZone.classList.remove('dragover');
}
function handleDrop(e) {
const files = e.dataTransfer.files;
handleFiles(files);
}
function handleFiles(files) {
// 清空文件列表
fileItems.innerHTML = '';
previewArea.innerHTML = '';
// 处理每个文件
Array.from(files).forEach(file => {
displayFile(file);
previewFile(file);
});
// 模拟上传进度
simulateUpload();
}
function displayFile(file) {
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.innerHTML = `
<h4>${file.name}</h4>
<p>大小: ${formatFileSize(file.size)}</p>
<p>类型: ${file.type || '未知'}</p>
<p>最后修改: ${new Date(file.lastModified).toLocaleString()}</p>
<button onclick="removeFile(this)">删除</button>
`;
fileItems.appendChild(fileItem);
}
function previewFile(file) {
const reader = new FileReader();
reader.onload = function(e) {
const preview = document.createElement('div');
preview.className = 'file-preview-item';
if (file.type.startsWith('image/')) {
preview.innerHTML = `
<img src="${e.target.result}" alt="${file.name}" style="max-width: 200px; max-height: 200px;">
<p>${file.name}</p>
`;
} else if (file.type === 'text/plain') {
preview.innerHTML = `
<h4>${file.name}</h4>
<pre>${e.target.result.substring(0, 200)}...</pre>
`;
} else {
preview.innerHTML = `
<h4>${file.name}</h4>
<p>无法预览此文件类型</p>
`;
}
previewArea.appendChild(preview);
};
if (file.type.startsWith('image/') || file.type === 'text/plain') {
reader.readAsDataURL(file);
} else {
reader.readAsText(file);
}
}
function simulateUpload() {
uploadProgress.hidden = false;
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 15;
if (progress >= 100) {
progress = 100;
clearInterval(interval);
setTimeout(() => {
uploadProgress.hidden = true;
}, 1000);
}
progressFill.style.width = progress + '%';
progressText.textContent = Math.round(progress) + '%';
}, 200);
}
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// 删除文件
window.removeFile = function(button) {
button.parentElement.remove();
};
});
</script>
</body>
</html><!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>任务看板拖放</title>
</head>
<body>
<h1>任务看板拖放应用</h1>
<!-- 任务看板 -->
<section class="kanban-board">
<h2>项目任务看板</h2>
<!-- 待办列 -->
<div class="kanban-column" data-status="todo">
<h3>待办事项</h3>
<div class="task-list" role="list">
<div class="task-card" draggable="true" data-task-id="1">
<h4>设计系统架构</h4>
<p>设计整体系统架构和技术选型</p>
<div class="task-meta">
<span class="priority high">高优先级</span>
<span class="assignee">张三</span>
</div>
</div>
<div class="task-card" draggable="true" data-task-id="2">
<h4>编写API文档</h4>
<p>编写RESTful API接口文档</p>
<div class="task-meta">
<span class="priority medium">中优先级</span>
<span class="assignee">李四</span>
</div>
</div>
</div>
</div>
<!-- 进行中列 -->
<div class="kanban-column" data-status="inprogress">
<h3>进行中</h3>
<div class="task-list" role="list">
<div class="task-card" draggable="true" data-task-id="3">
<h4>前端界面开发</h4>
<p>开发用户界面和交互功能</p>
<div class="task-meta">
<span class="priority high">高优先级</span>
<span class="assignee">王五</span>
</div>
</div>
</div>
</div>
<!-- 测试列 -->
<div class="kanban-column" data-status="testing">
<h3>测试中</h3>
<div class="task-list" role="list">
<div class="task-card" draggable="true" data-task-id="4">
<h4>单元测试</h4>
<p>编写和执行单元测试用例</p>
<div class="task-meta">
<span class="priority medium">中优先级</span>
<span class="assignee">赵六</span>
</div>
</div>
</div>
</div>
<!-- 完成列 -->
<div class="kanban-column" data-status="done">
<h3>已完成</h3>
<div class="task-list" role="list">
<div class="task-card" draggable="true" data-task-id="5">
<h4>数据库设计</h4>
<p>设计数据库表结构和关系</p>
<div class="task-meta">
<span class="priority high">高优先级</span>
<span class="assignee">张三</span>
</div>
</div>
</div>
</div>
</section>
<!-- 任务详情 -->
<section class="task-details">
<h2>任务详情</h2>
<div id="task-info" role="region" aria-live="polite">
<p>拖动任务卡片到不同状态列</p>
</div>
</section>
<!-- 操作日志 -->
<section class="operation-log">
<h2>操作日志</h2>
<div id="log-content" role="log" aria-live="polite"></div>
</section>
<script>
// 任务看板拖放实现
document.addEventListener('DOMContentLoaded', function() {
const taskCards = document.querySelectorAll('.task-card');
const taskLists = document.querySelectorAll('.task-list');
const taskInfo = document.getElementById('task-info');
const logContent = document.getElementById('log-content');
let draggedTask = null;
// 任务卡片拖动事件
taskCards.forEach(card => {
card.addEventListener('dragstart', function(e) {
draggedTask = this;
// 设置拖动数据
e.dataTransfer.setData('text/plain', this.dataset.taskId);
e.dataTransfer.setData('text/html', this.outerHTML);
// 设置拖动效果
e.dataTransfer.effectAllowed = 'move';
// 添加拖动样式
this.classList.add('dragging');
// 记录日志
logOperation('开始拖动任务: ' + this.querySelector('h4').textContent);
});
card.addEventListener('dragend', function(e) {
// 移除拖动样式
this.classList.remove('dragging');
draggedTask = null;
});
});
// 任务列表拖放事件
taskLists.forEach(list => {
list.addEventListener('dragover', function(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
// 添加放置目标样式
this.classList.add('drag-over');
});
list.addEventListener('dragleave', function(e) {
// 移除放置目标样式
this.classList.remove('drag-over');
});
list.addEventListener('drop', function(e) {
e.preventDefault();
// 移除放置目标样式
this.classList.remove('drag-over');
if (draggedTask) {
const oldStatus = draggedTask.closest('.kanban-column').dataset.status;
const newStatus = this.closest('.kanban-column').dataset.status;
if (oldStatus !== newStatus) {
// 移动任务到新列
this.appendChild(draggedTask);
// 更新任务信息
updateTaskInfo(draggedTask, oldStatus, newStatus);
// 记录操作日志
logOperation(
`任务 "${draggedTask.querySelector('h4').textContent}" 从 ${getStatusName(oldStatus)} 移动到 ${getStatusName(newStatus)}`
);
}
}
});
});
// 更新任务信息
function updateTaskInfo(task, oldStatus, newStatus) {
const taskTitle = task.querySelector('h4').textContent;
const taskDesc = task.querySelector('p').textContent;
const assignee = task.querySelector('.assignee').textContent;
const priority = task.querySelector('.priority').textContent;
taskInfo.innerHTML = `
<h3>任务已移动</h3>
<p><strong>任务标题:</strong> ${taskTitle}</p>
<p><strong>任务描述:</strong> ${taskDesc}</p>
<p><strong>负责人:</strong> ${assignee}</p>
<p><strong>优先级:</strong> ${priority}</p>
<p><strong>状态变更:</strong> ${getStatusName(oldStatus)} → ${getStatusName(newStatus)}</p>
<p><strong>更新时间:</strong> ${new Date().toLocaleString()}</p>
`;
}
// 记录操作日志
function logOperation(message) {
const logEntry = document.createElement('div');
logEntry.className = 'log-entry';
logEntry.innerHTML = `
<span class="timestamp">${new Date().toLocaleTimeString()}</span>
<span class="message">${message}</span>
`;
logContent.insertBefore(logEntry, logContent.firstChild);
// 限制日志数量
if (logContent.children.length > 10) {
logContent.removeChild(logContent.lastChild);
}
}
// 获取状态名称
function getStatusName(status) {
const statusNames = {
'todo': '待办事项',
'inprogress': '进行中',
'testing': '测试中',
'done': '已完成'
};
return statusNames[status] || status;
}
});
</script>
</body>
</html><!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>拖放可访问性</title>
</head>
<body>
<h1>拖放功能可访问性</h1>
<!-- 可访问的拖放界面 -->
<section class="accessible-dragdrop">
<h2>可访问的拖放界面</h2>
<!-- 操作说明 -->
<div class="instructions" role="region" aria-labelledby="instructions-title">
<h3 id="instructions-title">操作说明</h3>
<ul>
<li>使用Tab键在可拖动项目和目标区域之间导航</li>
<li>在可拖动项目上按空格键或Enter键激活拖放模式</li>
<li>使用方向键移动到目标区域</li>
<li>按空格键或Enter键完成拖放操作</li>
<li>按Escape键取消拖放操作</li>
</ul>
</div>
<!-- 拖动源 -->
<div class="drag-source" role="region" aria-labelledby="source-title">
<h3 id="source-title">拖动源</h3>
<div class="item"
draggable="true"
tabindex="0"
role="button"
aria-describedby="item1-desc"
data-item-id="1">
<h4>项目1</h4>
<p id="item1-desc">这是第一个可拖动项目</p>
</div>
<div class="item"
draggable="true"
tabindex="0"
role="button"
aria-describedby="item2-desc"
data-item-id="2">
<h4>项目2</h4>
<p id="item2-desc">这是第二个可拖动项目</p>
</div>
</div>
<!-- 拖放目标 -->
<div class="drop-targets" role="region" aria-labelledby="targets-title">
<h3 id="targets-title">拖放目标</h3>
<div class="target"
tabindex="0"
role="button"
aria-label="拖放目标区域A"
data-target="a">
<h4>目标A</h4>
<p>将项目拖放到这里</p>
</div>
<div class="target"
tabindex="0"
role="button"
aria-label="拖放目标区域B"
data-target="b">
<h4>目标B</h4>
<p>将项目拖放到这里</p>
</div>
</div>
<!-- 状态反馈 -->
<div class="status-feedback"
role="status"
aria-live="polite"
aria-atomic="true"
id="status-feedback">
准备拖放操作
</div>
</section>
<!-- 拖放结果 -->
<section class="drag-result">
<h2>拖放结果</h2>
<div id="result-display" role="log" aria-live="polite"></div>
</section>
<script>
// 可访问的拖放实现
document.addEventListener('DOMContentLoaded', function() {
const items = document.querySelectorAll('.item');
const targets = document.querySelectorAll('.target');
const statusFeedback = document.getElementById('status-feedback');
const resultDisplay = document.getElementById('result-display');
let dragMode = false;
let draggedItem = null;
let currentTarget = null;
// 拖动项目事件
items.forEach(item => {
// 鼠标拖放事件
item.addEventListener('dragstart', function(e) {
draggedItem = this;
e.dataTransfer.setData('text/plain', this.dataset.itemId);
updateStatus('开始拖动: ' + this.querySelector('h4').textContent);
});
item.addEventListener('dragend', function(e) {
draggedItem = null;
updateStatus('拖动结束');
});
// 键盘事件
item.addEventListener('keydown', function(e) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
activateDragMode(this);
} else if (e.key === 'Escape' && dragMode) {
e.preventDefault();
deactivateDragMode();
} else if (dragMode) {
handleDragNavigation(e);
}
});
// 焦点事件
item.addEventListener('focus', function() {
if (!dragMode) {
updateStatus('焦点在: ' + this.querySelector('h4').textContent + ',按空格键或Enter键激活拖放模式');
}
});
});
// 拖放目标事件
targets.forEach(target => {
// 鼠标拖放事件
target.addEventListener('dragover', function(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
});
target.addEventListener('drop', function(e) {
e.preventDefault();
const itemId = e.dataTransfer.getData('text/plain');
handleDrop(this, itemId);
});
// 键盘事件
target.addEventListener('keydown', function(e) {
if (dragMode && (e.key === ' ' || e.key === 'Enter')) {
e.preventDefault();
const itemId = draggedItem.dataset.itemId;
handleDrop(this, itemId);
deactivateDragMode();
} else if (e.key === 'Escape' && dragMode) {
e.preventDefault();
deactivateDragMode();
}
});
// 焦点事件
target.addEventListener('focus', function() {
currentTarget = this;
if (dragMode) {
updateStatus('准备拖放到: ' + this.querySelector('h4').textContent + ',按空格键或Enter键完成拖放');
} else {
updateStatus('焦点在目标: ' + this.querySelector('h4').textContent);
}
});
});
// 激活拖放模式
function activateDragMode(item) {
dragMode = true;
draggedItem = item;
item.classList.add('drag-active');
document.body.classList.add('drag-mode');
// 更新所有元素的aria-describedby
items.forEach(i => {
if (i !== item) {
i.setAttribute('aria-describedby', 'drag-mode-desc');
}
});
targets.forEach(t => {
t.setAttribute('aria-describedby', 'drop-target-desc');
});
updateStatus('拖放模式已激活,使用Tab键导航到目标区域,按空格键或Enter键完成拖放,按Escape键取消');
}
// 取消拖放模式
function deactivateDragMode() {
dragMode = false;
if (draggedItem) {
draggedItem.classList.remove('drag-active');
draggedItem.focus();
draggedItem = null;
}
document.body.classList.remove('drag-mode');
// 清除aria-describedby
items.forEach(i => {
i.removeAttribute('aria-describedby');
});
targets.forEach(t => {
t.removeAttribute('aria-describedby');
});
updateStatus('拖放模式已取消');
}
// 处理拖放导航
function handleDragNavigation(e) {
if (e.key === 'Tab') {
// 让Tab键正常工作
return;
}
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
e.preventDefault();
focusNext();
} else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
e.preventDefault();
focusPrevious();
}
}
// 焦点导航
function focusNext() {
const focusableElements = [...items, ...targets];
const currentIndex = focusableElements.indexOf(document.activeElement);
const nextIndex = (currentIndex + 1) % focusableElements.length;
focusableElements[nextIndex].focus();
}
function focusPrevious() {
const focusableElements = [...items, ...targets];
const currentIndex = focusableElements.indexOf(document.activeElement);
const prevIndex = (currentIndex - 1 + focusableElements.length) % focusableElements.length;
focusableElements[prevIndex].focus();
}
// 处理拖放
function handleDrop(target, itemId) {
const itemTitle = document.querySelector(`[data-item-id="${itemId}"] h4`).textContent;
const targetTitle = target.querySelector('h4').textContent;
// 显示结果
const result = document.createElement('div');
result.innerHTML = `
<p><strong>拖放成功!</strong></p>
<p>项目: ${itemTitle}</p>
<p>目标: ${targetTitle}</p>
<p>时间: ${new Date().toLocaleString()}</p>
`;
resultDisplay.appendChild(result);
updateStatus(`拖放成功: ${itemTitle} 已拖放到 ${targetTitle}`);
}
// 更新状态反馈
function updateStatus(message) {
statusFeedback.textContent = message;
}
// 全局键盘事件
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && dragMode) {
e.preventDefault();
deactivateDragMode();
}
});
});
</script>
<!-- 隐藏的描述文本 -->
<div id="drag-mode-desc" style="display: none;">
拖放模式已激活,此项目不可操作
</div>
<div id="drop-target-desc" style="display: none;">
可以在此完成拖放操作
</div>
</body>
</html>A1: 移动设备的拖放支持有限,可以使用touch事件模拟拖放行为,或使用专门的触摸拖放库。
A2: 正确使用preventDefault()和stopPropagation(),并在合适的时机清理事件监听器。
A3: 使用系统剪贴板API或设置适当的数据格式,但要注意浏览器安全限制。
A4: 使用事件委托、虚拟化技术、减少DOM操作频率等方法优化性能。
A5: 保存操作历史,实现状态管理,提供撤销和重做机制。
通过本节的学习,您应该能够熟练使用HTML5拖放API创建直观的用户界面,实现各种拖放功能,并确保良好的用户体验和可访问性。