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 カーネル デバイス ドライバー システム コールに関する注意事項

推薦する

デカルト積原理を使用してMySQLで複数のテーブルをクエリする方法を簡単に説明します。

MySQL マルチテーブルクエリ (直積原理)まず、データが使用するテーブルを決定します。デカルト...

MySQL主キー命名戦略関連

最近、データライフサイクル管理の詳細を整理していたときに、小さな問題を発見しました。それは、MySQ...

Dockerでイメージを削除する方法

dockerでイメージを削除するコマンドはdocker rmiですが、このコマンドを実行してもイメー...

Linux システムで Tomcat のポート 80 を使用する方法

アプリケーションシナリオ多くの場合、Linux サーバーに tomcat や nginx などのソフ...

nginx アンチホットリンクおよびアンチクローラー設定の詳細な説明

新しい設定ファイルを作成します (たとえば、nginx インストール ディレクトリの下の conf ...

MySQLテクノロジーにおけるInnoDBロックの詳細な説明

目次序文1. ロックとは何ですか? 2. InnoDBストレージエンジンのロック2.1 ロックの種類...

TypeScript を使用して Vue3 で axios をカプセル化する詳細な例

この axios パッケージは、vue3 デモで使用されます。便宜上、element-plus は ...

UbuntuにCMakeをインストールするいくつかの方法の詳細な説明

CMakeをインストール sudo apt をインストール cmake この方法はインストールが簡単...

DockerにRocketMQをインストールするための実装手順

目次1. 画像を取得する2. ブローカーサーバーを作成する3. ブローカーを作成する4. Rocke...

MySQL における単一テーブルと複数テーブル、およびビューと一時テーブルに対する Update と Select の違い

1. テーブルAのデータを使用してMySQLのテーブルBの内容を更新するたとえば、データ テーブル内...

MySQL データベース接続例外の概要 (収集する価値あり)

Centos にプロジェクトをデプロイするときに奇妙な問題が見つかりました。データベース接続で例外...

VUE+CanvasはシンプルなGobangゲームの全プロセスを実現します

序文レイアウトの点では、Gobang はランダムな動きを目的とするゲームよりも実装がはるかに簡単で、...

Redmine の Docker インストール手順

イメージをダウンロードします(オプションの手順です。省略した場合は、手順 3 と 4 で自動的にイン...

Dockerはbusyboxを使用してベースイメージを作成します

Docker イメージの最初の行は FROM alpine などのイメージで始まりますが、最初のベー...

JSはスネークゲームを実装する

目次1. 初期化構造2. 蛇の色のレンダリング3. ヘビの動き4. ヘビの死を判定する方法 ヘビの死...