ガイド
EffDNDは、属性だけでHTMLにドラッグ&ドロップ機能を追加します。JavaScript呼び出しは不要で、インポートするだけで設定も完了します。このガイドでは、EffDNDで何ができるのか、そしてどのように使うのかを解説します。
はじめに
npm i effdndアプリに一度インポートしてください:
import 'effdnd'; // ライブラリ
import 'effdnd/style.css'; // オプション: デフォルトスタイル以上です。以下の属性でマークされた要素は、後からページに追加された要素も含め、すぐにドラッグ可能になります。
フレームワーク: どのフレームワークでも動作します。コンポーネントラッパーは不要です。
動作原理
EffDNDは一つのコアアイデアに基づいて構築されており、このガイドの他の部分はすべてその拡張です。
- ドラッグを開始するのは
triggerのみです。 要素自体はドラッグできません。data-dnd-triggerでマークされたハンドルを掴むとドラッグが開始されます。トリガーがない場合、何も動きません。 - 実際に動くのは
itemです。 トリガーは通常、要素の内部に存在するため、要素全体がポインターに追従しますが、同じノードである必要はありません。 scopeはサンドボックスです。itemは、そのscope内でのみ移動およびドロップできます。スコープ外のドロップゾーンやコンテナは無視されます。
これは基本ケースです。スコープ内のアイテムに対するトリガーです。
<div data-dnd-scope="board"> <!-- スコープ: サンドボックス -->
<div data-dnd-item="a"> <!-- アイテム: 何が動くか -->
<span data-dnd-trigger>⠿</span> <!-- トリガー: 掴んだもの -->
私をつかんで
</div>
</div>以下に挙げるすべての機能(並べ替え、移動、スクロール、または下記のトリガーパラメータ)は、このコア動作を洗練または拡張するものです。
ドラッグ中のスタイリング 移動している要素は、
itemのクローン(コピー)であり、 アイテムの 自身の親 に挿入されます。つまり、カスケードされたスタイルや継承されたスタイル、 親セレクタ(ul > li、.zone .cardなど)に対して記述されたルールは、移動するクローンにも適用されます。 すべての色をインライン化する必要はありません。元のアイテムはリスト内の所定の位置に留まり、passive状態によって暗くなります(下記の スタイリング を参照)。注意点 - 変換された祖先要素 クローンは
position:fixedであるため、 レイアウトから切り離されます。ただし、アイテムの親要素にtransform、filter、perspective(またはwill-change:transform) が設定されている場合、ブラウザはfixedをabsoluteのように扱います。クローンは その親要素に対して相対的に配置され、ずれて表示される可能性があります (また、スクロール可能な変換済みコンテナと共にスクロールします)。ドラッグしたアイテムの変換済み親要素を避けるか、data-dnd-stateとアイテムに適用するインラインスタイルを使用して、fixed-visualスタイルを上書きしてください。
属性の概要
| 属性 | 機能 |
|---|---|
data-dnd-trigger | ユーザーがドラッグする「ハンドル」をマークします |
data-dnd-item | ドラッグされる要素をマークします |
data-dnd-scope | ドラッグとドロップのターゲットを1つのコンテナに制限します |
data-dnd-reorder | コンテナをソート可能なリスト(x または y)に変換します |
data-dnd-target | ドロップゾーンをマークします |
data-dnd-transfer | ドロップゾーンにアクション(append、prepend、remove)を設定します |
data-dnd-scroll | ドラッグ中にコンテナを自動スクロールします |
data-dnd-transition | 移動アニメーションを滑らかにします |
data-dnd-disabled | トリガーを無効にします |
data-dnd-state | ドラッグ中に設定されるランタイム状態(active/passive) - スタイル設定に使用されます |
最も基本的なケースでは、コンテナ(scope)内の要素(item)に対するハンドル(trigger)を指定するだけで十分です。その他のパラメータはすべてオプションであり、動作を追加するものです。
トリガーパラメータ
「トリガー」はエントリ ポイントであるため、最も頻繁に調整する属性です。セミコロンで区切られたパラメータのリストを受け入れます。
<span data-dnd-trigger="dist:12;axis:y;scope:board;item:task-1">⠿</span>ドラッグ距離(dist)を指定してください。
クリック時の意図しないドラッグを防ぎます。ポインターが指定されたピクセル数だけ移動した後でのみドラッグが実行されます。
<span data-dnd-trigger="dist:12">⠿</span> <!-- ポインターが12px移動した後にのみドラッグします -->1つの軸(axis)にロックする
<span data-dnd-trigger="axis:x">⠿</span> <!-- 水平方向のみ -->
<span data-dnd-trigger="axis:y">⠿</span> <!-- 垂直方向のみ -->特定のコンテナ(scope)を指定する
デフォルトでは、EffDNDはDOMを調べて最も近いscopeを見つけます。特定のscopeが必要な場合は、名前を付けてください。
<span data-dnd-trigger="scope:board">⠿</span>特定のアイテム(item)を指定する
トリガーは、その上にある特定のアイテムを移動対象として指定できます。
<span data-dnd-trigger="scope:board;item:task-1">⠿</span>ドロップ先を制限する(target)
data-dnd-targetが指定された名前で始まるゾーンへのドロップのみを許可する:
<span data-dnd-trigger="scope:board;target:drop">⠿</span>ページ全体を自由に移動する(scope:*)
特別な値scope:*は、scopeの境界を完全に無視します。
<span data-dnd-trigger="scope:*">⠿</span>その後、アイテムはページ上のどこにでもドラッグできます。
トリガーを無効にする
<span data-dnd-trigger data-dnd-disabled>⠿</span>再度有効にするには、属性を削除してください。
一般的なシナリオ
実際のインターフェースで各要素が組み合わされる一般的な例を以下に示します。
並べ替え:ソート可能なリスト
アイテムを data-dnd-reorder="y" でリストにラップし、各アイテムにマークを付け、ハンドルを追加します。
<ul data-dnd-scope="todo" data-dnd-reorder="y">
<li data-dnd-item="1"><span data-dnd-trigger>⠿</span>Buy milk</li>
<li data-dnd-item="2"><span data-dnd-trigger>⠿</span>Read a book</li>
</ul>yは縦方向に、xは横方向にソートします。- リストの直接の子である項目のみが並べ替えられます。
転送:コンテナ間でアイテムを移動します。
コンテナをドロップゾーンとして指定し、転送アクションを設定します。
<div data-dnd-scope="sprint">
<div class="zone" data-dnd-target="backlog" data-dnd-transfer="append">Backlog</div>
<div class="zone" data-dnd-target="done" data-dnd-transfer="prepend">Done</div>
</div>append— アイテムはゾーンの末尾に追加されます。prepend— アイテムはゾーンの先頭に追加されます。remove— アイテムは削除されます(ゴミ箱などに最適です)。
ドロップゾーンは同じscope内に存在する必要があります(またはscope:*を使用してください。上記を参照)。
組み合わせる:並べ替え可能なリストとゴミ箱
<div data-dnd-scope="mailbox">
<ul data-dnd-reorder="y">
<li data-dnd-item="1"><span data-dnd-trigger>⠿</span>Invoice</li>
<li data-dnd-item="2"><span data-dnd-trigger>⠿</span>Newsletter</li>
</ul>
<button data-dnd-target="trash" data-dnd-transfer="remove">Delete</button>
</div>ここではリストが自動的にソートされ、ボタンにドラッグ&ドロップすることで不要な行を削除できます。
カンバン:列とカード
ボードに列をソートするためのdata-dnd-reorder="x"を設定し、各列のカードエリアに垂直方向の並べ替えゾーンと転送レシーバーの両方を設定します。
<div data-dnd-scope="kanban" data-dnd-reorder="x"> <!-- 列をソートします -->
<div class="col" data-dnd-item="col-1">
<div class="col-head"><span data-dnd-trigger>⠿</span>Backlog</div>
<div class="cards" data-dnd-reorder="y"
data-dnd-target="col-1" data-dnd-transfer="append"> <!-- カードの仕分けと受け入れ -->
<div class="card" data-dnd-item="task-1"><span data-dnd-trigger>⠿</span>Write spec</div>
</div>
</div>
<!-- その他の列... -->
</div>列ヘッダーをドラッグして列の順序を変更します。 カードをドラッグして列間を移動します。
一般的な動作のチューニング
長いコンテナを自動スクロールする
data-dnd-scroll をスクロール可能な要素にアタッチします。
<div data-dnd-scroll="threshold:50;speed:12">threshold(デフォルト値30) — スクロールを開始する端からの距離 (ピクセル単位)。speed(デフォルト値10) — スクロール速度 (1フレームあたりのピクセル数)。
端の近くをドラッグするとコンテナがスクロールし、端に近づくほどスクロール速度が速くなります。
アニメーションを滑らかにする
<div data-dnd-transition="150ms ease">…</div>デフォルト 100ms linear.
JavaScriptからのドラッグ操作への反応
もちろん可能です。EffDNDでは、この機能をオプションとして利用できます。以下の各関数は、購読解除関数を返します。
import { onDrag, onDrop, onReorder, onTransfer, onDragStart, onDragEnd } from 'effdnd';
onReorder((event) => {
console.log('Item was reordered:', event.detail.keys.item);
});利用可能なイベント: effdragstart、effdrag、effdragend、effdragenter、effdragleave、effdrop、effreorder、efftransfer。
各イベントは、信頼できる detail オブジェクトを提供します。
event.detail.keys.item; // どのアイテム
event.detail.keys.scope; // どのスコープコンテナ内
event.detail.keys.target; // どのターゲットに対して
event.detail.item;役立つかもしれないヘルパー
import { getItem, getScope, getTargets, getReorderContainer, reset } from 'effdnd';
getItem(trigger); // 要素が属する `item`
getScope(trigger); // 要素が属するスコープ
getTargets(trigger); // 利用可能な`target`要素
reset(item); // アイテムを元の位置に戻すdata-dnd-state によるスタイリング
ドラッグ中、EffDND は関連する要素にランタイム属性 data-dnd-state を付与します。これにより、アクティブなドラッグをスタイリングするための簡潔なフックが提供され、インライン JavaScript は不要になります。何もドラッグされていないときは、data-dnd-state は存在しません。
状態値
| 要素 | 状態 | 意味 |
|---|---|---|
data-dnd-item | active | 移動中のクローン — ポインターに追従している要素 |
data-dnd-item | passive | 元のアイテムはその場に残され、クローンの背後で暗く表示されます |
data-dnd-scope | active | ポインターがスコープの境界外にある |
data-dnd-scope | passive | ポインターがスコープ内にある — ドラッグ中の通常状態 |
data-dnd-target | active | 現在ホバーしているドロップゾーン |
data-dnd-target | passive | ドロップを受け入れる準備ができている有効なドロップゾーン |
同梱されているindex.cssには、これらのセレクタに基づいて構築された洗練されたデフォルト設定が既に含まれており、それらを上書きするには同じセレクタを使用します。
/* index.css からデフォルト値を設定 */
[data-dnd-item][data-dnd-state="passive"] { opacity: 0.2; }
[data-dnd-item][data-dnd-state="active"] { opacity: 0.75; z-index: 1000; }
/* 独自のテーマ */
[data-dnd-item][data-dnd-state="passive"] { opacity: 0.08; }
[data-dnd-item][data-dnd-state="active"] {
opacity: 1;
box-shadow: 0 0 0 2px cornflowerblue;
border-radius: 8px;
}
[data-dnd-scope][data-dnd-state="active"] { outline: 2px dashed tomato; }
[data-dnd-target][data-dnd-state="active"] { background: rgba(100, 200, 255, 0.2); }デフォルト設定は index.css にあるため、それらを読み込むのは オプション です。インポートをスキップして独自の [data-dnd-state="…"] ルールを作成すれば、EffDND は完全に依存関係のない状態を維持し、視覚的な制御を完全に行うことができます。
ヒント 元の要素は削除されるのではなく、opacity によって暗く表示されます。そのため、リストのレイアウトは維持され、
ドラッグ中に兄弟要素がジャンプすることはありません。元の要素を完全に非表示にしたい場合は、
passive状態にopacity: 0(またはvisibility: hidden) を設定してください。
ミニリファレンス
すべての属性とそのパラメータを簡潔にまとめたものです。
| 属性 / パラメータ | 値 | デフォルト値 | 目的 |
|---|---|---|---|
data-dnd-trigger | — | — | ドラッグを開始するハンドルをマークします |
… dist | 数値 | — | ポインタが指定されたピクセル数だけ移動した後にのみドラッグします |
… axis | x / y | both | ドラッグを1つの軸に固定します |
… scope | 名前 / * | nearest in DOM | 特定のコンテナを使用します(またはスコープを無視します) |
… item | 名前 | nearest in DOM | 特定のアイテムをドラッグします |
… target | 名前 | all | 名前に一致するターゲットへのドロップのみを許可します |
data-dnd-disabled | — | — | トリガーを無効にします |
data-dnd-item | 一意の名前 | — | ドラッグされる要素をマークします |
data-dnd-scope | 一意の名前 | — | ドラッグ/ドロップを1つのコンテナに制限します |
data-dnd-reorder | x / y | — | コンテナをソート可能なリストに変換します |
data-dnd-target | 名前 | — | ドロップゾーンを指定します |
data-dnd-transfer | append / prepend / remove | — | ゾーンにドロップされたときに実行されるアクション |
data-dnd-scroll | threshold;speed | 30;10 | ドラッグ中にコンテナを自動スクロールします |
data-dnd-transition | duration & easing | 100ms linear | 移動アニメーションを滑らかにします |
data-dnd-state | active / passive | — | 実行時のドラッグ状態、CSS でスタイル設定(スタイリング を参照) |