⚠️Эта статья является первой подписанной статьей сообщества Nuggets, и её перепечатка без разрешения запрещена.
Дизайнеры на большинстве платформ с низким кодом поддерживают перетаскивание компонентов, что значительно улучшает работу пользователя с дизайном. Другим распространенным сценарием перетаскивания является загрузка файлов. С помощью перетаскивания пользователи могут легко загружать файлы. На самом деле, используя функцию перетаскивания, мы также можемОбмен данными через границы браузера.
так какОбмен данными через границы браузераШерстяная ткань? В этой статье брат Абао представит проект Google с открытым исходным кодом——transmat, вышеуказанные функции могут быть достигнуты с помощью этого проекта. Мало того, этот проект также может помочь нам реализовать некоторые забавные функции, такие как разные ответы на разные высвобождаемые цели.
Давайте сначала почувствуем это через 4 анимации Gif, используйтеtransmatразвитиеВолшебный и веселыйфункция перетаскивания.
Рис. 1 (перетащите перетаскиваемый элемент в редактор форматированного текста)
Рисунок 2 (Перетащите перетаскиваемый элемент в браузер Chrome, другие браузеры также поддерживаются)
Рис. 3 (перетащите перетаскиваемый элемент в пользовательскую цель выпуска)
Рисунок 4 (перетащите перетаскиваемый элемент в Chrome DevTools)
Версия браузера, используемая в приведенном выше примере: Chrome 91.0.4472.114 (официальная версия) (x86_64)
из 4 картинок вышеВсе перетаскиваемые элементы являются одним и тем же элементом, которые имели разные эффекты при размещении на разных высвобождаемых целях. В то же время мы также перешли границы браузеров и реализовали обмен данными. Прочитав вышеприведенные 4 движущиеся картинки, вы не думаете, что это потрясающе.На самом деле, помимо перетаскивания, этот пример также поддерживает операции копирования и вставки.. Однако подробно, как использоватьtransmatПеред реализацией вышеперечисленных функций кратко представимtransmatэта библиотека.
1. Введение в Трансмат
Transmatэто окружениеDataTransferНебольшая библиотека API, которая используетdrag-dropа такжеcopy-pasteВзаимодействие упрощает процесс передачи и получения данных в веб-приложении.DataTransferAPI может передавать множество различных типов данных в другие приложения на устройстве пользователя. API поддерживает следующие распространенные типы данных:text/plain
,text/html
а такжеapplication/json
Ждать.
(Источник изображения:Google.GitHub.IO/внезапно сообразил/)
пониматьtransmatПосле чего рассмотрим сценарии его применения:
- Хотите интегрироваться с внешними приложениями удобным способом.
- Хотите предоставить пользователям возможность обмениваться данными с другими приложениями, даже с теми, о которых вы не знаете.
- Ожидайте, что внешние приложения будут глубоко интегрированы с вашим веб-приложением.
- Хотите, чтобы ваше приложение лучше вписывалось в существующий рабочий процесс пользователя.
теперь ты правtransmatС определенным пониманием разберем, как использоватьtransmatРеализуйте функции, соответствующие вышеуказанным 4 анимациям Gif.
2. Трансмат бой
2.1 transmat-source
html
В следующем коде мыdiv#source
элемент добавленdraggable
Атрибут, этот атрибут используется для определения того, разрешено ли перетаскивание элемента, его значение равноtrue
илиfalse
.
<script src="https://unpkg.com/transmat/lib/index.umd.js"></script>
<div id="source" draggable="true" tabindex="0">大家好,我是阿宝哥</div>
css
#source {
background: #eef;
border: solid 1px rgba(0, 0, 255, 0.2);
border-radius: 8px;
cursor: move;
display: inline-block;
margin: 1em;
padding: 4em 5em;
}
js
const { Transmat, addListeners, TransmatObserver } = transmat;
const source = document.getElementById("source");
addListeners(source, "transmit", (event) => {
const transmat = new Transmat(event);
transmat.setData({
"text/plain": "大家好,我是阿宝哥!",
"text/html": `
<h1>大家好,我是阿宝哥</h1>
<p>聚焦全栈,专注分享 TS、Vue 3、前端架构等技术干货。
<a href="https://juejin.cn/user/764915822103079">访问我的主页</a>!
</p>
<img src="https://sf3-ttcdn-tos.pstatp.com/img/user-avatar/
075d8e781ba84bf64035ac251988fb93~300x300.image" border="1" />
`,
"text/uri-list": "https://juejin.cn/user/764915822103079",
"application/json": {
name: "阿宝哥",
wechat: "semlinker",
},
});
});
В приведенном выше коде мы используемtransmatпредоставляется этой библиотекойaddListeners
Функцияdiv#source
элемент, добавленныйtransmit
прослушиватель событий. В соответствующем обработчике событий мы сначала создалиTransmat
объект, а затем вызватьsetData
Настройки метода разныеMIMEтип данных.
Кратко рассмотрим примеры, использованные вMIMEТипы:
-
text/plain
: Указывает значение по умолчанию для текстовых файлов.Текстовый файл должен быть удобочитаемым и не содержать двоичных данных. -
text/html
: указывает тип файла HTML, некоторые редакторы форматированного текста имеют приоритет передdataTransfer
попасть на объектtext/html
Тип данных, если он не существует, получить его сноваtext/plain
тип данных. -
text/uri-list
: указывает тип ссылки URI. Большинство браузеров сначала считывают этот тип данных. Если найдена действительная ссылка URI, ссылка будет открыта напрямую. Если это недействительная ссылка URI, для Chrome она будет читатьсяtext/plain
тип данных и использовать данные в качестве ключевого слова для поиска контента. -
application/json
:ВыражатьJSONТип, этот тип должен быть знаком фронтенд-разработчикам.
Введениеtransmat-source
После этого давайте взглянем на код реализации пользовательской цели (transmat-target) на рисунке 3.
2.2 transmat-target
html
<script src="https://unpkg.com/transmat/lib/index.umd.js"></script>
<div id="target" tabindex="0">放这里哟!</div>
css
body {
text-align: center;
font: 1.2em Helvetia, Arial, sans-serif;
}
#target {
border: dashed 1px rgba(0, 0, 0, 0.5);
border-radius: 8px;
margin: 1em;
padding: 4em;
}
.drag-active {
background: rgba(255, 255, 0, 0.1);
}
.drag-over {
background: rgba(255, 255, 0, 0.5);
}
js
const { Transmat, addListeners, TransmatObserver } = transmat;
const target = document.getElementById("target");
addListeners(target, "receive", (event) => {
const transmat = new Transmat(event);
// 判断是否含有"application/json"类型的数据
// 及事件类型是否为drop或paste事件
if (transmat.hasType("application/json")
&& transmat.accept()
) {
const jsonString = transmat.getData("application/json");
const data = JSON.parse(jsonString);
target.textContent = jsonString;
}
});
В приведенном выше коде мы используемtransmatпредоставляется этой библиотекойaddListeners
Функцияdiv#target
элемент, добавленныйreceive
прослушиватель событий. Как следует из названия,receive
Событие представляет получение сообщения. В соответствующем обработчике события мы передаемtransmat
объектhasType
метод отфильтрованapplication/json
сообщение, затем черезJSON.parse
метод десериализации для получения соответствующих данных и одновременного преобразования соответствующихjsonString
содержимое отображается вdiv#target
внутри элемента.
На рис. 3, когда мы перетаскиваем перетаскиваемый элемент в настраиваемую цель выпуска, создается эффект выделения, как показано на следующем рисунке:
Этот эффект заключается в использованииtransmatпредоставляется этой библиотекойTransmatObserver
Этот класс может помочь нам реагировать на поведение пользователя при перетаскивании. Конкретное использование выглядит следующим образом:
const obs = new TransmatObserver((entries) => {
for (const entry of entries) {
const transmat = new Transmat(entry.event);
if (transmat.hasType("application/json")) {
entry.target.classList.toggle("drag-active", entry.isActive);
entry.target.classList.toggle("drag-over", entry.isTarget);
}
}
});
obs.observe(target);
первый раз вижуTransmatObserver
После этого Брат Абао сразу же подумал об этом.MutationObserver
API, потому что они обанаблюдательи имеет аналогичный API. использоватьMutationObserver APIМы можем отслеживать изменения в DOM. О любых изменениях в DOM, таких как добавление узлов, сокращение, изменения атрибутов и изменения текстового содержимого, мы можем получать уведомления через этот API. Если вы заинтересованы в API, вы можете прочитатьКто переместил мой DOM?Эта статья.
Теперь мы знаемtransmatКак пользоваться этой библиотекой Далее брат Абао расскажет вам о принципе работы этой библиотеки.
Пример использования Transmat:Transmat Demo
В-третьих, анализ исходного кода Transmat
существуетtransmatСсылка на анализ исходного кода, потому что в предыдущей собственно боевой части мы использовалиaddListeners
,Transmat
,TransmatObserver
Эти три «функции» реализуют основные функции, поэтому мы сосредоточимся на них в следующем анализе исходного кода. Здесь мы сначала анализируемaddListenersфункция.
3.1 функция addListeners
addListenersФункция используется для установки слушателя,После вызова этой функции она вернет функцию для удаления прослушивателя событий.. При анализе функции Brother Abao привык сначала анализировать сигнатуру функции:
// src/transmat.ts
function addListeners<T extends Node>(
target: T,
type: TransferEventType,
listener: (event: DataTransferEvent, target: T) => void,
options = {dragDrop: true, copyPaste: true}
): () => void
Наблюдая за приведенной выше сигнатурой функции, мы можем интуитивно понять ввод и вывод функции. Функция поддерживает следующие 4 параметра:
-
target
: Указывает цель мониторинга, ее типNode
Типы. -
type
: Указывает тип мониторинга, тип параметраTransferEventType
является типом объединения --'transmit' | 'receive'
. -
listener
: представляет прослушиватель событий, который поддерживает такие типы событий, какDataTransferEvent
, который также является типом объединения --DragEvent | ClipboardEvent
, который поддерживает события перетаскивания и события буфера обмена. -
options
: представляет объект конфигурации, который используется для разрешения операций перетаскивания, копирования и вставки.
существуетaddListenersТело функции в основном включает следующие три шага:
- Шаг ①: Согласно
isTransmitEvent
а такжеoptions.copyPaste
Значение для регистрации событий, связанных с буфером обмена. - Шаг ②: Согласно
isTransmitEvent
а такжеoptions.dragDrop
Значение , регистрирует события, связанные с перетаскиванием. - Шаг 3: Верните объект функции, чтобы удалить зарегистрированный прослушиватель событий.
// src/transmat.ts
export function addListeners<T extends Node>(
target: T,
type: TransferEventType, // 'transmit' | 'receive'
listener: (event: DataTransferEvent, target: T) => void,
options = {dragDrop: true, copyPaste: true}
): () => void {
const isTransmitEvent = type === 'transmit';
let unlistenCopyPaste: undefined | (() => void);
let unlistenDragDrop: undefined | (() => void);
if (options.copyPaste) {
// ① 可拖拽源监听cut和copy事件,可释放目标监听paste事件
const events = isTransmitEvent ? ['cut', 'copy'] : ['paste'];
const parentElement = target.parentElement!;
unlistenCopyPaste = addEventListeners(parentElement, events, event => {
if (!target.contains(document.activeElement)) {
return;
}
listener(event as DataTransferEvent, target);
if (event.type === 'copy' || event.type === 'cut') {
event.preventDefault();
}
});
}
if (options.dragDrop) {
// ② 可拖拽源监听dragstart事件,可释放目标监听dragover和drop事件
const events = isTransmitEvent ? ['dragstart'] : ['dragover', 'drop'];
unlistenDragDrop = addEventListeners(target, events, event => {
listener(event as DataTransferEvent, target);
});
}
// ③ 返回函数对象,用于移除已注册的事件监听
return () => {
unlistenCopyPaste && unlistenCopyPaste();
unlistenDragDrop && unlistenDragDrop();
};
}
Мониторинг событий приведенного выше кода, наконец, выполняется путем вызоваaddEventListenersФункция для достижения, внутри функция будет вызываться циклическиaddEventListener
метод добавления прослушивателей событий. Взяв в качестве примера предыдущий пример использования Transmat, внутри соответствующей функции обратного вызова обработки событий мы будем использоватьevent
Объект события является параметром, вызовTransmat
создание конструктораTransmat
пример. Так что же делает этот экземпляр? Чтобы понять, что он делает, нам нужно понятьTransmatДобрый.
3.2 Класс трансмата
Класс Transmat определен вsrc/transmat.ts
файл, конструктор этого класса содержит типDataTransferEvent
параметрыevent
:
// src/transmat.ts
export class Transmat {
public readonly event: DataTransferEvent;
public readonly dataTransfer: DataTransfer;
// type DataTransferEvent = DragEvent | ClipboardEvent;
constructor(event: DataTransferEvent) {
this.event = event;
this.dataTransfer = getDataTransfer(event);
}
}
существуетTransmat
Внутри конструктор также пройдетgetDataTransfer
функция, чтобы получитьDataTransfer
объект и назначьте его внутреннемуdataTransfer
Атрибуты.DataTransfer
Объекты используются для хранения данных во время перетаскивания. Он может содержать один или несколько элементов данных, которые могут относиться к одному или нескольким типам данных.
Давайте взглянемgetDataTransfer
Конкретная реализация функции:
// src/data_transfer.ts
export function getDataTransfer(event: DataTransferEvent): DataTransfer {
const dataTransfer =
(event as ClipboardEvent).clipboardData ??
(event as DragEvent).dataTransfer;
if (!dataTransfer) {
throw new Error('No DataTransfer available at this event.');
}
return dataTransfer;
}
В приведенном выше коде используется нулевой оператор объединения.??
. Особенности этого оператора:Когда левый операнд равен нулю или не определен, возвращается правый операнд, в противном случае возвращается левый операнд.. То есть сначала определите, является ли это событием буфера обмена, и если да, то оно начнется сclipboardData
приобретение собственностиDataTransfer
объект. В противном случае изdataTransfer
приобретение имущества.
Для перетаскиваемых источников после созданияTransmat
объект, мы можем назватьsetData
Метод содержит один или несколько элементов данных. Например, в следующем коде мы устанавливаем разные типы нескольких элементов:
transmat.setData({
"text/plain": "大家好,我是阿宝哥!",
"text/html": `
<h1>大家好,我是阿宝哥</h1>
...
`,
"text/uri-list": "https://juejin.cn/user/764915822103079",
"application/json": {
name: "阿宝哥",
wechat: "semlinker",
},
});
пониматьsetData
После использования метода давайте посмотрим на его конкретную реализацию:
// src/transmat.ts
setData(
typeOrEntries: string | {[type: string]: unknown},
data?: unknown
): void {
if (typeof typeOrEntries === 'string') {
this.setData({[typeOrEntries]: data});
} else {
// 处理多种类型的数据
for (const [type, data] of Object.entries(typeOrEntries)) {
const stringData =
typeof data === 'object' ? JSON.stringify(data) : `${data}`;
this.dataTransfer.setData(normalizeType(type), stringData);
}
}
}
Как видно из приведенного выше кода, вsetData
Внутри метод в конечном итоге вызоветdataTransfer.setData
способ сохранения данных.dataTransfer
объектsetData
Метод поддерживает два параметра строкового типа:format
а такжеdata
. Они представляют собой формат сохраняемых данных и фактические данные соответственно.Если данный формат данных не существует, сохраните соответствующие данные в конец. Если данный формат данных уже существует, старые данные будут заменены новыми данными..
На картинке ниже dataTransfer.setData
Описание совместимости метода, из рисунка видно, что основные современные браузеры поддерживают этот метод.
(Источник изображения:потрите news.com/madonna-api_datang…
В дополнение к классу TransmatsetData
метод, он также содержитgetData
способ получения сохраненных данных.getData
Метод поддерживает параметр типа stringtype
, используемый для указания типа данных. Прежде чем получить данные, он вызоветhasType
Метод определяет, содержит ли он данные этого типа. Если он включен, он пройдетdataTransfer
объектgetData
метод для получения данных, соответствующих типу.
// src/transmat.ts
getData(type: string): string | undefined {
return this.hasType(type)
? this.dataTransfer.getData(normalizeType(type))
: undefined;
}
Кроме того, при вызовеgetData
метод, он также вызоветnormalizeType
функция, для входящегоtype
Параметры типа нормализованы. В частности, следующим образом:
// src/data_transfer.ts
export function normalizeType(input: string) {
const result = input.toLowerCase();
switch (result) {
case 'text':
return 'text/plain';
case 'url':
return 'text/uri-list';
default:
return result;
}
}
Аналогично, давайте посмотрим наdataTransfer.getData
Совместимость методов:
(Источник изображения:потрите news.com/madonna-api_datang…
Хорошо, в классе TransmatsetData
а такжеgetData
Эти два основных метода представлены здесь первыми. Далее мы вводим еще один класс — TransmatObserver.
3.3 Класс TransmatObserver
Роль класса TransmatObserver заключается в том, чтобы помочь нам реагировать на поведение пользователя при перетаскивании, и его можно использовать для выделения области перетаскивания в процессе перетаскивания. Например, в предыдущем примере мы добились выделения области перетаскивания следующим образом:
const obs = new TransmatObserver((entries) => {
for (const entry of entries) {
const transmat = new Transmat(entry.event);
if (transmat.hasType("application/json")) {
entry.target.classList.toggle("drag-active", entry.isActive);
entry.target.classList.toggle("drag-over", entry.isTarget);
}
}
});
obs.observe(target);
Точно так же давайте сначала проанализируем конструктор класса TransmatObserver:
// src/transmat_observer.ts
export class TransmatObserver {
private readonly targets = new Set<Element>(); // 观察的目标集合
private prevRecords: ReadonlyArray<TransmatObserverEntry> = []; // 保存前一次的记录
private removeEventListeners = () => {};
constructor(private readonly callback: TransmatObserverCallback) {}
}
Как видно из приведенного выше кода, конструктор класса TransmatObserver поддерживает типTransmatObserverCallback
параметрыcallback
, соответствующий тип этого параметра определяется следующим образом:
// src/transmat_observer.ts
export type TransmatObserverCallback = (
entries: ReadonlyArray<TransmatObserverEntry>,
observer: TransmatObserver
) => void;
TransmatObserverCallback
Типы функций получают два параметра:entries
а такжеobserver
. вentries
Тип параметра
Массив только для чтения (ReadonlyArray), тип каждого элемента в массивеTransmatObserverEntry
, соответствующие типы определяются следующим образом:
// src/transmat_observer.ts
export interface TransmatObserverEntry {
target: Element;
/** type DataTransferEvent = DragEvent | ClipboardEvent */
event: DataTransferEvent;
/** Whether a transfer operation is active in this window. */
isActive: boolean;
/** Whether the element is the active target (dragover). */
isTarget: boolean;
}
спередиtransmat-targetнапример, при созданииTransmatObserver
экземпляр, экземплярobserve
метод и передать объект для наблюдения.observe
Реализация метода не сложная, а заключается в следующем:
// src/transmat_observer.ts
observe(target: Element) {
/** private readonly targets = new Set<Element>(); */
this.targets.add(target);
if (this.targets.size === 1) {
this.addEventListeners();
}
}
существуетobserve
Внутри метода наблюдаемый элемент будет сохранен вtargets
Setв коллекции. когдаtargets
Когда размер коллекции равен 1, будет вызван текущий экземпляр.addEventListeners
метод добавления прослушивателей событий:
// src/transmat_observer.ts
private addEventListeners() {
const listener = this.onTransferEvent as EventListener;
this.removeEventListeners = addEventListeners(
document,
['dragover', 'dragend', 'dragleave', 'drop'],
listener,
true
);
}
наединеaddEventListeners
Внутри метода мы будем использовать введенный ранееaddEventListeners
функционировать, чтобыdocument
Добавляйте прослушиватели событий, связанные с перетаскиванием, к элементам в пакетах. Соответствующее описание события выглядит следующим образом:
-
dragover
: срабатывает, когда элемент или выделенный текст перетаскиваются на освобождаемую цель; -
dragend
: срабатывает при завершении операции перетаскивания (например, при отпускании кнопки мыши); -
dragleave
: срабатывает, когда перетаскиваемый элемент или выделенный текст покидают освобождаемую цель; -
drop
: Запускается, когда элемент или выделенный текст высвобождаются на освобождаемой цели.
На самом деле, есть не только четыре вышеупомянутых события, связанных с перетаскиванием.Если вас интересуют полные события, вы можете прочитать MDN наAPI перетаскивания HTMLЭта статья. Ниже мы сосредоточимся на анализеonTransferEvent
Прослушиватель событий:
private onTransferEvent = (event: DataTransferEvent) => {
const records: TransmatObserverEntry[] = [];
for (const target of this.targets) {
// 当光标离开浏览器时,对应的事件将会被派发到body或html节点
const isLeavingDrag =
event.type === 'dragleave' &&
(event.target === document.body ||
event.target === document.body.parentElement);
// 页面上是否有拖拽行为发生
// 当拖拽操作结束时触发dragend事件
// 当元素或选中的文本在可释放目标上被释放时触发drop事件
const isActive = event.type !== 'drop'
&& event.type !== 'dragend' && !isLeavingDrag;
// 判断可拖拽的元素是否被拖到target元素上
const isTargetNode = target.contains(event.target as Node);
const isTarget = isActive && isTargetNode
&& event.type === 'dragover';
records.push({
target,
event,
isActive,
isTarget,
});
}
// 仅当记录发生变化的时候,才会调用回调函数
if (!entryStatesEqual(records, this.prevRecords)) {
this.prevRecords = records as ReadonlyArray<TransmatObserverEntry>;
this.callback(records, this);
}
}
В приведенном выше коде используйтеnode.contains(otherNode)
Метод для определения того, перетаскивается ли перетаскиваемый элемент вtarget
на элементе. когдаotherNode
даnode
потомок узла , илиnode
Когда сам узел возвращаетtrue
, иначе возвратfalse
.此外,为了避免频繁地触发回调函数,在调用回调函数前会先调用entryStatesEqual
функция, чтобы определить, изменилась ли запись.entryStatesEqual
Реализация функции относительно проста, а именно:
// src/transmat_observer.ts
function entryStatesEqual(
a: ReadonlyArray<TransmatObserverEntry>,
b: ReadonlyArray<TransmatObserverEntry>
): boolean {
if (a.length !== b.length) {
return false;
}
// 如果有一项不匹配,则立即返回false。
return a.every((av, index) => {
const bv = b[index];
return av.isActive === bv.isActive && av.isTarget === bv.isTarget;
});
}
а такжеMutationObserverТакой же,TransmatObserverТакже предоставляет метод для получения недавно инициированных записей.takeRecords
Способы и способы «отключения» соединенийdisconnect
метод:
// 返回最近已触发记录
takeRecords() {
return this.prevRecords;
}
// 移除所有目标及事件监听器
disconnect() {
this.targets.clear();
this.removeEventListeners();
}
Соответствующее содержание анализа исходного кода Transmat было представлено здесь.Если вы заинтересованы в проекте, вы можете самостоятельно прочитать полный исходный код проекта. В проекте используетсяTypeScriptразработка, началоTypeScriptНебольшие партнеры могут использовать этот проект для консолидации знаний TS и идей объектно-ориентированного проектирования ООП, которые они изучили.
4. Резюме
В этой статье Baoge представил GoogletransmatСценарии приложений, методы использования и соответствующий исходный код проектов с открытым исходным кодом. При анализе исходного кода мы рассмотрели события, связанные с перетаскиванием иDataTransferAPI. Кроме того, мы проанализировалиTransmatObserver
класс, я надеюсь, что, проанализировав класс, выMutationObserverAPI может иметь более глубокое понимание. В то же время, в дальнейшей работе, если вы столкнетесь с подобными сценариями, вы можете обратиться кTransmatObserver
класс для реализации собственногоObserver
Добрый.
Хотя настраиваемые полезные нагрузки (настраиваемые данные JSON) полезны для связи между приложениями, которыми вы управляете, они также ограничивают возможность передачи данных во внешние приложения. Чтобы исправить это, вы можете рассмотреть возможность использования облегченногоJSON-LD (связанные данные)формат данных, соответствующий ему тип MIME'application/ld+json'
. С помощью этого формата данных данные могут быть лучше организованы и связаны для создания лучших веб-приложений. Если вас интересует этот формат данных и вы хотите узнать большеJSON-LD (связанные данные), вы можете прочитать этостатья.