Reactドラッグフックを実装するための100行以上のコード

Reactドラッグフックを実装するための100行以上のコード

序文

ソースコードは合計で 100 行強しかありません。これを読めば、react-dnd などの成熟した react ドラッグ ライブラリの実装アイデアを大まかに理解でき、これらのライブラリをすぐに使い始めることができます。

フックを使用した場合の一般的な効果は次のとおりです。

私たちの目標は、useDrag フックと useDrop フックを実装することです。これにより、要素を簡単にドラッグ可能にすることができ、以下に示すように、ドラッグの各ライフサイクルでメッセージの配信をカスタマイズできます (ちなみに、ドラッグによってトリガーされるいくつかのイベントを導入します)。

  • dragstart: ユーザーがドラッグを開始すると、ドラッグされたノードでトリガーされます。このイベントのターゲット属性は、ドラッグされたノードです。
  • dragenter: 現在のノードにドラッグすると、現在のノードで 1 回トリガーされ、イベントのターゲット属性は現在のノードになります。通常、このイベントのリスニング関数で、ドラッグされたデータを現在のノードにドロップできるようにするかどうかを指定する必要があります。現在のノードにイベントのリスナー関数がない場合、またはリスナー関数が何の操作も実行しない場合は、現在のノードでデータをドロップできないことを意味します。現在のノードへのドラッグの視覚的な表示も、このイベントのリスニング関数で設定されます。
  • dragover: 現在のノードの上にドラッグすると、現在のノードで継続的にトリガーされ (数百ミリ秒間隔で)、イベントのターゲット属性は現在のノードになります。このイベントと dragenter イベントの違いは、dragenter イベントはノードに入るときにトリガーされ、その後、ノードを離れない限り dragover イベントがトリガーされ続けることです。
  • dragleave: ドラッグ操作が現在のノード範囲から外れると、現在のノードでトリガーされます。このイベントのターゲット属性は現在のノードです。現在のノードでのドラッグ アンド ドロップ操作を視覚的に表示する場合は、このイベントのリスナー関数で設定します。

使い方+ソースコード解説

クラスHelloはReact.Component<any, any>を拡張します。
 コンストラクタ(props: any) {
  スーパー(小道具)
  this.state = {}
 }

 与える() {
  戻る (
   <ドラッグアンドドロップ>
    <ドラッグ要素 />
    <ドロップ要素 />
   </ドラッグアンドドロップ>
  )
 }
}

ReactDOM.render(<Hello />, window.document.getElementById("root"))

前述の通り、DragAndDrop コンポーネントの機能は、現在ドラッグされている要素がどの DOM であるかなどのメッセージを、useDrag と useDrop を使用するすべてのコンポーネントに渡すことですが、必要に応じて他の情報を追加することもできます。その実装を見てみましょう。

DragAndDropManager を React.createContext に追加します。
const DragAndDrop = ({ children }) => (
 <DragAndDropContext.Provider 値 = {{ DragAndDropManager: new DragAndDropManager() }}>
  {子供たち}
 </ドラッグアンドドロップコンテキスト.プロバイダー>
)

メッセージパッシングは react の Context API を使用して実装されていることがわかります。この DragAndDropManager に注目してください。実装を見てみましょう。

デフォルトクラスDragAndDropManagerをエクスポートします。

 コンストラクタ() {
  this.active = null
  this.subscriptions = []
  this.id = -1
 }

 setActive(アクティブプロパティ) {
  this.active = アクティブプロパティ
  this.subscriptions.forEach((サブスクリプション) => subscription.callback())
 }

 subscribe(コールバック) {
  this.id += 1
  this.subscriptions.push({
   折り返し電話、
   id: this.id,
  })

  this.idを返す
 }

 登録解除(id) {
  this.subscriptions = this.subscriptions.filter((sub) => sub.id !== id)
 }
}

setActive の機能は、現在ドラッグされている要素を記録することです。これは useDrag で使用されます。useDrag のフックの実装を見ると、setActive メソッドを呼び出してドラッグされた DOM 要素を渡すだけで、現在ドラッグされている要素がわかることがわかります。

さらに、イベントをサブスクライブするための API subscribe も追加しました。まだ使用していないため、この例では無視してかまいません。サブスクリプション イベントを追加できることだけ知っておいてください。

次に、useDrag の使い方を見てみましょう。DragElement の実装は次のとおりです。

関数DragElement() {
 定数入力 = useRef(null)
 const hanleDrag = useDrag({
  参照: 入力、
  collection: {}, // ここでドロップ要素に渡したいメッセージを入力できます。このメッセージは後でドロップ要素にパラメータとして渡されます})
 戻る (
  <div ref={入力}>
   <h1 role="button" onClick={handleDrag}>
    ドラッグ要素
  </div>
 )
}

非常にシンプルなuseDragの実装を見てみましょう。

デフォルト関数 useDrag(props) をエクスポートします。

 const { DragAndDropManager } = useContext(DragAndDropContext)
 
 定数handleDragStart = (e) => {
  DragAndDropManager.setActive(props.collection)
  e.dataTransfer !== 未定義の場合 {
   e.dataTransfer.effectAllowed = "移動"
   e.dataTransfer.dropEffect = "移動"
   e.dataTransfer.setData("text/plain", "drag") // Firefox の修正
  }
  (props.onDragStart)の場合{
   props.onDragStart(DragAndDropManager.active)
  }
 }
 
 使用効果(() => {
  もしprops.refがそうであれば、戻り値は()=>{}
  定数{
   参照: { 現在の },
  } = 小道具
  if (現在) {
   current.setAttribute("ドラッグ可能", true)
   current.addEventListener("dragstart", handleDragStart)
  }
  戻り値 () => {
   current.removeEventListener("dragstart", handleDragStart)
  }
 }, [props.ref.current])

 handleDragStart を返す
}

useDrag が行うことは非常にシンプルです。

  • まず、useContext を使用して、最も外側のストア (上記のコードでは DragAndDropManager) のデータを取得します。
  • useEffect では、外部から ref が渡されると、DOM 要素の draggable 属性が true に設定され、ドラッグ可能になります。
  • 次に、この要素に dragstart イベントをバインドします。メモリ リークを防ぐために、コンポーネントを破棄するときにイベントを削除する必要があることに注意してください。
  • handleDragStart イベントは、まず外部から外部ストアに渡された props.collection を更新します。これにより、ドラッグされる各要素は、useDrag で渡された useDrag({collection: {}}) 情報を、DragAndDropManager.setActive(props.collection) を介して外部ストアに渡すことができます。
  • 次に、dataTransder 属性を使用して、要素のドラッグ属性を move に設定し、Firefox と互換性を持たせます。
  • 最後に、ドラッグ イベントがトリガーされるたびに、外部からの onDragStart イベントもトリガーされ、ストア内のデータが渡されます。

このうち、useDrop の使用と DropElement の実装は次のとおりです。

関数 DropElement(props: any): any {
 定数入力 = useRef(null)
 ドロップを使用します({
  参照: 入力、
  // e は、dragOver イベントが発生したときにドラッグされている要素のイベント オブジェクトを表します // collection はストアに保存されているデータです // showAfter は、マウスが要素をドラッグしているときに、ドロップされた要素の上をマウスが通過するかどうかを示します (上は上半分、下は下半分)
  onDragOver: (e, コレクション, showAfter) => {
  // 上半分を通過すると、ドロップ要素の上部の境界線が赤くなります if (!showAfter) {
    input.current.style = "border-bottom: none;border-top: 1px の赤一色"
   } それ以外 {
    // 下半分を通過すると、ドロップ要素の上部の境界線は赤になります input.current.style = "border-top: none; border-bottom: 1px solid red"
   }
  },
  // ドロップ要素上でマウスを離すと、スタイルがクリアされます onDrop: () => {
   入力.現在のスタイル = ""
  },
  // ドロップ要素を離れるとスタイルはクリアされます onDragLeave: () => {
   入力.現在のスタイル = ""
  },
 })
 戻る (
  <div>
   <h1 ref={input}>要素をドロップ</h1>
  </div>
 )
}

最後に、useDropの実装を見てみましょう。

デフォルト関数 useDrop(props) をエクスポートします。
// 最も外側のストアのデータを取得します。const { DragAndDropManager } = useContext(DragAndDropContext)
 定数handleDragOver = (e) => {
 // e はドラッグ イベント オブジェクトです e.preventDefault()
  // 下の getBoundingClientRect の図を参照してください const overElementHeight = e.currentTarget.getBoundingClientRect().height / 2
  定数 overElementTopOffset = e.currentTarget.getBoundingClientRect().top
  // clientYはマウスからブラウザページの表示領域の上部までの距離です。const mousePositionY = e.clientY
  // mousePositionY - overElementTopOffset は、要素内のマウスから要素の border-top までの距離です。const showAfter = mousePositionY - overElementTopOffset > overElementHeight
  (props.onDragOver)の場合{
   props.onDragOver(e, DragAndDropManager.active, showAfter)
  }
 }
 // ドロップイベント const handledDop = (e: React.DragEvent) => {
  e.preventDefault()

  (props.onDrop)の場合{
   props.onDrop(DragAndDropManager.active)
  }
 }
 // dragLeave イベント const handlerragLeave = (e: React.DragEvent) => {
  e.preventDefault()

  (props.onDragLeave)の場合{
   props.onDragLeave(DragAndDropManager.active)
  }
 }
  // イベントを登録します。メモリリークを避けるために、コンポーネントを破棄するときにはイベントの登録を解除する必要があることに注意してください。useEffect(() => {
  もしprops.refがそうであれば、戻り値は()=>{}
  定数{
   参照: { 現在の },
  } = 小道具
  if (現在) {
   current.addEventListener("dragover", handleDragOver)
   current.addEventListener("ドロップ"、handledDop)
   current.addEventListener("dragleave", 処理済みragLeave)
  }
  戻り値 () => {
   current.removeEventListener("dragover", handleDragOver)
   current.removeEventListener("drop", 処理されたDop)
   current.removeEventListener("dragleave", 処理済みragLeave)
  }
 }, [props.ref.current])
}

GetBoundingClientRect API ダイアグラム:

rectObject = object.getBoundingClientRect();

rectObject.top: 要素の上端からウィンドウの上端までの距離。

rectObject.right: 要素の右側からウィンドウの左側までの距離。

rectObject.bottom: 要素の下部からウィンドウの上部までの距離。

rectObject.left: 要素の左側からウィンドウの左側までの距離。

これで、100 行を超えるコードで React ドラッグ アンド ドロップ フックを実装する方法に関するこの記事は終了です。React ドラッグ アンド ドロップ フックに関するより関連性の高いコンテンツについては、123WORDPRESS.COM で以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • ドラッグ可能で編集可能なガントチャートの詳細な説明(HighchartsはVueとReactで使用できます)
  • Typescript+React でモバイルと PC でシンプルなドラッグ アンド ドロップ効果を実現
  • react-beautiful-dnd はコンポーネントのドラッグ アンド ドロップ機能を実装します
  • react-beautiful-dnd を使用してリスト間のドラッグ アンド ドロップを実装する
  • React.js コンポーネントはドラッグ アンド ドロップによるソート コンポーネント機能のプロセス分析を実装します
  • ドラッグアンドドロップ機能を実装するReactサンプルコード
  • React.js コンポーネントはドラッグ アンド ドロップ コピーとソート可能なサンプル コードを実装します
  • React.jsがネイティブjsドラッグエフェクトを実装することで発生する一連の問題についてもう一度話しましょう
  • React.js をベースにしたネイティブ js ドラッグ エフェクトの実装に関する考察
  • Reactはシンプルなドラッグアンドドロップ機能を実装します

<<:  mysql5.7 でユーザーの初期パスワードを変更する方法

>>:  Linux カーネル デバイス ドライバー システム コールに関する注意事項

推薦する

Alipay の新しいホームページのフロントエンドの実践的な概要

もちろん、ページ パフォーマンスの最適化に関する個人的な経験も含まれています。ここでいくつかの点につ...

配列をフィルタリングするJavaScript

この記事では、配列フィルタリングを実装するためのJavaScriptの具体的なコードを参考までに紹介...

MySQL 数値型オーバーフローの処理方法

さて、質問させてください。MySQL で列を int(0) に設定すると何が起こりますか?この問題を...

HTML でさまざまなスペースの特徴と表現を探る (推奨)

I. 概要HTML テンプレートを作成するときに、テキスト レイアウトの手段としてスペースが使用さ...

JS でカルーセル効果を実現する 3 つの簡単な方法

この記事では、JSカルーセル効果の具体的なコードを実現するための3つの方法を紹介します。具体的な内容...

DeepinでPyenvをインストールする手順

序文これまでは、/bin/ ディレクトリのソフトリンクを変更して Python のバージョンを切り替...

HTTP サーバーとクライアントのやり取りをシミュレートする Node.js+postman

目次1. NodeがHTTPサーバーを構築する2. HTTPサーバーがリクエストを取得する1. Po...

MySQL のインデックス障害の一般的なシナリオと回避方法

序文これまでにも、一部の SQL ステートメントを不適切に使用すると MySQL インデックスが失敗...

HTML テーブルタグチュートリアル (25): 垂直配置属性 VALIGN

垂直方向では、行の配置を上、中央、下に設定できます。基本的な構文<TR VALIGN=&quo...

Windows 10 での MySQL 8.0.12 解凍バージョンのインストール グラフィック チュートリアル

この記事は、MySQL 8.0.12解凍版のインストールグラフィックチュートリアルを記録しています。...

MySQL 8.0.12 のインストールと設定方法のグラフィックチュートリアル (Windows 版)

1. はじめにプロジェクトではMySQLを使用しています。インターネット上の例を参考にインストール...

CSS3で実装された水平ヘッダーメニュー

結果:実装コードhtml <nav class="dropdownmenu"...

クリーンで美しいウェブデザインのための4つの原則

この記事では、 Webデザインに関連するこれら4 つの原則について説明します。これら4 つの原則を念...

CSS の Flex レイアウトを使用してシンプルな縦棒グラフを作成する方法

以下は、Flex レイアウトを使用した棒グラフです。 HTML: <div class=&qu...

MAC 上の MySQL の初期パスワードを忘れた場合の対処方法

MACでMySQLの初期パスワードを忘れた場合の解決策を参考までに共有します。具体的な内容は次のとお...