Clipboard API
The modern Clipboard API provides secure access to clipboard operations, replacing the older document.execCommand() method.
Basic Clipboard Operations
html
<div class="clipboard-demo">
<textarea id="text-input" placeholder="Type something to copy...">Hello, World!</textarea>
<br><br>
<button id="copy-btn">📋 Copy Text</button>
<button id="paste-btn">📄 Paste Text</button>
<button id="clear-btn">🗑️ Clear</button>
<div id="status"></div>
</div>
<script>
const textInput = document.getElementById('text-input');
const copyBtn = document.getElementById('copy-btn');
const pasteBtn = document.getElementById('paste-btn');
const clearBtn = document.getElementById('clear-btn');
const status = document.getElementById('status');
// Copy text to clipboard
copyBtn.addEventListener('click', async () => {
try {
const text = textInput.value;
await navigator.clipboard.writeText(text);
showStatus('✅ Text copied to clipboard!', 'success');
} catch (err) {
showStatus('❌ Failed to copy text: ' + err.message, 'error');
// Fallback for older browsers
fallbackCopy(textInput.value);
}
});
// Paste text from clipboard
pasteBtn.addEventListener('click', async () => {
try {
const text = await navigator.clipboard.readText();
textInput.value = text;
showStatus('✅ Text pasted from clipboard!', 'success');
} catch (err) {
showStatus('❌ Failed to paste text: ' + err.message, 'error');
}
});
// Clear text
clearBtn.addEventListener('click', () => {
textInput.value = '';
showStatus('🗑️ Text cleared', 'info');
});
// Fallback copy method for older browsers
function fallbackCopy(text) {
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
showStatus('✅ Text copied to clipboard! (fallback)', 'success');
} catch (err) {
showStatus('❌ Copy failed', 'error');
}
document.body.removeChild(textArea);
}
// Status display function
function showStatus(message, type = 'info') {
status.textContent = message;
status.className = `status ${type}`;
setTimeout(() => {
status.textContent = '';
status.className = '';
}, 3000);
}
// Check clipboard API support
if (!navigator.clipboard) {
showStatus('⚠️ Clipboard API not supported, using fallback', 'warning');
}
</script>
<style>
.clipboard-demo {
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
margin: 20px 0;
}
#text-input {
width: 100%;
height: 100px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-family: Arial, sans-serif;
}
button {
padding: 10px 15px;
margin: 5px;
border: none;
border-radius: 4px;
cursor: pointer;
background: #007bff;
color: white;
}
button:hover {
background: #0056b3;
}
.status {
margin-top: 10px;
padding: 8px;
border-radius: 4px;
}
.status.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.status.warning {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeaa7;
}
.status.info {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
</style>
Advanced Clipboard Operations
html
<div class="advanced-clipboard">
<h3>Advanced Clipboard Demo</h3>
<!-- Rich text editor -->
<div id="editor" contenteditable="true"
style="border: 1px solid #ccc; padding: 10px; min-height: 100px; margin-bottom: 10px;">
<p>This is <strong>rich text</strong> with <em>formatting</em>!</p>
<p>You can copy and paste <span style="color: blue;">styled content</span>.</p>
</div>
<button id="copy-html">Copy HTML</button>
<button id="paste-html">Paste HTML</button>
<button id="copy-image">Copy Image</button>
<!-- Image display -->
<div id="image-container" style="margin-top: 15px;">
<img id="demo-image" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iIzMwOThkYiIvPjx0ZXh0IHg9IjUwIiB5PSI1NSIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjE0IiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+SW1hZ2U8L3RleHQ+PC9zdmc+"
alt="Demo image" style="max-width: 100px; margin: 10px 0;">
</div>
<div id="advanced-status"></div>
</div>
<script>
const editor = document.getElementById('editor');
const copyHtmlBtn = document.getElementById('copy-html');
const pasteHtmlBtn = document.getElementById('paste-html');
const copyImageBtn = document.getElementById('copy-image');
const demoImage = document.getElementById('demo-image');
const advancedStatus = document.getElementById('advanced-status');
// Copy HTML content
copyHtmlBtn.addEventListener('click', async () => {
try {
const html = editor.innerHTML;
const plainText = editor.textContent;
const clipboardItem = new ClipboardItem({
'text/html': new Blob([html], { type: 'text/html' }),
'text/plain': new Blob([plainText], { type: 'text/plain' })
});
await navigator.clipboard.write([clipboardItem]);
showAdvancedStatus('✅ Rich content copied!', 'success');
} catch (err) {
showAdvancedStatus('❌ Failed to copy HTML: ' + err.message, 'error');
}
});
// Paste HTML content
pasteHtmlBtn.addEventListener('click', async () => {
try {
const clipboardItems = await navigator.clipboard.read();
for (const clipboardItem of clipboardItems) {
// Try to get HTML first, then plain text
if (clipboardItem.types.includes('text/html')) {
const htmlBlob = await clipboardItem.getType('text/html');
const html = await htmlBlob.text();
editor.innerHTML = html;
showAdvancedStatus('✅ Rich content pasted!', 'success');
} else if (clipboardItem.types.includes('text/plain')) {
const textBlob = await clipboardItem.getType('text/plain');
const text = await textBlob.text();
editor.textContent = text;
showAdvancedStatus('✅ Plain text pasted!', 'success');
}
}
} catch (err) {
showAdvancedStatus('❌ Failed to paste: ' + err.message, 'error');
}
});
// Copy image to clipboard
copyImageBtn.addEventListener('click', async () => {
try {
// Convert image to canvas then to blob
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = demoImage.naturalWidth || 100;
canvas.height = demoImage.naturalHeight || 100;
ctx.drawImage(demoImage, 0, 0);
canvas.toBlob(async (blob) => {
try {
const clipboardItem = new ClipboardItem({
[blob.type]: blob
});
await navigator.clipboard.write([clipboardItem]);
showAdvancedStatus('✅ Image copied to clipboard!', 'success');
} catch (err) {
showAdvancedStatus('❌ Failed to copy image: ' + err.message, 'error');
}
});
} catch (err) {
showAdvancedStatus('❌ Failed to process image: ' + err.message, 'error');
}
});
function showAdvancedStatus(message, type) {
advancedStatus.textContent = message;
advancedStatus.className = `status ${type}`;
setTimeout(() => {
advancedStatus.textContent = '';
advancedStatus.className = '';
}, 3000);
}
</script>
Drag and Drop API
The HTML Drag and Drop API enables drag-and-drop functionality for web applications.
Basic Drag and Drop
html
<div class="drag-drop-demo">
<h3>Basic Drag and Drop</h3>
<div class="drag-container">
<div class="draggable-item" draggable="true" data-item="apple">
🍎 Apple
</div>
<div class="draggable-item" draggable="true" data-item="banana">
🍌 Banana
</div>
<div class="draggable-item" draggable="true" data-item="orange">
🍊 Orange
</div>
</div>
<div class="drop-zones">
<div class="drop-zone" data-zone="fruits">
<h4>Fruits Basket</h4>
<div class="zone-content"></div>
</div>
<div class="drop-zone" data-zone="trash">
<h4>🗑️ Trash</h4>
<div class="zone-content"></div>
</div>
</div>
<button id="reset-items">Reset Items</button>
</div>
<style>
.drag-drop-demo {
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
margin: 20px 0;
}
.drag-container {
display: flex;
gap: 10px;
margin: 20px 0;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
}
.draggable-item {
padding: 15px 20px;
background: #ffffff;
border: 2px solid #dee2e6;
border-radius: 8px;
cursor: grab;
user-select: none;
transition: all 0.2s ease;
}
.draggable-item:hover {
background: #e9ecef;
transform: translateY(-2px);
}
.draggable-item.dragging {
opacity: 0.5;
transform: rotate(5deg);
}
.drop-zones {
display: flex;
gap: 20px;
margin: 20px 0;
}
.drop-zone {
flex: 1;
min-height: 150px;
border: 3px dashed #ced4da;
border-radius: 8px;
padding: 15px;
background: #f8f9fa;
transition: all 0.3s ease;
}
.drop-zone.drag-over {
border-color: #007bff;
background: #e3f2fd;
}
.drop-zone h4 {
margin: 0 0 10px 0;
color: #495057;
text-align: center;
}
.zone-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.zone-content .draggable-item {
margin: 0;
}
#reset-items {
padding: 10px 20px;
background: #28a745;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
<script>
let draggedElement = null;
// Get all draggable items and drop zones
const draggableItems = document.querySelectorAll('.draggable-item');
const dropZones = document.querySelectorAll('.drop-zone');
const resetBtn = document.getElementById('reset-items');
// Add drag event listeners to draggable items
draggableItems.forEach(item => {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragend', handleDragEnd);
});
// Add drop event listeners to drop zones
dropZones.forEach(zone => {
zone.addEventListener('dragover', handleDragOver);
zone.addEventListener('dragenter', handleDragEnter);
zone.addEventListener('dragleave', handleDragLeave);
zone.addEventListener('drop', handleDrop);
});
function handleDragStart(e) {
draggedElement = this;
this.classList.add('dragging');
// Set drag data
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.outerHTML);
e.dataTransfer.setData('text/plain', this.textContent);
e.dataTransfer.setData('application/x-item-id', this.dataset.item);
}
function handleDragEnd(e) {
this.classList.remove('dragging');
draggedElement = null;
}
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault();
}
e.dataTransfer.dropEffect = 'move';
return false;
}
function handleDragEnter(e) {
this.classList.add('drag-over');
}
function handleDragLeave(e) {
this.classList.remove('drag-over');
}
function handleDrop(e) {
if (e.stopPropagation) {
e.stopPropagation();
}
this.classList.remove('drag-over');
if (draggedElement) {
// Move the element to the drop zone
const zoneContent = this.querySelector('.zone-content');
const clonedElement = draggedElement.cloneNode(true);
// Add event listeners to the cloned element
clonedElement.addEventListener('dragstart', handleDragStart);
clonedElement.addEventListener('dragend', handleDragEnd);
zoneContent.appendChild(clonedElement);
// Remove original element
draggedElement.remove();
// Special handling for trash zone
if (this.dataset.zone === 'trash') {
setTimeout(() => {
clonedElement.style.animation = 'fadeOut 0.5s ease-out forwards';
setTimeout(() => clonedElement.remove(), 500);
}, 100);
}
}
return false;
}
// Reset functionality
resetBtn.addEventListener('click', () => {
// Clear all drop zones
document.querySelectorAll('.zone-content').forEach(content => {
content.innerHTML = '';
});
// Restore original items
const dragContainer = document.querySelector('.drag-container');
dragContainer.innerHTML = `
<div class="draggable-item" draggable="true" data-item="apple">🍎 Apple</div>
<div class="draggable-item" draggable="true" data-item="banana">🍌 Banana</div>
<div class="draggable-item" draggable="true" data-item="orange">🍊 Orange</div>
`;
// Re-add event listeners
dragContainer.querySelectorAll('.draggable-item').forEach(item => {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragend', handleDragEnd);
});
});
// Add CSS animation for fade out
const style = document.createElement('style');
style.textContent = `
@keyframes fadeOut {
from { opacity: 1; transform: scale(1); }
to { opacity: 0; transform: scale(0.8); }
}
`;
document.head.appendChild(style);
</script>
File Upload with Drag and Drop
html
<div class="file-upload-demo">
<h3>File Upload with Drag & Drop</h3>
<div id="drop-area" class="drop-area">
<div class="upload-icon">📁</div>
<p>Drag and drop files here or <button id="file-select">Browse Files</button></p>
<input type="file" id="file-input" multiple accept="image/*,text/*,.pdf" style="display: none;">
<div class="upload-info">Supported: Images, Text files, PDFs</div>
</div>
<div id="file-list" class="file-list"></div>
<div class="upload-actions">
<button id="upload-files" style="display: none;">Upload Selected Files</button>
<button id="clear-files" style="display: none;">Clear All</button>
</div>
<div class="upload-progress" id="upload-progress" style="display: none;">
<div class="progress-bar">
<div class="progress-fill" id="progress-fill"></div>
</div>
<div class="progress-text" id="progress-text">0%</div>
</div>
</div>
<style>
.file-upload-demo {
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
margin: 20px 0;
}
.drop-area {
border: 3px dashed #ccc;
border-radius: 12px;
padding: 40px;
text-align: center;
background: #fafafa;
transition: all 0.3s ease;
cursor: pointer;
}
.drop-area.drag-over {
border-color: #007bff;
background: #e3f2fd;
transform: scale(1.02);
}
.upload-icon {
font-size: 3em;
margin-bottom: 15px;
}
.drop-area p {
font-size: 1.1em;
color: #666;
margin: 10px 0;
}
#file-select {
background: #007bff;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
.upload-info {
font-size: 0.9em;
color: #999;
margin-top: 10px;
}
.file-list {
margin: 20px 0;
max-height: 300px;
overflow-y: auto;
}
.file-item {
display: flex;
align-items: center;
gap: 15px;
padding: 12px;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin-bottom: 8px;
background: white;
}
.file-icon {
font-size: 1.5em;
min-width: 30px;
}
.file-info {
flex: 1;
}
.file-name {
font-weight: bold;
color: #333;
}
.file-size {
font-size: 0.9em;
color: #666;
}
.file-preview {
max-width: 60px;
max-height: 60px;
border-radius: 4px;
}
.file-remove {
background: #dc3545;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 0.8em;
}
.upload-actions {
margin: 20px 0;
text-align: center;
}
.upload-actions button {
margin: 0 10px;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
#upload-files {
background: #28a745;
color: white;
}
#clear-files {
background: #6c757d;
color: white;
}
.upload-progress {
margin: 20px 0;
}
.progress-bar {
width: 100%;
height: 20px;
background: #e0e0e0;
border-radius: 10px;
overflow: hidden;
margin-bottom: 8px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #007bff, #0056b3);
border-radius: 10px;
width: 0%;
transition: width 0.3s ease;
}
.progress-text {
text-align: center;
font-weight: bold;
color: #333;
}
</style>
<script>
class FileUploader {
constructor() {
this.files = [];
this.initializeElements();
this.addEventListeners();
}
initializeElements() {
this.dropArea = document.getElementById('drop-area');
this.fileInput = document.getElementById('file-input');
this.fileSelect = document.getElementById('file-select');
this.fileList = document.getElementById('file-list');
this.uploadBtn = document.getElementById('upload-files');
this.clearBtn = document.getElementById('clear-files');
this.progressContainer = document.getElementById('upload-progress');
this.progressFill = document.getElementById('progress-fill');
this.progressText = document.getElementById('progress-text');
}
addEventListeners() {
// Drop area events
this.dropArea.addEventListener('dragover', this.handleDragOver.bind(this));
this.dropArea.addEventListener('dragenter', this.handleDragEnter.bind(this));
this.dropArea.addEventListener('dragleave', this.handleDragLeave.bind(this));
this.dropArea.addEventListener('drop', this.handleDrop.bind(this));
this.dropArea.addEventListener('click', () => this.fileInput.click());
// File input events
this.fileInput.addEventListener('change', this.handleFileSelect.bind(this));
this.fileSelect.addEventListener('click', (e) => {
e.stopPropagation();
this.fileInput.click();
});
// Button events
this.uploadBtn.addEventListener('click', this.uploadFiles.bind(this));
this.clearBtn.addEventListener('click', this.clearFiles.bind(this));
}
handleDragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
}
handleDragEnter(e) {
e.preventDefault();
this.dropArea.classList.add('drag-over');
}
handleDragLeave(e) {
e.preventDefault();
this.dropArea.classList.remove('drag-over');
}
handleDrop(e) {
e.preventDefault();
this.dropArea.classList.remove('drag-over');
const files = Array.from(e.dataTransfer.files);
this.addFiles(files);
}
handleFileSelect(e) {
const files = Array.from(e.target.files);
this.addFiles(files);
e.target.value = ''; // Reset input
}
addFiles(newFiles) {
// Filter and validate files
const validFiles = newFiles.filter(file => {
if (file.size > 10 * 1024 * 1024) { // 10MB limit
alert(`File "${file.name}" is too large (max 10MB)`);
return false;
}
// Check if file already exists
const exists = this.files.some(existingFile =>
existingFile.name === file.name && existingFile.size === file.size
);
if (exists) {
alert(`File "${file.name}" already added`);
return false;
}
return true;
});
this.files.push(...validFiles);
this.updateFileList();
this.updateButtons();
}
updateFileList() {
this.fileList.innerHTML = '';
this.files.forEach((file, index) => {
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
const fileIcon = this.getFileIcon(file.type);
const fileSize = this.formatFileSize(file.size);
fileItem.innerHTML = `
<div class="file-icon">${fileIcon}</div>
<div class="file-info">
<div class="file-name">${file.name}</div>
<div class="file-size">${fileSize}</div>
</div>
${file.type.startsWith('image/') ?
`<img class="file-preview" src="${URL.createObjectURL(file)}" alt="Preview">` :
''}
<button class="file-remove" onclick="fileUploader.removeFile(${index})">Remove</button>
`;
this.fileList.appendChild(fileItem);
});
}
getFileIcon(mimeType) {
if (mimeType.startsWith('image/')) return '🖼️';
if (mimeType.startsWith('text/')) return '📄';
if (mimeType === 'application/pdf') return '📕';
if (mimeType.startsWith('video/')) return '🎥';
if (mimeType.startsWith('audio/')) return '🎵';
return '📁';
}
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];
}
removeFile(index) {
this.files.splice(index, 1);
this.updateFileList();
this.updateButtons();
}
updateButtons() {
const hasFiles = this.files.length > 0;
this.uploadBtn.style.display = hasFiles ? 'inline-block' : 'none';
this.clearBtn.style.display = hasFiles ? 'inline-block' : 'none';
}
clearFiles() {
this.files = [];
this.updateFileList();
this.updateButtons();
this.hideProgress();
}
async uploadFiles() {
if (this.files.length === 0) return;
this.showProgress();
this.uploadBtn.disabled = true;
try {
for (let i = 0; i < this.files.length; i++) {
const file = this.files[i];
await this.uploadFile(file);
const progress = ((i + 1) / this.files.length) * 100;
this.updateProgress(progress);
}
alert('All files uploaded successfully!');
this.clearFiles();
} catch (error) {
alert('Upload failed: ' + error.message);
} finally {
this.uploadBtn.disabled = false;
this.hideProgress();
}
}
async uploadFile(file) {
// Simulate file upload with delay
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Uploaded: ${file.name}`);
resolve();
}, 1000);
});
// Real upload implementation would look like:
/*
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/upload', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error('Upload failed');
}
return response.json();
*/
}
showProgress() {
this.progressContainer.style.display = 'block';
this.updateProgress(0);
}
hideProgress() {
this.progressContainer.style.display = 'none';
}
updateProgress(percentage) {
this.progressFill.style.width = percentage + '%';
this.progressText.textContent = Math.round(percentage) + '%';
}
}
// Initialize file uploader
const fileUploader = new FileUploader();
</script>
Advanced Drag and Drop Features
html
<div class="advanced-drag-drop">
<h3>Advanced Drag & Drop Features</h3>
<!-- Sortable list -->
<div class="sortable-container">
<h4>Sortable Task List</h4>
<ul id="sortable-list" class="sortable-list">
<li class="sortable-item" draggable="true">
<span class="drag-handle">⋮⋮</span>
<span>Complete project proposal</span>
</li>
<li class="sortable-item" draggable="true">
<span class="drag-handle">⋮⋮</span>
<span>Review client feedback</span>
</li>
<li class="sortable-item" draggable="true">
<span class="drag-handle">⋮⋮</span>
<span>Update website design</span>
</li>
<li class="sortable-item" draggable="true">
<span class="drag-handle">⋮⋮</span>
<span>Prepare presentation slides</span>
</li>
</ul>
</div>
<!-- Kanban board -->
<div class="kanban-board">
<h4>Kanban Board</h4>
<div class="kanban-columns">
<div class="kanban-column" data-status="todo">
<h5>To Do</h5>
<div class="kanban-cards">
<div class="kanban-card" draggable="true" data-card="1">
<div class="card-title">Design Homepage</div>
<div class="card-meta">Due: Jan 25</div>
</div>
<div class="kanban-card" draggable="true" data-card="2">
<div class="card-title">Write Documentation</div>
<div class="card-meta">Due: Jan 30</div>
</div>
</div>
</div>
<div class="kanban-column" data-status="progress">
<h5>In Progress</h5>
<div class="kanban-cards">
<div class="kanban-card" draggable="true" data-card="3">
<div class="card-title">API Development</div>
<div class="card-meta">Started: Jan 20</div>
</div>
</div>
</div>
<div class="kanban-column" data-status="done">
<h5>Done</h5>
<div class="kanban-cards">
<div class="kanban-card" draggable="true" data-card="4">
<div class="card-title">Setup Development Environment</div>
<div class="card-meta">Completed: Jan 18</div>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.advanced-drag-drop {
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
margin: 20px 0;
}
.sortable-container, .kanban-board {
margin: 20px 0;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
}
.sortable-list {
list-style: none;
padding: 0;
margin: 0;
}
.sortable-item {
display: flex;
align-items: center;
padding: 12px;
margin: 8px 0;
background: white;
border: 1px solid #dee2e6;
border-radius: 6px;
cursor: move;
transition: all 0.2s ease;
}
.sortable-item:hover {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transform: translateY(-1px);
}
.sortable-item.dragging {
opacity: 0.5;
}
.drag-handle {
color: #6c757d;
margin-right: 10px;
cursor: grab;
}
.kanban-columns {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.kanban-column {
background: white;
border-radius: 8px;
padding: 15px;
border: 1px solid #e0e0e0;
}
.kanban-column h5 {
margin: 0 0 15px 0;
padding: 8px;
background: #f1f3f4;
border-radius: 4px;
text-align: center;
color: #333;
}
.kanban-cards {
min-height: 100px;
}
.kanban-card {
background: #fff;
border: 1px solid #d1d5db;
border-radius: 6px;
padding: 12px;
margin: 8px 0;
cursor: move;
transition: all 0.2s ease;
}
.kanban-card:hover {
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.kanban-card.dragging {
opacity: 0.5;
transform: rotate(3deg);
}
.kanban-column.drag-over {
background: #e3f2fd;
border-color: #2196f3;
}
.card-title {
font-weight: bold;
margin-bottom: 6px;
color: #333;
}
.card-meta {
font-size: 0.85em;
color: #666;
}
</style>
<script>
class AdvancedDragDrop {
constructor() {
this.setupSortableList();
this.setupKanbanBoard();
}
setupSortableList() {
const list = document.getElementById('sortable-list');
let draggedItem = null;
list.addEventListener('dragstart', (e) => {
if (e.target.classList.contains('sortable-item')) {
draggedItem = e.target;
e.target.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
}
});
list.addEventListener('dragend', (e) => {
if (e.target.classList.contains('sortable-item')) {
e.target.classList.remove('dragging');
draggedItem = null;
}
});
list.addEventListener('dragover', (e) => {
e.preventDefault();
const afterElement = this.getDragAfterElement(list, e.clientY);
if (afterElement == null) {
list.appendChild(draggedItem);
} else {
list.insertBefore(draggedItem, afterElement);
}
});
}
getDragAfterElement(container, y) {
const draggableElements = [...container.querySelectorAll('.sortable-item:not(.dragging)')];
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child };
} else {
return closest;
}
}, { offset: Number.NEGATIVE_INFINITY }).element;
}
setupKanbanBoard() {
const columns = document.querySelectorAll('.kanban-column');
const cards = document.querySelectorAll('.kanban-card');
let draggedCard = null;
// Add drag events to cards
cards.forEach(card => {
card.addEventListener('dragstart', (e) => {
draggedCard = e.target;
e.target.classList.add('dragging');
e.dataTransfer.setData('text/plain', e.target.dataset.card);
});
card.addEventListener('dragend', (e) => {
e.target.classList.remove('dragging');
draggedCard = null;
});
});
// Add drop events to columns
columns.forEach(column => {
column.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
});
column.addEventListener('dragenter', (e) => {
e.preventDefault();
column.classList.add('drag-over');
});
column.addEventListener('dragleave', (e) => {
if (!column.contains(e.relatedTarget)) {
column.classList.remove('drag-over');
}
});
column.addEventListener('drop', (e) => {
e.preventDefault();
column.classList.remove('drag-over');
if (draggedCard) {
const cardsContainer = column.querySelector('.kanban-cards');
cardsContainer.appendChild(draggedCard);
// Update card status based on column
const newStatus = column.dataset.status;
console.log(`Card ${draggedCard.dataset.card} moved to ${newStatus}`);
}
});
});
}
}
// Initialize advanced drag and drop
const advancedDragDrop = new AdvancedDragDrop();
</script>
Integration Examples
Copy-Paste with Drag-Drop
html
<div class="integration-demo">
<h3>Copy-Paste + Drag-Drop Integration</h3>
<div class="demo-areas">
<div class="content-area">
<h4>Content Creator</h4>
<textarea id="content-input" placeholder="Create some content...">Sample text content</textarea>
<button id="copy-content">📋 Copy Content</button>
</div>
<div class="workspace">
<h4>Workspace</h4>
<div id="workspace-area" class="workspace-area">
<div class="workspace-item" draggable="true">
📝 Draggable Note 1
</div>
<div class="workspace-item" draggable="true">
📝 Draggable Note 2
</div>
</div>
<button id="paste-content">📄 Paste Here</button>
<button id="add-from-clipboard">➕ Add from Clipboard</button>
</div>
</div>
</div>
<style>
.integration-demo {
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
margin: 20px 0;
}
.demo-areas {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.content-area, .workspace {
padding: 15px;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
#content-input {
width: 100%;
height: 100px;
margin-bottom: 10px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
.workspace-area {
min-height: 150px;
border: 2px dashed #ccc;
border-radius: 8px;
padding: 15px;
margin-bottom: 10px;
}
.workspace-item {
display: inline-block;
padding: 8px 12px;
margin: 4px;
background: #e3f2fd;
border: 1px solid #2196f3;
border-radius: 4px;
cursor: move;
}
.workspace-item.dragging {
opacity: 0.5;
}
</style>
<script>
class IntegratedDemo {
constructor() {
this.setupElements();
this.addEventListeners();
}
setupElements() {
this.contentInput = document.getElementById('content-input');
this.copyBtn = document.getElementById('copy-content');
this.pasteBtn = document.getElementById('paste-content');
this.addBtn = document.getElementById('add-from-clipboard');
this.workspace = document.getElementById('workspace-area');
}
addEventListeners() {
this.copyBtn.addEventListener('click', this.copyContent.bind(this));
this.pasteBtn.addEventListener('click', this.pasteContent.bind(this));
this.addBtn.addEventListener('click', this.addFromClipboard.bind(this));
// Drag and drop for workspace items
this.workspace.addEventListener('dragstart', this.handleDragStart.bind(this));
this.workspace.addEventListener('dragover', this.handleDragOver.bind(this));
this.workspace.addEventListener('drop', this.handleDrop.bind(this));
}
async copyContent() {
try {
await navigator.clipboard.writeText(this.contentInput.value);
this.showStatus('Content copied to clipboard!');
} catch (err) {
console.error('Copy failed:', err);
}
}
async pasteContent() {
try {
const text = await navigator.clipboard.readText();
this.contentInput.value = text;
this.showStatus('Content pasted from clipboard!');
} catch (err) {
console.error('Paste failed:', err);
}
}
async addFromClipboard() {
try {
const text = await navigator.clipboard.readText();
if (text.trim()) {
const item = document.createElement('div');
item.className = 'workspace-item';
item.draggable = true;
item.textContent = '📋 ' + text.substring(0, 30) + (text.length > 30 ? '...' : '');
this.workspace.appendChild(item);
this.showStatus('Added item from clipboard!');
}
} catch (err) {
console.error('Add from clipboard failed:', err);
}
}
handleDragStart(e) {
if (e.target.classList.contains('workspace-item')) {
e.target.classList.add('dragging');
e.dataTransfer.setData('text/plain', e.target.textContent);
}
}
handleDragOver(e) {
e.preventDefault();
}
handleDrop(e) {
e.preventDefault();
const draggedItem = this.workspace.querySelector('.dragging');
if (draggedItem) {
draggedItem.classList.remove('dragging');
// Reorder logic could be added here
}
}
showStatus(message) {
// Simple status display
console.log(message);
// You could add a toast notification here
const toast = document.createElement('div');
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #28a745;
color: white;
padding: 10px 15px;
border-radius: 4px;
z-index: 1000;
`;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
}
}
const integratedDemo = new IntegratedDemo();
</script>