1. Home
  2. JavaScript
  3. jQuery
  4. jQueryでドラッグ&ドロップによる要素の並び替えとそれをCookieで記憶する仕組みを作る

jQueryでドラッグ&ドロップによる要素の並び替えとそれをCookieで記憶する仕組みを作る

  • 公開日
  • 更新日
  • カテゴリ:jQuery
  • タグ:JavaScript,Frontend,jQuery,Cookie,Sortable.js,jquery.cookie.js
jQueryでドラッグ&ドロップによる要素の並び替えとそれをCookieで記憶する仕組みを作る

JavaScript のフレームワーク jQuery とライブラリを使い、ドラッグアンドドロップで要素の移動を行います。また、応用として、それらを Cookie に保存し、ページ遷移・リロードを行っても変更を保持できる仕組みを構築します。

Contents

  1. 開発環境
  2. Sortable.js
  3. 最小構成
  4. 要素の移動時にアニメーションを付ける
  5. ドラッグする要素を限定する
  6. 入れ替えた要素を記憶する
  7. 別の領域へ要素を移動する

開発環境

今回は HTML/CSS/JavaScript のみを使用します。また、jQuery を使用するので、予めコアソースをダウンロードしておいてください。
jQuery
https://jquery.com

Sortable.js

ドラッグアンドドロップを実現する為に、Sortable.js というライブラリを使います。

Sortable.js
https://github.com/RubaXa/Sortable

ちなみに、jQuery-ui を使ってもドラッグアンドドロップは実現できますが、このライブラリとは挙動が違うので、どちらを使うかは完全に好みです。

最小構成

まずはミニマムで動作を確認します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>drag and drop | jQuery JavaScript</title>
</head>
<body>
<style>
    body {
        margin: 10px;
    }
    #listWithHandle div {
        float: left;
        margin-left: 5px;
        width: 100px;
        height: 100px;
        border: 1px solid #00A5E3;
        font-size: 24px;
        text-align: center;
    }
    #listWithHandle div:hover {
        cursor: move;
    }
</style>

<div id="listWithHandle">
    <div>01</div>
    <div>02</div>
    <div>03</div>
    <div>04</div>
    <div>05</div>
    <div>06</div>
    <div>07</div>
    <div>08</div>
    <div>09</div>
</div>

<script src="js/jquery-3.3.1.min.js"></script>
<script src="js/Sortable.min.js"></script>
<script>
    $(function(){
        Sortable.create(listWithHandle);
    });
</script>
</body>
</html>

記述した style は見た目を整えただけなので、ライブラリ用に必要な記述ではありません。無くてもいいし、自分の好きに変更できます。

<div id="listWithHandle">
  <div>01</div>
  <div>02</div>
  <div>03</div>
  <div>04</div>
  <div>05</div>
  <div>06</div>
  <div>07</div>
  <div>08</div>
  <div>09</div>
</div>

ここが並べ替えるメインの部分です。 div要素で作っていますが、リストでも良いし、何でも OK です。今回の場合で言えば、親ラッパーに listWithHandle という ID 名を付けている事が、ライブラリを動作させる為に必要な記述です。

<script src="js/jquery-3.3.1.min.js"></script>
<script src="js/Sortable.min.js"></script>

jQuery と Sortable.js を読み込んでいます。 CDN でももちろん OK 。

<script>
  $(function(){
    Sortable.create(listWithHandle);
  });
</script>

ページ読み込み時にスクリプトを走らせて要素を draggable にしています。 create() メソッドの引数にドラッグアンドドロップを行いたい要素たちの親要素である listWithHandle という ID 名を渡しています。

これをブラウザで表示すると以下のようになります。 ドラッグアンドドロップを行ってみてください。

See the Pen <a href="https://codepen.io/rito328/pen/ZEojvJg"> drag and drop | jQuery JavaScript</a> by rito (<a href="https://codepen.io/rito328">@rito328</a>) on <a href="https://codepen.io">CodePen</a>.

ミニマムでのドラッグアンドドロップを実現できました。

要素の移動時にアニメーションを付ける

実際に動かしてみるとわかりますが、要素が入れ替わる際に雑然と入れ替わるので、なんだかインタラクティブではありません。そこで、オブションを指定して要素の入れ替わり時にアニメーションをつけます。

オプションを指定するには、create() メソッドの第二引数にオプションオブジェクトを渡します。

<script>
  $(function(){
    Sortable.create(listWithHandle, {
      animation: 150,
    });
  });
</script>

animation プロパティを設定する事で、要素の入れ替わりの動きにインタラクションを加える事が出来ます。値はアニメーションのスピードで、ms(ミリ秒)で指定します。

See the Pen <a href="https://codepen.io/rito328/pen/JjvBMwW"> drag and drop Animation | jQuery JavaScript</a> by rito (<a href="https://codepen.io/rito328">@rito328</a>) on <a href="https://codepen.io">CodePen</a>.

ドラッグする要素を限定する

もし単純なリストではなく、色々な要素を詰め込んだラッパー自体を入れ替えていく場合、通常のようにラッパー自体をドラッガブルにしてしまうと要素内のテキストなどが選択できず、ユーザビリティが低下します。

その場合はドラッグを行う要素を作成し(アイコンなど)、それによって対象の領域が入れ替えられるようにします。

まずはドラッグ対象の HTML を以下に変更します。

<div id="listWithHandle">
  <div><span class="icon-move" aria-hidden="true">#</span>01</div>
  <div><span class="icon-move" aria-hidden="true">#</span>02</div>
  <div><span class="icon-move" aria-hidden="true">#</span>03</div>
  <div><span class="icon-move" aria-hidden="true">#</span>04</div>
  <div><span class="icon-move" aria-hidden="true">#</span>05</div>
  <div><span class="icon-move" aria-hidden="true">#</span>06</div>
  <div><span class="icon-move" aria-hidden="true">#</span>07</div>
  <div><span class="icon-move" aria-hidden="true">#</span>08</div>
  <div><span class="icon-move" aria-hidden="true">#</span>09</div>
</div>

各リストに、以下のようにしてドラッグを行う為の要素を追加しています。

<span class="icon-move" aria-hidden="true">#</span>

この#をアイコンと見立てて、これをドラッグする事で、要素を移動する。という流れになります。

次に jQuery 側を変更します。

<script>
  $(function(){
    Sortable.create(listWithHandle, {
      handle: '.icon-move',
      animation: 150,
    });
  });
</script>

handle プロパティに対して、対象のクラス名を指定する事で、ドラッグ要素を限定しドラッグアンドドロップを実現する事ができます。

See the Pen <a href="https://codepen.io/rito328/pen/RwyBxmy"> Limit the elements to drag</a> by rito (<a href="https://codepen.io/rito328">@rito328</a>) on <a href="https://codepen.io">CodePen</a>.

#を押下する事でこれらの要素を移動できます。#05 の要素を見てもらうとわかる通り、こうする事で#以外のテキストを選択できるようになります。(通常では、どこを押下してもドラッグが発火するので選択できない)

尚、この時点で以下のようなエラーでドラッグが発火しない場合があります。

Uncaught DOMException: Failed to execute 'matches' on 'Element': '>*' is not a valid selector.

その場合はコアソースに問題があるかもしれません。今回のサンプルソースを Github に上げていますので、そこに収録している Sortable.min.js を使用してみてください。

入れ替えた要素を記憶する

最後に、入れ替えた要素を記憶させ、ページをリロードしても入れ替えた要素を再現できるようにします。

Cookie を簡単に扱う為に、jquery.cookie.js というライブラリを使います。
jquery.cookie.js
https://github.com/carhartl/jquery-cookie

まずは jquery.cookie.js を読み込みます。

<script src="js/jquery-3.3.1.min.js"></script>
<script src="js/Sortable/Sortable.min.js"></script>
<script src="js/jquery.cookie.1.4.1.min.js"></script>

ドラッグ領域の HTML を以下に変更します。

<div id="listWithHandle" class="list-group"></div>

中身は JS で挿入するので、ラッパーのみになります。

最後に jQuery 側の変更です。

<script>
    var page;
    var table = ["01", "02", "03", "04", "05", "06", "07", "08", "09"];

    function init() {
        sort_table = getTable();

        for (i = 0; i < sort_table.length; i++) {
            $('#listWithHandle').append(createHtml(sort_table[i]));
        }
    }

    function getTable() {
        $.cookie.json = true;
        page = $.cookie('page.page01');

        var sort_table;
        if(page !== undefined) {
            sort_table = page;
        } else {
            sort_table = table;
        }

        return sort_table;
    }

    function createHtml(name) {
        return '<div class="wrap list-group-item" data-name="' + name + '">' +
            '<span class="icon-move" aria-hidden="true">#</span>' + name +
            '</div>';
    }

    $(function(){
        init();

        Sortable.create(listWithHandle, {
            handle: '.icon-move',
            animation: 150,
            onUpdate: function (e) {
                var box = [];
                $('.wrap').each(function(index, element){
                    box.push($(element).data('name'));
                });
                $.cookie('page.page01', box);
            },
        });
    });
</script>

上から解説します。

var page;
var table = ["01", "02", "03", "04", "05", "06", "07", "08", "09"];

Cookie 値を収納する為の変数 page と、デフォルトである要素名を収録した変数 table を宣言しています。

function init() {
  sort_table = getTable();

  for (i = 0; i < sort_table.length; i++) {
    $('#listWithHandle').append(createHtml(sort_table[i]));
  }
}

init() メソッドは初期化用メソッドです。描画対象の要素を取得し、ドラッグ領域に挿入しています。

function getTable() {
  $.cookie.json = true;
  page = $.cookie('page.page01');

  var sort_table;
  if(page !== undefined) {
    sort_table = page;
  } else {
    sort_table = ;
  }

  return sort_table;
}

getTable() メソッドでは、Cookie に値がある場合はその要素を、ない場合にはデフォルト要素を返します。

function createHtml(name) {
    return '<div class="wrap list-group-item" data-name="' + name + '">' +
        '<span class="icon-move" aria-hidden="true">#</span>' + name +
        '</div>';
}

createHtml() メソッドでは、挿入する HTML を生成します。

$(function(){
    init();

    Sortable.create(listWithHandle, {
        handle: '.icon-move',
        animation: 150,
        onUpdate: function (e) {
            var box = [];
            $('.wrap').each(function(index, element){
                box.push($(element).data('name'));
            });
            $.cookie('page.page01', box);
        },
    });
});

ページ読み込みが完了したら初期化メソッドを実行し、その後で Sortable.js を動作させます。その際にオプションで onUpdate プロパティをクロージャで設定します。ここでは変更された DOM の data 属性を収集し、Cookie に保存しています。

これで要素の変更を行った後でページをリロードしても、変更された並び順で表示されます。

別の領域へ要素を移動する

これまでは、1つのリスト(グループ)内での並び替えを行いましたが、次は異なるリストへも要素を移動できるようにします。(Cookie での記憶は使いません)

今回は3つのリスト間を移動できるようにしていきます。 HTML ソースは以下のようになります。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>drag and drop | jQuery JavaScript</title>
</head>
<body>
<style>
    body {
        margin: 10px;
    }
    .list {
        float: left;
    }
    .list li {
        padding: 5px 10px;
        border: 1px solid #0c5460;
        list-style: none;
    }
    .list li:hover {
        cursor: move;
    }
    .a { background-color: #a2c2e6; }
    .b { background-color: #f3981d; }
    .c { background-color: #a4d5bd; }
</style>

<div class="container">
    <div class="lists_wrap">
        <ul class="list" id="list1">
            <li class="a">AAA 1</li>
            <li class="a">AAA 2</li>
            <li class="a">AAA 3</li>
            <li class="a">AAA 4</li>
            <li class="a">AAA 5</li>
        </ul>
        <ul class="list" id="list2">
            <li class="b">BBB 1</li>
            <li class="b">BBB 2</li>
            <li class="b">BBB 3</li>
            <li class="b">BBB 4</li>
            <li class="b">BBB 5</li>
        </ul>
        <ul class="list" id="list3">
            <li class="c">CCC 1</li>
            <li class="c">CCC 2</li>
            <li class="c">CCC 3</li>
            <li class="c">CCC 4</li>
            <li class="c">CCC 5</li>
        </ul>
    </div>
</div>

<script src="../js/jquery-3.3.1.min.js"></script>
<script src="../js/Sortable/Sortable.min.js"></script>
<script>
    $(function(){
        Sortable.create(list1, {
            sort: 1,
            group: {
                name: 'common_lists',
                pull: 'clone',
                put: true
            },
            animation: 150
        });
        Sortable.create(list2, {
            sort: 1,
            group: {
                name: 'common_lists',
                pull: true,
                put: true
            },
            animation: 150
        });
        Sortable.create(list3, {
            sort: 1,
            group: {
                name: 'common_lists',
                pull: true,
                put: true
            },
            animation: 150
        });
    });
</script>
</body>
</html>

3つのリスト(と言っていますが ui の意味ではありません)に対してそれぞれ create() メソッドを呼んでいますが、ポイントは以下の部分です。

group: {
  name: 'common_lists',
  pull: true,
  put: true
}

group プロパティを設定し、それぞれ name プロパティには共通の名前を付けます。こうする事で、異なる領域間での移動が可能になります。

また、pull プロパティを以下のように変更する事で、異なる領域にドラッグアンドドロップさせた後でその要素を「コピー」するのか「移動」させるのかを切り替える事ができます。「コピー」とはその名の通り、要素をコピーするので移動元からは無くなりません。対して「移動」は、移動元からその要素は無くなります。

pull: 'clone', // コピー
pull: true,   // 移動

ブラウザからアクセスして動作確認を行います。

See the Pen <a href="https://codepen.io/rito328/pen/KKRBQpw"> Memorize drag and drop elements with cookies | jQuery JavaScript</a> by rito (<a href="https://codepen.io/rito328">@rito328</a>) on <a href="https://codepen.io">CodePen</a>.

別領域へも移動が出来ている事を確認できました。ちなみによく見るとわかりますが、「AAA 」のリストはコピーの設定にしているので、「AAA1 」が複数できています。

もちろん、リスト内での並び替えもいつも通り行えます。

まとめ

以上で作業は終了です。 jQuery でドラッグアンドドロップを実現する手法は他にもありますが、導入しやすいものの1つだと思うので、是非試してみてください。

尚、今回のサンプルソースは Github に上げてあります。
rito-nishino/jquery-sample-drag-and-drop

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級