はじめに
カンバンボードのようなものを作りたく、
ドラッグアンドドロップのツールを探していました。
公式でドラッグアンドドロップのonイベントは用意されていますが、もう少し簡単に実装できないかと思い、
svelte-dnd-action
を使ってみました。
svelte-dnd-action導入
$ npm install --save-dev svelte-dnd-action
使い方
まずは参考コードから。
<script lang="ts">
const myItems = {
id: 1, name: 'item1',
}
</script>
<div
use:dndzone="{{items: myItems, ...otherOptions}}"
on:consider="{handler}"
on:finalize="{handler}"
>
{#each myItems as item(item.id)}
<div>{item.name}</div>
{/each}
</div>
<div>
タグに注目すると、use:dndzone
、on:consider
、on:finalize
を発見。
<div
use:dndzone="{{items: myItems, ...otherOptions}}"
on:consider="{handler}"
on:finalize="{handler}"
>
この3つをドラッグアンドドロップさせたい範囲に設定することでドラッグアンドドロップを機能させています。
use:dndzone
ドラッグアンドドロップをさせたい配列を指定するメソッド。
otherOptions
には文字通り、オプションを指定する。
オプションは以下の通り
オプション
Name | Type | 必須 | デフォルト |
---|---|---|---|
flipDurationMs | Number | No | 0 |
type | String | No | Internal |
dragDisabled | Boolean | No | false |
morphDisabled | Boolean | No | false |
dropFromOthersDisabled | Boolean | No | false |
zoneTabIndex | Number | No | 0 |
dropTargetStyle | Object | No | {outline: 'rgba(255, 255, 102, 0.7) solid 2px'} |
dropTargetClasses | Array | No | [] |
transformDraggedElement | Function | No | () => {} |
autoAriaDisabled | Boolean | No | false |
centreDraggedOnCursor | Boolean | No | false |
よく使いそうなものを解説していきます。
flipDurationMs
ドラッグされたアイテムが戻るときのアニメーションの秒数。
0または設定しないとアニメーションがないことにできる
type
同じタイプが設定されているdnd-zone同士ではドロップアンドドロップできます。
デフォルトではすべてのdnd-zoneが同じタイプになってます。
dragDisabled
設定するとドラッグアンドドロップを禁止できる
dropFromOthersDisabled
他のdnd-zoneからのドロップを許可するかどうか。
こちらからのドラッグは制限しない。
centreDraggedOnCursor
trueにするとドラッグした際に、アイテムの中心を持つようにアイテムが移動します。
カンバンツールっぽいものを実装してみた
以上を使って実装したのがこちら。
UIコンポーネントにはFlowbite-svelteを利用しました。
導入方法は前回の記事で公開しています。
// handler.ts
export const DndConsider = (e, items) => {
items = e.detail.items;
return items
}
export const DndFinalize = (e, items) => {
items = e.detail.items;
return items
}
// Dnd.svelte
<script>
import {flip} from "svelte/animate";
import {dndzone} from "svelte-dnd-action";
import { Card } from "flowbite-svelte";
import { DndConsider, DndFinalize } from "./handler";
let Aitems = [
{id: 1, name: "Aitem1"},
{id: 2, name: "Aitem2"},
{id: 3, name: "Aitem3"},
{id: 4, name: "Aitem4"}
];
let Bitems = [
{id: 5, name: "Bitem1"},
{id: 6, name: "Bitem2"},
{id: 7, name: "Bitem3"},
{id: 8, name: "Bitem4"}
];
const flipDurationMs = 300;
</script>
<div class="flex">
<div class="w-1/2">
<h1 class="text-4xl font-bold text-center">配列A</h1>
<div
class="box mx-auto"
use:dndzone="{{items: Aitems, centreDraggedOnCursor: true, flipDurationMs}}"
on:consider="{e => Aitems = DndConsider(e, Aitems)}"
on:finalize="{e => Aitems = DndFinalize(e, Aitems)}"
>
{#each Aitems as item(item.id)}
<div class="my-2" animate:flip="{{duration: flipDurationMs}}">
<Card class="mx-auto">
{item.name}
</Card>
</div>
{/each}
</div>
</div>
<div class="w-1/2">
<h1 class="text-4xl font-bold text-center">配列B</h1>
<div
class="box mx-auto"
use:dndzone="{{items: Bitems, centreDraggedOnCursor: true, flipDurationMs}}"
on:consider="{e => Bitems = DndConsider(e, Bitems)}"
on:finalize="{e => Bitems = DndFinalize(e, Bitems)}"
>
{#each Bitems as item(item.id)}
<div class="my-2" animate:flip="{{duration: flipDurationMs}}">
<Card class="mx-auto">
{item.name}
</Card>
</div>
{/each}
</div>
</div>
</div>
<style>
.box {
width: 400px;
border: 1px solid black;
}
</style>