DUICUO

実際にこのようにドラッグ アンド ドロップすることができます。

[[413634]]

ほとんどのローコードプラットフォームデザイナーはドラッグアンドドロップコンポーネント機能をサポートしており、ユーザーのデザインエクスペリエンスを大幅に向上させます。ドラッグアンドドロップのもう一つの一般的なユースケースはファイルのアップロードで、ユーザーは簡単にファイルをアップロードできます。さらに、ドラッグアンドドロップ機能はブラウザ間のデータ共有にも利用できます。

では、ブラウザの境界を越えたデータ共有を実現するにはどうすればよいでしょうか?この記事では、GoogleのオープンソースプロジェクトであるTransmatを紹介します。Transmatは、前述の機能の実現に役立ちます。さらに、このプロジェクトは、リリース可能なターゲットに応じて異なるレスポンスを返すなど、興味深い機能の実装にも役立ちます。

まずは、4 つの GIF を通じて、Transmat を使用して開発された驚くほど楽しいドラッグ アンド ドロップ機能を体験してみましょう。

図1(ドラッグ可能な要素をリッチテキストエディタにドラッグアンドドロップする)


図 2 (ドラッグ可能な要素を Chrome ブラウザにドラッグ アンド ドロップします。他のブラウザもサポートされています)


図3(ドラッグ可能な要素をカスタムリリースターゲットにドラッグ)


図4(ドラッグ可能な要素をChromeデベロッパーツールにドラッグアンドドロップする)


上記の例で使用されているブラウザのバージョンは、Chrome 91.0.4472.114 (リリース済み) (x86_64) です。

上の4つの画像に表示されているドラッグ可能な要素はすべて同じ要素ですが、ドロップ先によって効果が異なります。また、ブラウザの境界を越えることでデータ共有も実現しています。これらの4つのGIF画像を見て、驚かれませんか?実は、ドラッグ&ドロップだけでなく、コピー&ペーストもサポートされています。しかし、上記の機能を実現するためにtransmatを使用する方法を詳しく説明する前に、transmatライブラリについて簡単に説明しておきましょう。

I. トランスマット入門

Transmatは、DataTransfer APIをベースに構築された小さなライブラリです。ドラッグ&ドロップやコピー&ペーストといった操作で、Webアプリケーションにおけるデータの送受信プロセスを簡素化します。DataTransfer APIは、様々なタイプのデータをユーザーのデバイス上の他のアプリケーションに転送できます。APIでサポートされている一般的なデータタイプには、text/plain、text/html、application/jsonなどがあります。

(画像出典:https://google.github.io/transmat/)

Transmat が何であるかがわかったので、その応用シナリオを見てみましょう。

  • 外部アプリケーションと便利な方法で統合したい。
  • 目標は、ユーザーが知らないアプリケーションであっても、他のアプリケーションとデータを共有できるようにすることです。
  • 外部アプリケーションを Web アプリケーションと深く統合したいと考えています。
  • アプリケーションをユーザーの既存のワークフローに適合させたいですか?

Transmat の基本を理解できたので、次に Transmat を使用して上記の 4 つの GIF アニメーションに対応する機能を実現する方法を分析してみましょう。

II. トランスマットの実践

2.1 トランスマットソース

html

以下のコードでは、div#source要素にdraggable属性を追加しました。この属性は要素がドラッグ可能かどうかを示すために使用され、値はtrueまたはfalseのいずれかになります。

  1. <script src= "https://unpkg.com/transmat/lib/index.umd.js" ></script>
  2. <div id= "source" draggable= "true" tabindex= "0" >皆さんこんにちは、私はバオ兄弟です</div>

CSS

  1. #ソース {
  2. 背景: #eef;
  3. 境界線: 実線 1px rgba(0, 0, 255, 0.2);
  4. 境界線の半径: 8px;
  5. カーソル移動;
  6. 表示: インラインブロック;
  7. マージン: 1em;
  8. パディング: 4em 5em;
  9. }

js

  1. const { Transmat、addListeners、TransmatObserver } = transmat;
  2.  
  3. const ソース = document.getElementById( "ソース" );
  4.  
  5. addListeners(ソース、 "送信" 、(イベント) => {
  6. const transmat = 新しい Transmat(イベント);
  7. トランスマット.setData({
  8. "text/plain" : "みなさんこんにちは、私はバオ兄さんです! "
  9. "テキスト/html" : `
  10. みなさんこんにちは。私はバオ兄さんです。
  11. 私たちはフルスタック開発に重点を置き、TS、Vue 3、フロントエンドアーキテクチャ、その他の関連テクノロジーに関する技術的な洞察を共有することに専念しています。
  12. 私のホームページ<a href="https://juejin.cn/user/764915822103079"> をご覧ください!</a>
  13. </p>
  14. <img src="https://sf3-ttcdn-tos.pstatp.com/img/ユーザーアバター/
  15. 075d8e781ba84bf64035ac251988fb93~300x300.image " border=" 1" />
  16. `、
  17. 「テキスト/uriリスト」 : 「https://juejin.cn/user/764915822103079」
  18. 「アプリケーション/json」 : {
  19. 名前 「バオ兄弟
  20. WeChat: 「semlinker
  21. },
  22. });
  23. });

上記のコードでは、`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 を紹介した後、図 3 に示すカスタム ターゲット (transmat-target) の実装コードを見てみましょう。

2.2 トランスマットターゲット

html

  1. <script src= "https://unpkg.com/transmat/lib/index.umd.js" ></script>
  2. <div id= "target" tabindex= "0" >ここに配置してください!</div>

CSS

  1. 体 {
  2. テキスト配置: 中央;
  3. フォント: 1.2em Helvetia、Arial、sans-serif;
  4. }
  5. #ターゲット {
  6. 境界線: 破線 1px rgba(0, 0, 0, 0.5);
  7. 境界線の半径: 8px;
  8. マージン: 1em;
  9. パディング: 4em;
  10. }
  11. .drag-active {
  12. 背景: rgba(255, 255, 0, 0.1);
  13. }
  14. .ドラッグオーバー{
  15. 背景: rgba(255, 255, 0, 0.5);
  16. }

js

  1. const { Transmat、addListeners、TransmatObserver } = transmat;
  2.  
  3. const ターゲット = document.getElementById( "ターゲット" );
  4.  
  5. addListeners(target, "receive" , (イベント) => {
  6. const transmat = 新しい Transmat(イベント);
  7. // データに「application/json」型のデータが含まれているかどうかを確認します
  8. // イベントの種類がドロップイベントか貼り付けイベント
  9. if (transmat.hasType( "application/json" )
  10. && トランスマット.accept()
  11. ){
  12. const jsonString = transmat.getData( "application/json" );
  13. const データ = JSON.parse(jsonString);
  14. target.textContent = jsonString;
  15. }
  16. });

上記のコードでは、`transmat` ライブラリが提供する `addListeners` 関数を使用して、`div#target` 要素に `receive` イベントリスナーを追加しています。名前が示すように、`receive` イベントはメッセージが受信されたことを示します。対応するイベントハンドラーでは、`transmat` オブジェクトの `hasType` メソッドを使用して `application/json` メッセージをフィルタリングし、`JSON.parse` メソッドを使用してデシリアライズして対応するデータを取得し、対応する `jsonString` の内容を `div#target` 要素に表示しています。

図 3 では、ドラッグ可能な要素をカスタム リリース ターゲットにドラッグすると、次の図に示すように強調表示効果が生成されます。


この効果は、Transmatライブラリが提供するTransmatObserverクラスを使用して実現されます。このクラスは、ユーザーのドラッグ&ドロップ操作に応答するのに役立ちます。具体的な使用方法は以下の通りです。

  1. const obs = 新しい TransmatObserver((エントリ) => {
  2. for (constエントリエントリ) {
  3. const transmat = 新しい Transmat(entry.event);
  4. if (transmat.hasType( "application/json" )) {
  5. entry.target.classList.toggle( "drag-active" 、 entry.isActive);
  6. entry.target.classList.toggle( "ドラッグオーバー" 、 entry.isTarget);
  7. }
  8. }
  9. });
  10. obs.observe(ターゲット);

`TransmatObserver` を初めて見たとき、すぐに `MutationObserver` API を思い浮かべました。どちらも似たような API を持つオブザーバーだからです。`MutationObserver` API を使うと、DOM の変更を監視できます。ノードの追加や削除、属性の変更、テキストコンテンツの変更など、DOM へのあらゆる変更をこの API を通じて通知できます。この API にご興味をお持ちの方は、「Who Moved My DOM?」という記事をお読みください。

トランスマット ライブラリの使い方がわかったので、次に、Bao 兄弟がこのライブラリの動作原理の分析を案内します。

Transmatの使用例: Transmatデモ

https://gist.github.com/semlinker/c40baa3d4a0567e555e2e839c84d10dd

III. トランスマットソースコード解析

Transmatソースコード分析セクションでは、前章の実践セクションでコア機能を実装するためにaddListeners、Transmat、TransmatObserverの3つの関数を使用したため、以降のソースコード分析ではこれらの関数に焦点を当てます。ここでは、まずaddListeners関数の分析から始めます。

3.1 addListeners関数

`addListeners`関数はイベントリスナーを設定するために使用されます。この関数を呼び出すと、イベントリスナーを削除する関数が返されます。関数を分析する際、Brother Baoは通常、関数シグネチャの分析から始めます。

  1. // src/transmat.ts
  2. 関数addListeners<T extends Node>(
  3. ターゲット: T,
  4. タイプ: TransferEventType、
  5. リスナー: (イベント: DataTransferEvent、ターゲット: T) => void、
  6. オプション = {ドラッグドロップ: true 、コピーペースト: true }
  7. ): () => 無効

上記の関数シグネチャを観察することで、関数の入出力を直感的に理解できます。この関数は以下の4つのパラメータをサポートしています。

  • target: 監視対象ターゲットを示し、そのタイプはノードです。
  • type: 受信するイベントの種類を示します。このパラメータ TransferEventType の型は、ユニオン型(「transmit」または「receive」)です。
  • listener: これはイベントリスナーを指します。イベントタイプ DataTransferEvent をサポートします。これは DragEvent | ClipboardEvent のユニオン型でもあり、ドラッグイベントとクリップボードイベントをサポートすることを意味します。
  • options: これは、ドラッグ アンド ドロップ、コピー、貼り付け操作を許可するかどうかを設定するために使用される構成オブジェクトを表します。

addListeners 関数本体には、主に次の 3 つのステップが含まれます。

  • ステップ1:isTransmitEventとoptions.copyPasteの値に基づいてクリップボード関連のイベントを登録します。
  • ステップ2:isTransmitEventとoptions.dragDropの値に基づいてドラッグアンドドロップ関連のイベントを登録します。
  • ステップ 3: 登録されたイベント リスナーを削除するために使用される関数オブジェクトを返します。

  1. // src/transmat.ts
  2. エクスポート関数addListeners<T extends Node>(
  3. ターゲット: T,
  4. タイプ: TransferEventType, // '送信' | '受信'  
  5. リスナー: (イベント: DataTransferEvent、ターゲット: T) => void、
  6. オプション = {ドラッグドロップ: true 、コピーペースト: true }
  7. ): () => void {
  8. const isTransmitEvent = type === '送信' ;
  9. unlistenCopyPaste: undefined | (() => void);
  10. unlistenDragDrop: 未定義 | (() => void);
  11.  
  12. if (options.copyPaste) {
  13. // ① ソースをドラッグして切り取りイベントとコピーイベントをリッスンし、ターゲットを解放して貼り付けイベントをリッスンすることができます。
  14. const events = isTransmitEvent ? [ '切り取り' , 'コピー' ] : [ '貼り付け' ];
  15. const 親要素 = target.parentElement!;
  16. unlistenCopyPaste = addEventListeners(parentElement, events, event => {
  17. if (!target. contains (document.activeElement)) {
  18. 戻る;
  19. }
  20. リスナー(イベントDataTransferEvent、ターゲット)。
  21.  
  22. if (event.type === 'コピー' || event.type === 'カット' ) {
  23. イベント.preventDefault();
  24. }
  25. });
  26. }
  27.  
  28. if (options.dragDrop) {
  29. // ② ドラッグ可能なソースは dragstart イベントをリッスンし、dragover イベントとdropイベントをリッスンすることでターゲットを解放できます。
  30. const events = isTransmitEvent ? [ 'dragstart' ] : [ 'dragover' , 'drop' ];
  31. unlistenDragDrop = addEventListeners(ターゲット、イベント、イベント => {
  32. リスナー(イベントDataTransferEvent、ターゲット)。
  33. });
  34. }
  35.  
  36. // ③ 登録されたイベントリスナーを削除するために使用する関数オブジェクトを返します。
  37. 戻り値() => {
  38. unlistenCopyPaste && unlistenCopyPaste();
  39. unlistenDragDrop && unlistenDragDrop();
  40. };
  41. }

上記のコードにおけるイベントリスナーは、最終的には `addEventListeners` 関数を呼び出すことで実装されます。この関数は、イベントリスナーを追加するために `addEventListener` メソッドを繰り返し呼び出します。前述の Transmat の使用例を例に挙げると、対応するイベント処理コールバック関数内で、Transmat コンストラクターを呼び出して、`event` オブジェクトをパラメーターとして持つ Transmat インスタンスを作成します。では、このインスタンスの目的は何でしょうか?その目的を理解するには、Transmat クラスを理解する必要があります。

3.2 トランスマットクラス

Transmat クラスは src/transmat.ts ファイルで定義されており、そのコンストラクターには DataTransferEvent 型のパラメーター イベントがあります。

  1. // src/transmat.ts
  2. エクスポートクラスTransmat {
  3. パブリック読み取り専用イベント: DataTransferEvent;
  4. パブリック読み取り専用データ転送: DataTransfer;
  5.  
  6. // タイプ DataTransferEvent = DragEvent | ClipboardEvent;
  7. コンストラクター(イベント: DataTransferEvent) {
  8. this.event = イベント;
  9. this.dataTransfer = getDataTransfer(イベント);
  10. }
  11. }

Transmatコンストラクタ内では、getDataTransfer関数がDataTransferオブジェクトを取得し、内部のdataTransferプロパティに代入します。DataTransferオブジェクトは、ドラッグ&ドロップ処理中にデータを保存するために使用されます。このオブジェクトには、1つ以上のデータ項目(1つ以上のデータ型)を保存できます。

getDataTransfer 関数の具体的な実装を見てみましょう。

  1. // src/data_transfer.ts
  2. エクスポート関数getDataTransfer(イベント: DataTransferEvent): DataTransfer {
  3. 定数データ転送 =
  4. (イベントClipboardEvent).clipboardData ですか?
  5. (DragEventとしてのイベント).dataTransfer;
  6. if (!データ転送) {
  7. throw new Error( 'このイベントでは DataTransfer は利用できません。' );
  8. }
  9. dataTransfer を返します
  10. }

上記のコードでは、null 結合演算子 `??` を使用しています。この演算子は、左オペランドが null または undefined の場合は右オペランドを返し、そうでない場合は左オペランドを返すという特徴があります。具体的には、まずクリップボードイベントかどうかを確認し、クリップボードイベントの場合は `clipboardData` プロパティから `DataTransfer` オブジェクトを取得します。そうでない場合は `dataTransfer` プロパティから取得します。

ドラッグ可能なソースの場合、Transmatオブジェクトを作成した後、そのオブジェクトのsetDataメソッドを呼び出して、1つ以上のデータ項目を保存できます。例えば、次のコードでは、異なるタイプの複数のデータ項目を設定しています。

  1. トランスマット.setData({
  2. "text/plain" : "みなさんこんにちは、私はバオ兄さんです! "
  3. "テキスト/html" : `
  4. みなさんこんにちは。私はバオ兄さんです。
  5. ...
  6. `、
  7. 「テキスト/uriリスト」 : 「https://juejin.cn/user/764915822103079」
  8. 「アプリケーション/json」 : {
  9. 名前 「バオ兄弟
  10. WeChat: 「semlinker
  11. },
  12. });

setData メソッドの使い方を理解した後、その具体的な実装を見てみましょう。

  1. // src/transmat.ts
  2. データ設定(
  3. typeOrEntries: 文字列 | {[type: 文字列]: 不明},
  4. データ?: 不明
  5. ): 空所 {
  6. if (typeof typeOrEntries === 'string' ) {
  7. this.setData({[typeOrEntries]: data});
  8. }それ以外{
  9. // 複数の種類のデータの処理
  10. Object.entries(typeOrEntries)const [type, data]に対して{
  11. 定数文字列データ =
  12. typeof data === 'object' ? JSON.stringify(data) : `${data}`;
  13. this.dataTransfer.setData(normalizeType(type), stringData);
  14. }
  15. }
  16. }

上記のコードに示すように、`setData` メソッドは最終的に `dataTransfer.setData` メソッドを呼び出してデータを保存します。`dataTransfer` オブジェクトの `setData` メソッドは、`format` と `data` という 2 つの文字列パラメータをサポートしています。これらはそれぞれ、保存するデータ形式と実際のデータを表します。指定されたデータ形式が存在しない場合は、対応するデータが最後に保存されます。指定されたデータ形式が既に存在する場合は、新しいデータで古いデータが置き換えられます。

下の画像は、dataTransfer.setDataメソッドの互換性を示しています。画像からわかるように、ほとんどの最新ブラウザがこのメソッドをサポートしています。

(画像出典:https://caniuse.com/mdn-api_datatransfer_setdata)

`Transmat` クラスには、`setData` メソッドに加えて、保存されたデータを取得するための `getData` メソッドも含まれています。`getData` メソッドは、データ型を表す文字列パラメータ `type` を受け取ります。データを取得する前に、`hasType` メソッドを呼び出して、その型のデータが存在するかどうかを判断します。存在する場合は、`dataTransfer` オブジェクトの `getData` メソッドを使用して、その型の対応するデータを取得します。

  1. // src/transmat.ts
  2. getData(型: 文字列): 文字列 | 未定義 {
  3. this.hasType(type)を返す
  4. ? this.dataTransfer.getData(normalizeType(type))
  5. : 未定義;
  6. }

さらに、`getData` メソッドを呼び出す前に、渡された `type` パラメータを正規化するために `normalizeType` 関数が呼び出されます。具体的な内容は次のとおりです。

  1. // src/data_transfer.ts
  2. エクスポート関数normalizeType(入力: 文字列) {
  3. const 結果 = input.toLowerCase();
  4. スイッチ (結果) {
  5. 場合  '文章'
  6. 戻る  'テキスト/プレーン' ;
  7. 場合  'URL' :
  8. 戻る  'テキスト/uri-list' ;
  9. デフォルト
  10. 結果を返します
  11. }
  12. }

同様に、dataTransfer.getData メソッドの互換性を見てみましょう。

(画像出典: https://caniuse.com/mdn-api_datatransfer_getdata)

さて、これでTransmatクラスの2つのコアメソッド、setDataとgetDataの紹介は終わりです。次は、TransmatObserverという別のクラスを紹介します。

3.3 TransmatObserver クラス

`TransmatObserver` クラスは、ユーザーのドラッグ&ドロップ操作に応答し、ドラッグ処理中に配置領域をハイライト表示するのに役立ちます。例えば、前の例では、配置領域のハイライト表示を次のように実装しました。

  1. const obs = 新しい TransmatObserver((エントリ) => {
  2. for (constエントリエントリ) {
  3. const transmat = 新しい Transmat(entry.event);
  4. if (transmat.hasType( "application/json" )) {
  5. entry.target.classList.toggle( "drag-active" 、 entry.isActive);
  6. entry.target.classList.toggle( "ドラッグオーバー" 、 entry.isTarget);
  7. }
  8. }
  9. });
  10. obs.observe(ターゲット);

同様に、まず TransmatObserver クラスのコンストラクターを分析してみましょう。

  1. // src/transmat_observer.ts
  2. エクスポートクラス TransmatObserver {
  3. private readonly targets = new Set <Element>(); // 監視するターゲットのセット
  4. private prevRecords: ReadonlyArray<TransmatObserverEntry> = []; // 前のレコードを保存します
  5. プライベートremoveEventListeners = () => {};
  6.  
  7. コンストラクター(プライベート読み取り専用コールバック: TransmatObserverCallback) {}
  8. }

上記のコードからわかるように、TransmatObserver クラスのコンストラクターは、TransmatObserverCallback 型の callback と呼ばれるパラメーターをサポートしており、このパラメーターの型定義は次のとおりです。

  1. // src/transmat_observer.ts
  2. エクスポート型 TransmatObserverCallback = (
  3. エントリ: ReadonlyArray<TransmatObserverEntry>,
  4. オブザーバー: TransmatObserver
  5. => 無効;

`TransmatObserverCallback` 関数型は、`entries` と `observer` の2つのパラメータを受け入れます。`entries` パラメータは `[]` 型です。

読み取り専用配列。各項目は TransmatObserverEntry 型で、次の型定義を持ちます。

  1. // src/transmat_observer.ts
  2. エクスポートインターフェース TransmatObserverEntry {
  3. ターゲット: 要素;
  4. /** タイプ DataTransferEvent = DragEvent | ClipboardEvent */
  5. イベント: DataTransferEvent;
  6. /** このウィンドウ転送操作アクティブかどうか。 */
  7. isActive: ブール値;
  8. /** 要素がアクティブターゲット(ドラッグオーバー)あるかどうか。 */
  9. isTarget: ブール値;
  10. }

前述の `transmat-target` の例では、`TransmatObserver` インスタンスを作成した後、その `observe` メソッドが呼び出され、監視対象のオブジェクトが渡されます。`observe` メソッドの実装は、以下に示すようにそれほど複雑ではありません。

  1. // src/transmat_observer.ts
  2. 観察(ターゲット: 要素) {
  3. /** プライベート読み取り専用ターゲット = 新しいSet <Element>(); */
  4. this.targets.add (ターゲット);
  5. if ( this.targets.size === 1) {
  6. this.addEventListeners();
  7. }
  8. }

`observe` メソッド内では、監視対象の要素が `targetsSet` コレクションに保存されます。`targetsSet` のサイズが 1 になると、現在のインスタンスの `addEventListeners` メソッドが呼び出され、イベントリスナーが追加されます。

  1. // src/transmat_observer.ts
  2. プライベートaddEventListeners() {
  3. const リスナー = this.onTransferEvent をEventListenerとして定義します。
  4. this.removeEventListeners = addEventListeners(
  5. 書類
  6. [ 'dragover' 'dragend' 'dragleave' 'drop' ],
  7. リスナー
  8. 真実 
  9. );
  10. }

プライベートメソッド `addEventListeners` 内では、先ほど紹介した `addEventListeners` 関数を使用して、ドラッグ&ドロップ関連のイベントリスナーをドキュメント要素に一括で追加します。対応するイベントの説明は次のとおりです。

  • dragover: 要素または選択したテキストが解放可能なターゲットにドラッグされたときにトリガーされます。
  • dragend: ドラッグ操作が終了したときにトリガーされます (例: マウス ボタンが放されたとき)。
  • dragleave: 要素または選択されたテキストが解放可能なターゲットからドラッグされたときにトリガーされます。
  • drop: 要素または選択されたテキストが解放可能なターゲット上で解放されたときにトリガーされます。

実際には、ドラッグ&ドロップに関連するイベントは上記4つだけではありません。イベントの完全なリストにご興味がおありでしたら、MDNの記事「HTMLドラッグ&ドロップAPI」をご覧ください。以下では、`onTransferEvent`イベントリスナーの分析に焦点を当てます。

  1. プライベート onTransferEvent = (イベント: DataTransferEvent) => {
  2. 定数レコード: TransmatObserverEntry[] = [];
  3. for ( this.targetsconst ターゲット) {
  4. // カーソルがブラウザから離れると、対応するイベントが body または html ノードに送信されます。
  5. const isLeavingDrag =
  6. event.type === 'dragleave' &&
  7. (イベント.ターゲット === ドキュメント.本文 ||
  8. event.target === document.body.parentElement);
  9.  
  10. // ページ上でドラッグ動作が発生していますか?
  11. // ドラッグ操作が終了すると、dragend イベントがトリガーされます。
  12. // 要素または選択されたテキストがリリース可能なターゲット上でリリースされると、ドロップイベントがトリガーされます。
  13. const isActive = event.type !== 'ドロップ'  
  14. && イベント.タイプ !== 'ドラジェンド' && !isLeavingDrag;
  15.  
  16. // ドラッグ可能な要素がターゲット要素にドラッグされているかどうかを判断します。
  17. const isTargetNode = target.contains (event.targetNodeとして格納)。
  18. const isTarget = isActive && isTargetNode
  19. && イベント.type === 'dragover' ;
  20.  
  21. records.push({
  22. ターゲット、
  23. イベント、
  24. アクティブ、
  25. isTarget、
  26. });
  27. }
  28.      
  29. // コールバック関数はレコードが変更された場合にのみ呼び出されます。
  30. if (!entryStatesEqual(レコード、this.prevRecords)) {
  31. this.prevRecords = records をReadonlyArray<TransmatObserverEntry>として保存します。
  32. this.callback(レコード、this);
  33. }
  34. }

上記のコードでは、`node.contains(otherNode)` メソッドを使用して、ドラッグ可能な要素が `target` 要素にドラッグされたかどうかを判定しています。`otherNode` が `node` の子孫ノード、または `node` 自身である場合は `true` を返し、それ以外の場合は `false` を返します。さらに、コールバック関数の頻繁な呼び出しを避けるため、コールバック関数を呼び出す前に `entryStatesEqual` 関数を呼び出して、レコードが変更されたかどうかを確認します。`entryStatesEqual` 関数の実装は、以下に示すように比較的シンプルです。

  1. // src/transmat_observer.ts
  2. 関数entryStatesEqual(
  3. a: ReadonlyArray<TransmatObserverEntry>,
  4. b: 読み取り専用配列<TransmatObserverEntry>
  5. ): ブール値 {
  6. もし (a.長さ !== b.長さ) {
  7. 戻る 間違い;
  8. }
  9. // いずれかの項目が一致しない場合は、直ちにfalseを返します
  10. a.every((av, index ) => {を返します。
  11. const bv = b[インデックス];
  12. return av.isActive === bv.isActive && av.isTarget === bv.isTarget;
  13. });
  14. }

MutationObserver と同様に、TransmatObserver も、最近トリガーされたレコードを取得するための takeRecords メソッドと、接続を「切断」するための disconnect メソッドを提供します。

  1. // 最後にトリガーされたレコードに戻る
  2. レコードを取得する() {
  3. this.prevRecordsを返します
  4. }
  5.  
  6. // すべてのターゲットとイベントリスナーを削除します
  7. 切断() {
  8. this.targets.clear();
  9. this.removeEventListeners();
  10. }

Transmatのソースコードの分析はこれで終了です。このプロジェクトにご興味をお持ちいただけましたら、完全なソースコードをご自身でご覧いただけます。このプロジェクトはTypeScriptを使用して開発されており、既にTypeScriptを学習されている方は、このプロジェクトを通じて、TSの知識とOOPのオブジェクト指向設計原則を定着させることができます。

IV. 要約

この記事では、Google Transmatオープンソースプロジェクトの適用シナリオ、使用方法、および関連ソースコードを紹介します。ソースコード分析セクションでは、ドラッグ&ドロップ関連のイベントとDataTransfer APIについて確認しました。さらに、ユーザーのドラッグ&ドロップ操作への応答に役立つTransmatObserverクラスを分析しました。このクラスを分析することで、MutationObserver APIへの理解が深まることを願っています。今後、同様のシナリオに遭遇した場合は、TransmatObserverクラスを参考にして独自のObserverクラスを実装してください。

カスタムペイロード(カスタムJSONデータ)は、管理するアプリケーション間の通信に便利ですが、外部アプリケーションへのデータ転送能力にも制限があります。この問題に対処するには、MIMEタイプが「application/ld+json」である軽量なJSON-LD(Linked Data)データ形式の使用を検討してください。このデータ形式は、データの整理とリンクをより適切に行えるため、より優れたWebアプリケーションの構築につながります。このデータ形式に興味があり、JSON-LD(Linked Data)についてさらに詳しく知りたい場合は、こちらの記事をご覧ください。