Skip to content

はじめに(バージョン3)

インストール

ターミナルに以下を入力してください:

sh
# npm
npm i effdnd

# pnpm
pnpm add effdnd

# yarn
yarn add effdnd

クイックスタート

簡単に言うと、effdnd は 2 つのカスタム Web コンポーネントを使用します。

  • effdnd trigger はドラッグ&ドロップをトリガーします。
  • effdnd-actor は、ドラッグ&ドロップ処理に参加するレイアウト領域を指定します(それぞれの役割を担います)。

Web コンポーネント effdnd-actor は、複数の役割を担うことができます。

  • item - 移動するアイテム、
  • target - 移動先、
  • scope - 移動範囲。

役割を設定するには、effdnd-actor 要素に同名の属性を設定する必要があります。1つの要素に複数の役割を設定できます。

ドラッグ&ドロップの開始時、effdnd-trigger Webコンポーネントは、その属性を使用してプロセスに参加する要素(アクター)を選択します。

  • item 属性を使用すると、移動するアイテムを選択できます。属性が指定されていない場合は、指定された属性 item を持つツリー内の最も近い要素 <effdnd-actor item='...'> が使用されます。値が # の属性が設定されている場合(<effdnd-trigger item='#'>)、または上記のDOMツリーに <effdnd-actor item='...'> が存在しない場合は、トリガー自体が移動します。
  • scope属性を使用すると、移動可能な領域を選択できます。この属性が指定されていない場合、指定されたscope属性を持つ<effdnd-actor scope='...'>がツリー内で最も近いものとして使用されます。値が#の属性が設定されている場合(<effdnd-trigger scope='#'>)、または上記のDOMツリーに<effdnd-actor scope='...'>が存在しない場合は、移動領域はdocument.bodyとみなされます。
  • target属性を使用すると、移動可能なターゲットを選択できます。移動ターゲットは、ドラッグアンドドロップが終了したときに特別なイベントを生成します。scope領域内にあるすべての<effdnd-actor target='...'>要素は移動ターゲットとみなされ、その名前はトリガーのtarget属性の値で始まります。これは、属性が指定されていない場合、現在の <effdnd-actor scope='...'> 内ですべての <effdnd-actor target='...'> が使用可能であることを意味します。

両方のWebコンポーネントを定義するには、単にuseDnD関数を呼び出し、その呼び出し結果をイベントリッスンするだけです。

jsx
import { useRef } from 'react';
import { useDnD } from 'effdnd';

const { observe } = useDnD();

export const Component = () => {
    const ref = useRef();
    useEffect(() => {
      // ドラッグ&ドロップイベントに登録できます
      const unobserve = observe((e) => {
        // イベントはここで処理されています
      }, ref.current);
      // 購読を解除できます
      return () => unobserve();
    });
    return <effdnd-actor scope='top' ref={ref} >
        <div className="targets-wrapper">
            <effdnd-actor target='target-1'>...</effdnd-actor>
            <effdnd-actor target='target-2'>...</effdnd-actor>
        </div>
        <div id="items-wrapper">
            <effdnd-actor item='item-1'>
                <effdnd-trigger>Trigger #1</effdnd-trigger>
            </effdnd-actor>
            <effdnd-actor item='item-2'>
                <effdnd-trigger>Trigger #2</effdnd-trigger>
            </effdnd-actor>
        </div>
    </effdnd-actor>;
}

アクティブ要素とパッシブ要素

ドラッグ&ドロップ中、effdnd-trigger は選択された effdnd-actor アクターに動的な state 属性を設定します。つまり、トリガーは最初に、何が移動するのか (item)、どこへ移動できるのか (scope)、そしてどこへ移動すべきなのか (target) を決定します。要素は、

  • 状態なし、
  • パッシブ状態 (state="passive")、
  • アクティブ状態 (state="active") のいずれかになります。

item ロールを持つ要素は、移動中は アクティブ状態になります。さらに、item ロールを持つ要素は、クローンがアクティブな場合、クローン状態 (state="cloned") になることもあります。アイテムをクローンするには、item-mode="cloned" モードを有効にします。

scopeロールを持つ要素は、itemがその内部を移動する際にパッシブ状態になります。この状態は、移動可能な領域の境界を示します。

scopeロールを持つ要素は、パッシブ状態にあるときにitemがその境界を超えようとするとアクティブ状態になります。この状態は、カーソルが許容範囲を超えて移動したことをユーザーに示します。

targetロールを持つ要素は、アクティブまたはパッシブ状態のscope内にある場合パッシブ状態になります。この状態は、itemが移動可能な範囲を示します。

targetロールを持つ要素は、パッシブ状態にあるときにitemを持つカーソルがその要素の上にある場合アクティブ状態になります。この状態は、targetitemを受け入れる準備をしていることを示します。

ロールモード

ライブラリの基本的な動作は非常に低レベルであるため、各ロールのモードによってユースケースを詳細に制御できます。

  • item-modeitem のモードを定義し、以下の値を設定できます。
  • item-mode="keep" - item 要素は移動後も新しい座標を保持します。
  • item-mode="clone" - item 要素のクローンが作成され、そのクローンが移動します。
  • target-modetarget が選択された後のモードを定義し、以下の値を設定できます。
  • target-mode="prepend" - item 要素は target の最初の子要素として追加されます。
  • target-mode="append" - item 要素は target の最後の子要素として追加されます。
  • target-mode="remove" - item要素がDOMツリーから削除されます。
  • scope-modeはドラッグアンドドロップ時のscopeモードを定義し、以下の値を設定できます。
  • scope-mode="order-x" - scope要素を使用すると、item要素をx軸に沿って並べ替えることができます。
  • scope-mode="order-y" - scope要素を使用すると、item要素をy軸に沿って並べ替えることができます。

スタイル

<effdnd-actor item='...'> の外観は、移動中に属性を使用してトリガーによって設定されます。属性を使用して、アニメーションパラメータ、移動軸、最小射程距離を設定することもできます。

ts
/export interface ITriggerAttrs {
    /**
     * Item name
     * @description
     * If the value is "#" then it will use `effnd-trigger` as item
     */
    item?: string;
    /**
     * Bounding scope name
     * @description
     * If the value is "#" then it will use `document.body` as scope
     */
    scope?: string;
    /**
     * Target name or its prefix
     */
    target?: string;
    /**
     * Triggering distance
     */
    dist?: string;
    /**
     * Triggering mouse button
     * @description
     * Any mouse button if not specified
     */
    btn?: '0' | '1' | '2';
    /**
     * DnD axis
     */
    axis?: string;
    /**
     * Triggering event
     * @description
     * Both 'touch' and 'mouse' by default
     */
    event?: 'touch' | 'mouse';
    /**
     * Transition duration
     */
    dur?: string;
    /**
     * Transition delay
     */
    del?: string;
    /**
     * Transition timing-function
     */
    tf?: string;
}

デフォルトでは、空のトリガー内にSVG移動アイコンが表示されます。このアイコンの表示形式はaxis属性によって決まります。アイコンを非表示にするには、effdnd-trigger内に独自のコンテンツを追加してください。

Webコンポーネントeffdnd-actorには、ロールのパッシブ状態とアクティブ状態の初期スタイルが既に含まれていますが、unstyled属性を使用してこれらのスタイルをキャンセルすることもできます。同時に、<effdnd-actor unstyled="target">targetの組み込みスタイルのみをキャンセルし、<effdnd-actor unstyled="scope">scopeの組み込みスタイルのみをキャンセルし、<effdnd-actor unstyled>はすべての組み込みスタイルをキャンセルします。

ts
/**
 * DnD actor attributes
 */
export interface IActorAttrs {
    /**
     * Item name
     */
    item?: string;
    /**
     * Scope name
     */
    scope?: string;
    /**
     * Target name
     */
    target?: string;
    /**
     * Actor state
     */
    state?: 'active' | 'passive';
    /**
     * Reset target state styles
     */
    unstyled?: boolean | 'target' | 'scope';
    /**
     * Display contents
     */
    contents?: boolean;
    /**
     * Item mode
     */
    'item-mode'?: 'keep' | 'clone';
    /**
     * Target mode
     */
    'target-mode'?: 'append' | 'prepend' | 'remove';
    /**
     * Scope mode
     */
    'scope-mode'?: 'order-x' | 'order-y';
    /**
     * Scope transition duration
     */
    'scope-dur'?: number;
    /**
     * Scope transition delay
     */
    'scope-del'?: number;
    /**
     * Scope transition timing-function
     */
    'scope-tf'?: string;
}

scopeロールを持つ要素のscope-durscope-delscope-del属性は、CSSのtransitionプロパティのデフォルト値を設定します。

contents属性を使用すると、display:contents;スタイルを設定できます。

また、異なる状態に対して独自のCSSスタイルを設定することもできます。

css
effdnd-actor[target][state=passive] .custom-dnd-target {
  outline: 4px solid #646cffaa;
}
effdnd-actor[target][state=active] .custom-dnd-target {
  outline: 4px solid #646cffaa;
  background: #646cffaa;
}

サーバーサイドレンダリング

サーバーサイドレンダリングを使用している場合は、レイアウトを維持するために、effdnd宣言を含むスクリプトをHTMLのheadタグ内に追加してください。

html
<head>
    <script src="https://cdn.jsdelivr.net/npm/effdnd@3.1.0/dist/use.js"></script>
</head>

このスクリプトを使用すると、effdnd をグローバル変数として使用することもできます。

ts
import { TUseDnD } from 'effdnd';

declare global {
    interface Window {
        effdnd: ReturnType<TUseDnD>;
    }
};

const unobserve = window.effdnd.observe(() => console.log('effdnd'));

useDnD

この関数は、Webコンポーネントeffdnd-triggereffdnd-actorを宣言し、カスタムイベントを購読するためのハンドラーも返します。

ts
export type TEventDetail = {
    type: TEventType;
    refs: TRefs;
    keys: TKeys;
    event: MouseEvent | TouchEvent;
};

interface TDnDEvent extends CustomEvent {
    detail: TEventDetail;
}

export type TDnDCallback = (event: TDnDEvent) => void;

export type TUseDnD = () => {
    /**
     * Observe DnD events
     * @param callback - event handler
     * @param element - HTML element
     */
    observe: (callback: TDnDCallback, element?: HTMLElement) => () => void;
    /**
     * Unobserve DnD events
     * @param callback - event handler
     * @param element - HTML element
     */
    unobserve: (callback: TDnDCallback, element?: HTMLElement) => void;
};

effdnd-trigger

トリガーWebコンポーネントには、いくつかの便利なメソッドとプロパティが含まれています。

ts
/**
 * DnD trigger element 
 */
export interface ITriggerElement extends HTMLElement {
    /**
     * Is trigger active
     */
    isActive: boolean;
    /**
     * DnD item y-offset
     */
    dndY: number;
    /**
     * DnD item x-offset
     */
    dndX: number;
    /**
     * DnD item
     */
    dndItem: HTMLElement;
    /**
     * DnD scope
     */
    dndScope: HTMLElement;
    /**
     * DnD targets
     */
    dndTargets: Set<HTMLElement>;
    /**
     * Reset DnD translate
     */
    resetDnD(): void;
}

Apache License 2.0に基づいて公開されています。