Две мощные браузерные возможности для работы с данными: Clipboard API позволяет читать и писать в буфер обмена, а Drag and Drop API — перетаскивать элементы интерфейса и файлы.
Современный асинхронный API для работы с буфером обмена. Требует разрешения пользователя.
Запись текста:
await navigator.clipboard.writeText('Привет!')
console.log('Скопировано в буфер')Чтение текста:
const text = await navigator.clipboard.readText()
console.log('Из буфера:', text)Запись произвольных данных:
const item = new ClipboardItem({
'text/plain': new Blob(['Текст'], { type: 'text/plain' }),
'text/html': new Blob(['<b>Жирный</b>'], { type: 'text/html' }),
})
await navigator.clipboard.write([item])Чтение произвольных данных:
const items = await navigator.clipboard.read()
for (const item of items) {
for (const type of item.types) {
const blob = await item.getType(type)
const text = await blob.text()
console.log(`${type}: ${text}`)
}
}clipboard-write — запись в буфер (разрешено автоматически для активных вкладок)clipboard-read — чтение из буфера (требует явного разрешения пользователя)Проверить разрешение:
const { state } = await navigator.permissions.query({ name: 'clipboard-read' })
// 'granted', 'denied', 'prompt'Делаем элемент перетаскиваемым:
<div draggable="true" id="item">Перетащи меня</div>Основные события:
element.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', 'payload data')
e.dataTransfer.effectAllowed = 'move'
})
dropZone.addEventListener('dragover', (e) => {
e.preventDefault() // обязательно! иначе drop не сработает
e.dataTransfer.dropEffect = 'move'
})
dropZone.addEventListener('drop', (e) => {
e.preventDefault()
const data = e.dataTransfer.getData('text/plain')
console.log('Получено:', data)
})// Установка данных разных типов
e.dataTransfer.setData('text/plain', 'текст')
e.dataTransfer.setData('text/html', '<b>html</b>')
e.dataTransfer.setData('application/json', JSON.stringify({ id: 1 }))
// Чтение данных
const text = e.dataTransfer.getData('text/plain')
const types = e.dataTransfer.types // ['text/plain', 'text/html', ...]
// Для файлов
const files = e.dataTransfer.files // FileListdropZone.addEventListener('drop', async (e) => {
e.preventDefault()
const files = [...e.dataTransfer.files]
for (const file of files) {
console.log(file.name, file.size, file.type)
const text = await file.text()
}
})dropZone.addEventListener('dragenter', () => dropZone.classList.add('drag-over'))
dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over'))
dropZone.addEventListener('drop', () => dropZone.classList.remove('drag-over'))document.addEventListener('copy', (e) => {
e.clipboardData.setData('text/plain', 'перехвачено!')
e.preventDefault() // отменяем стандартное копирование
})
document.addEventListener('paste', (e) => {
const text = e.clipboardData.getData('text/plain')
console.log('Вставлено:', text)
})Симуляция операций с буфером обмена и drag-and-drop передачи данных между зонами
// Симуляция Clipboard и Drag & Drop без браузера
class SimulatedClipboard {
constructor() {
this._buffer = null
this._permissions = { read: 'granted', write: 'granted' }
}
async writeText(text) {
if (this._permissions.write !== 'granted') throw new Error('Нет разрешения')
this._buffer = { type: 'text/plain', data: text }
console.log(`[Clipboard] Записано: "${text}"`)
}
async readText() {
if (this._permissions.read !== 'granted') throw new Error('Нет разрешения')
if (!this._buffer) return ''
return this._buffer.data
}
async write(items) {
this._buffer = items[0]
console.log(`[Clipboard] Записан объект типа: ${items[0].type}`)
}
}
class SimulatedDataTransfer {
constructor() {
this._data = new Map()
this.effectAllowed = 'all'
this.dropEffect = 'none'
}
setData(type, data) { this._data.set(type, data) }
getData(type) { return this._data.get(type) || '' }
get types() { return [...this._data.keys()] }
}
class SimulatedDragDropBoard {
constructor() {
this._dropZones = new Map()
this._currentDrag = null
this._history = []
}
registerDropZone(id) {
this._dropZones.set(id, { id, items: [] })
console.log(`[DnD] Зона зарегистрирована: #${id}`)
}
startDrag(sourceId, data) {
if (!this._dropZones.has(sourceId)) {
console.log(`[DnD] Источник #${sourceId} не найден`)
return
}
const transfer = new SimulatedDataTransfer()
transfer.setData('text/plain', typeof data === 'string' ? data : JSON.stringify(data))
transfer.setData('application/json', JSON.stringify({ source: sourceId, data }))
this._currentDrag = { sourceId, transfer, data }
console.log(`[DnD] Начало перетаскивания из #${sourceId}: ${JSON.stringify(data)}`)
}
drop(targetId) {
if (!this._currentDrag) {
console.log('[DnD] Нет активного перетаскивания')
return null
}
if (!this._dropZones.has(targetId)) {
console.log(`[DnD] Зона #${targetId} не найдена`)
return null
}
const { sourceId, data, transfer } = this._currentDrag
const zone = this._dropZones.get(targetId)
zone.items.push(data)
this._history.push({ from: sourceId, to: targetId, data })
this._currentDrag = null
console.log(`[DnD] Сброшено в #${targetId}: ${JSON.stringify(data)}`)
return transfer.getData('text/plain')
}
getHistory() { return [...this._history] }
getZoneItems(id) { return this._dropZones.get(id)?.items || [] }
}
// Демо
const clipboard = new SimulatedClipboard()
const board = new SimulatedDragDropBoard()
// Clipboard
await clipboard.writeText('Привет, буфер обмена!')
const text = await clipboard.readText()
console.log('Прочитано из буфера:', text)
// Drag & Drop
board.registerDropZone('list-a')
board.registerDropZone('list-b')
board.registerDropZone('trash')
console.log('\n--- Перетаскивание элементов ---')
board.startDrag('list-a', { id: 1, label: 'Задача 1' })
board.drop('list-b')
board.startDrag('list-a', { id: 2, label: 'Задача 2' })
board.drop('trash')
board.startDrag('list-b', { id: 3, label: 'Задача 3' })
board.drop('list-a')
console.log('\n--- История перемещений ---')
board.getHistory().forEach(({ from, to, data }) => {
console.log(` ${from} → ${to}: ${JSON.stringify(data)}`)
})
console.log('\nЭлементы в list-b:', board.getZoneItems('list-b'))Две мощные браузерные возможности для работы с данными: Clipboard API позволяет читать и писать в буфер обмена, а Drag and Drop API — перетаскивать элементы интерфейса и файлы.
Современный асинхронный API для работы с буфером обмена. Требует разрешения пользователя.
Запись текста:
await navigator.clipboard.writeText('Привет!')
console.log('Скопировано в буфер')Чтение текста:
const text = await navigator.clipboard.readText()
console.log('Из буфера:', text)Запись произвольных данных:
const item = new ClipboardItem({
'text/plain': new Blob(['Текст'], { type: 'text/plain' }),
'text/html': new Blob(['<b>Жирный</b>'], { type: 'text/html' }),
})
await navigator.clipboard.write([item])Чтение произвольных данных:
const items = await navigator.clipboard.read()
for (const item of items) {
for (const type of item.types) {
const blob = await item.getType(type)
const text = await blob.text()
console.log(`${type}: ${text}`)
}
}clipboard-write — запись в буфер (разрешено автоматически для активных вкладок)clipboard-read — чтение из буфера (требует явного разрешения пользователя)Проверить разрешение:
const { state } = await navigator.permissions.query({ name: 'clipboard-read' })
// 'granted', 'denied', 'prompt'Делаем элемент перетаскиваемым:
<div draggable="true" id="item">Перетащи меня</div>Основные события:
element.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', 'payload data')
e.dataTransfer.effectAllowed = 'move'
})
dropZone.addEventListener('dragover', (e) => {
e.preventDefault() // обязательно! иначе drop не сработает
e.dataTransfer.dropEffect = 'move'
})
dropZone.addEventListener('drop', (e) => {
e.preventDefault()
const data = e.dataTransfer.getData('text/plain')
console.log('Получено:', data)
})// Установка данных разных типов
e.dataTransfer.setData('text/plain', 'текст')
e.dataTransfer.setData('text/html', '<b>html</b>')
e.dataTransfer.setData('application/json', JSON.stringify({ id: 1 }))
// Чтение данных
const text = e.dataTransfer.getData('text/plain')
const types = e.dataTransfer.types // ['text/plain', 'text/html', ...]
// Для файлов
const files = e.dataTransfer.files // FileListdropZone.addEventListener('drop', async (e) => {
e.preventDefault()
const files = [...e.dataTransfer.files]
for (const file of files) {
console.log(file.name, file.size, file.type)
const text = await file.text()
}
})dropZone.addEventListener('dragenter', () => dropZone.classList.add('drag-over'))
dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over'))
dropZone.addEventListener('drop', () => dropZone.classList.remove('drag-over'))document.addEventListener('copy', (e) => {
e.clipboardData.setData('text/plain', 'перехвачено!')
e.preventDefault() // отменяем стандартное копирование
})
document.addEventListener('paste', (e) => {
const text = e.clipboardData.getData('text/plain')
console.log('Вставлено:', text)
})Симуляция операций с буфером обмена и drag-and-drop передачи данных между зонами
// Симуляция Clipboard и Drag & Drop без браузера
class SimulatedClipboard {
constructor() {
this._buffer = null
this._permissions = { read: 'granted', write: 'granted' }
}
async writeText(text) {
if (this._permissions.write !== 'granted') throw new Error('Нет разрешения')
this._buffer = { type: 'text/plain', data: text }
console.log(`[Clipboard] Записано: "${text}"`)
}
async readText() {
if (this._permissions.read !== 'granted') throw new Error('Нет разрешения')
if (!this._buffer) return ''
return this._buffer.data
}
async write(items) {
this._buffer = items[0]
console.log(`[Clipboard] Записан объект типа: ${items[0].type}`)
}
}
class SimulatedDataTransfer {
constructor() {
this._data = new Map()
this.effectAllowed = 'all'
this.dropEffect = 'none'
}
setData(type, data) { this._data.set(type, data) }
getData(type) { return this._data.get(type) || '' }
get types() { return [...this._data.keys()] }
}
class SimulatedDragDropBoard {
constructor() {
this._dropZones = new Map()
this._currentDrag = null
this._history = []
}
registerDropZone(id) {
this._dropZones.set(id, { id, items: [] })
console.log(`[DnD] Зона зарегистрирована: #${id}`)
}
startDrag(sourceId, data) {
if (!this._dropZones.has(sourceId)) {
console.log(`[DnD] Источник #${sourceId} не найден`)
return
}
const transfer = new SimulatedDataTransfer()
transfer.setData('text/plain', typeof data === 'string' ? data : JSON.stringify(data))
transfer.setData('application/json', JSON.stringify({ source: sourceId, data }))
this._currentDrag = { sourceId, transfer, data }
console.log(`[DnD] Начало перетаскивания из #${sourceId}: ${JSON.stringify(data)}`)
}
drop(targetId) {
if (!this._currentDrag) {
console.log('[DnD] Нет активного перетаскивания')
return null
}
if (!this._dropZones.has(targetId)) {
console.log(`[DnD] Зона #${targetId} не найдена`)
return null
}
const { sourceId, data, transfer } = this._currentDrag
const zone = this._dropZones.get(targetId)
zone.items.push(data)
this._history.push({ from: sourceId, to: targetId, data })
this._currentDrag = null
console.log(`[DnD] Сброшено в #${targetId}: ${JSON.stringify(data)}`)
return transfer.getData('text/plain')
}
getHistory() { return [...this._history] }
getZoneItems(id) { return this._dropZones.get(id)?.items || [] }
}
// Демо
const clipboard = new SimulatedClipboard()
const board = new SimulatedDragDropBoard()
// Clipboard
await clipboard.writeText('Привет, буфер обмена!')
const text = await clipboard.readText()
console.log('Прочитано из буфера:', text)
// Drag & Drop
board.registerDropZone('list-a')
board.registerDropZone('list-b')
board.registerDropZone('trash')
console.log('\n--- Перетаскивание элементов ---')
board.startDrag('list-a', { id: 1, label: 'Задача 1' })
board.drop('list-b')
board.startDrag('list-a', { id: 2, label: 'Задача 2' })
board.drop('trash')
board.startDrag('list-b', { id: 3, label: 'Задача 3' })
board.drop('list-a')
console.log('\n--- История перемещений ---')
board.getHistory().forEach(({ from, to, data }) => {
console.log(` ${from} → ${to}: ${JSON.stringify(data)}`)
})
console.log('\nЭлементы в list-b:', board.getZoneItems('list-b'))Реализуй createDragDropBoard() с методами: registerDropZone(id) регистрирует зону для сброса, startDrag(data) начинает перетаскивание с данными (запоминает текущие данные), drop(zoneId) завершает перетаскивание и возвращает данные (или null если нет активного перетаскивания), getHistory() возвращает массив объектов { from: undefined, to: zoneId, data }.
activeDrag хранит объект { data }. В drop() проверяй !activeDrag для возврата null. После успешного drop сохраняй в history и устанавливай activeDrag = null. getHistory() возвращает копию [...history]. zones.has(zoneId) проверяет что зона зарегистрирована.