Back

Svelteでドラッグ&ドロップを実装する方法

Svelteでドラッグ&ドロップを実装する方法

ドラッグ&ドロップは、実際に構築しようとするまでは簡単に感じられます。ブラウザはネイティブAPIを提供していますが、実際には制限があります。スムーズなアニメーションがなく、タッチサポートに一貫性がなく、ブラウザ間で予測不可能な動作をします。ドロップ時にアイテムが不自然にカクッと配置されるのを見たことがあれば、この問題をご存知でしょう。

このガイドでは、Svelteでドラッグ&ドロップを実装するための2つの実用的なアプローチ(ネイティブHTML5 APIの使用とライブラリの使用)を取り上げ、実際に構築しているものに適したツールを選択できるようにします。

重要なポイント

  • ネイティブHTML5 Drag and Drop APIは、依存関係ゼロでシンプルなリストの並べ替えに機能しますが、スムーズなアニメーション、タッチサポート、一貫したクロスブラウザ動作が欠けています。
  • Svelte 5のrunes($state())とondragstart属性構文により、Svelte 3/4と比較してリアクティブなドラッグ&ドロップが簡素化されます。
  • アニメーション付き、複数リスト、タッチ対応、またはアクセシブルなドラッグ&ドロップには、svelte-dnd-actionが実用的なライブラリの選択肢です。
  • ドラッグイベントはクライアント専用です。SSRを使用するSvelteKitでは、ドラッグロジックをonMountまたは$app/environmentbrowserチェックでガードしてください。

2つのアプローチの理解

コードを書く前に、何を選択しているのかを理解することが役立ちます。

ネイティブHTML5 Drag and Drop APIは、ブラウザ組み込みのイベント(dragstartdragoverdragenterdragleavedrop)を使用します。依存関係がゼロで、シンプルなユースケースに適しています。トレードオフとして、アニメーションには手動作業が必要で、タッチサポートはデバイス間で一貫性がなく、ドラッグ中の視覚的フィードバックが制限されています。APIの詳細については、MDN Drag and Dropドキュメントをご覧ください。

ライブラリベースのソリューション(svelte-dnd-actionNeodragなど)は、スムーズなFLIPアニメーション、タッチサポート、アクセシブルなインタラクションといった難しい部分を標準で処理します。バンドルサイズが少し増えますが、基本的なソート可能なリスト以上のものに対しては、実装時間を大幅に節約できます。

Svelte 5でネイティブAPIを使用したドラッグ&ドロップの実装

以下は、Svelte 5のrunes構文を使用したクリーンな単一リスト並べ替えの例です:

<script>
  let items = $state(['Svelte', 'SvelteKit', 'Vite', 'TypeScript']);
  let dragIndex = $state(null);

  function handleDragStart(event, index) {
    dragIndex = index;
    event.dataTransfer.effectAllowed = 'move';
  }

  function handleDragOver(event, index) {
    event.preventDefault();
    if (dragIndex === null || dragIndex === index) return;

    const updated = [...items];
    const [moved] = updated.splice(dragIndex, 1);
    updated.splice(index, 0, moved);

    items = updated;
    dragIndex = index;
  }

  function handleDragEnd() {
    dragIndex = null;
  }
</script>

<ul>
  {#each items as item, index (item)}
    <li
      draggable="true"
      class:dragging={dragIndex === index}
      ondragstart={(e) => handleDragStart(e, index)}
      ondragover={(e) => handleDragOver(e, index)}
      ondragend={handleDragEnd}
    >
      {item}
    </li>
  {/each}
</ul>

<style>
  li {
    padding: 10px 16px;
    margin: 6px 0;
    background: #f1f1f1;
    cursor: grab;
    list-style: none;
    border-radius: 4px;
  }
  .dragging {
    opacity: 0.4;
  }
</style>

このSvelte 5パターンについて注目すべき点がいくつかあります:

  • $state()は、Svelte 3/4の古いリアクティブ変数宣言を置き換えます。
  • Svelte 5は、従来のon:dragstartイベントディレクティブに加えて、ondragstart属性構文もサポートしています。
  • リストはドロップ時だけでなく、ドラッグ(dragover時)に更新されます。これにより、ユーザーにリアルタイムの視覚的フィードバックが提供されます。

SvelteKitの注意点: ドラッグイベントはクライアント専用です。SSRを使用するSvelteKitを使用している場合は、ドラッグ関連のロジックをonMountでラップするか、$app/environmentbrowserチェックでガードしてください。

代わりにsvelte-dnd-actionを使用するタイミング

ネイティブアプローチは、シンプルなリスト並べ替えシナリオに適しています。しかし、以下のいずれかが必要になったら、svelte-dnd-actionを使用してください:

  • スムーズなFLIPアニメーション(リスト位置間)
  • 複数リストのドラッグ&ドロップ(カンバンスタイルのボード)
  • タッチとモバイルサポート(追加コードなし)
  • アクセシブルなキーボードインタラクション(組み込み)

svelte-dnd-actionのパターンは簡単です。コンテナにuse:dndzoneアクションを適用し、アイテム配列を渡し、considerfinalizeイベントを処理して状態を更新します。各アイテムには一意のidプロパティが必要です。Svelteの組み込みflipアニメーションと組み合わせると、20行未満で本番品質のドラッグインタラクションが得られます。

必要な機能使用するもの
シンプルなリスト並べ替え、アニメーションなしネイティブAPI
スムーズなアニメーション、複数リスト、タッチsvelte-dnd-action
自由形式の要素ドラッグNeodrag

結論

基本的なSvelteのドラッグ&ドロップリスト並べ替えには、Svelte 5のrunesを使用したネイティブブラウザAPIで依存関係なしで実現できます。より複雑なもの(アニメーション付きカンバンボード、タッチサポート、アクセシブルなインタラクション)には、svelte-dnd-actionが実用的な選択肢です。メカニズムを理解するためにネイティブアプローチから始め、要件が求めるときにライブラリにアップグレードしてください。

よくある質問

信頼性はありません。タッチデバイスでのドラッグイベントのサポートは、モバイルブラウザ間で一貫性がありません。モバイルを適切にサポートするには、mobile-drag-dropのようなポリフィルか、タッチインタラクションをネイティブに処理するsvelte-dnd-actionのようなライブラリが必要です。

はい。svelte-dnd-actionはSvelte 5で動作します。$state()でアイテム配列を宣言し、considerとfinalizeイベントハンドラ内で更新します。use:dndzone ディレクティブは同じままです。ライブラリが要素を正しく追跡できるように、配列内の各アイテムに一意のidプロパティがあることを確認してください。

ブラウザは、ドラッグされた要素の外観からデフォルトのゴースト画像を生成します。ネイティブAPIではこれに対する制御が制限されています。event.dataTransfer.setDragImage()を使用してカスタム要素やキャンバススナップショットを提供することでカスタマイズできますが、ドラッグ中の完全な視覚的制御には、svelte-dnd-actionやNeodragのようなライブラリの方が適しています。

ネイティブAPIを使用した複数リストのドラッグ&ドロップでは、ソースとターゲットのコンテナを手動で追跡する必要があり、すぐに複雑になります。svelte-dnd-actionは、各リストコンテナにuse:dndzoneを適用し、ゾーン間で同じアイテムタイプを共有することで、これを簡素化します。アイテムはコンテナ間でドラッグされると自動的に移動します。

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay