1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

htmlのli要素をドラッグして順序を変更できるようにする

Posted at

はじめに

Chromeの機能拡張を作っていて、li要素を変更できる機能を実装しようと思いました。
その部分だけを抜き出して説明します。

ソースは下記にあります。(この記事中ですべて説明できていますが、一応)

動作サンプルと最低限必要な全ソースはこちら

See the Pen draggable_li by yoichiro ikeda (@yo16) on CodePen.

手順

1.各liに、draggable="true"を設定する

draggable_li.html(抜粋)
<ul id="myUl">
    <li draggable="true" style="background-color:#fcc">list item1 赤</li>
    <li draggable="true" style="background-color:#ffc">list item2 黄</li>
    <li draggable="true" style="background-color:#ccf">list item3 青</li>
</ul>

そのままですが、lidraggable="true"を追加します。

styleは、ドラッグを視覚的にわかりやすくしているだけです。

2.ulにドラッグ系の3イベントを追加する

3イベントとは、dragstartdragoverdragendです。

2.1. 初期化

ドラッグ対象のulを捕まえておきます。
draggingElmは、liが入る予定の変数です。

const list = document.getElementById('myUl');
let draggingElm = null;    // ドラッグしているli要素

2.2. dragstartハンドラーを追加

uldragstartイベントのハンドラーを追加します。「とある要素のドラッグを始めた!」というイベントです。

list.addEventListener('dragstart', function(e) {
    draggingElm = e.target;
    //console.log(draggingElm); // つかんだli要素
    e.dataTransfer.effectAllowed = 'move';
});

e.dataTransfer.effectAllowed = 'move';は、moveを許可するという意味です。デフォルトはallになっていてmoveに絞るのですが、デフォルトで動作するのであえて絞る意図はあまりないかもしれません。

2.3. dragoverハンドラーを追加

次はdragoverです。こちらは「何かが上に来た!」というイベントです。dragstartとはイベントを起こす要素が異なっていて、受け側になります。

// ドラッグしている要素が何かの上にoverしたときのハンドラ
// ここでeは、ドラッグして覆いかぶされた要素=targetとしている
// 最初はドラッグ直後なので自分自身で、マウスを移動すると変わる
list.addEventListener('dragover', function(e) {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';

    const target = e.target;
    //console.log(target);
    // 自分でもなく、li要素の場合に変更する
    if (target && target !== draggingElm && target.nodeName === 'LI') {
        const rect = target.getBoundingClientRect();
        // 0.5より小さい=マウスが上半分にある=false, 0.5より大きい=下半分にある=true
        const next = (e.clientY - rect.top) / (rect.bottom - rect.top) > 0.5;
        // ドラッグしているものの上半分にある場合は、次の要素のbeforeにinsert
        // 下半分にある場合は、今の要素のbeforeにinsert
        // 一番下の場合はnextSiblingはnullで、nullをinsertBeforeに渡すと最後になる
        list.insertBefore(draggingElm, next && target.nextSibling || target);
    }
});

デフォルトの動作を停止し、moveをしている旨をdropEffectに設定。

target、つまり乗られた要素と、draggingElm、つまりドラッグしている要素を比較します。それらが違う要素で、targetがLIの場合に移動します。

マウス位置が、targetの上半分か下半分かで挿入位置を確定して、insertBeforeで位置を変更します。

2.4. dragendハンドラーを追加

ドラッグを終了しマウスボタンを離したときの、ドラッグ元要素のイベントハンドラーです。

list.addEventListener('dragend', function(e) {
    draggingElm = null;
});

初期化で定義した変数をnullにします。

正直やらなくても大丈夫かもしれませんが、バグ防止のためにきっちり消しておきます。
(起こる事象のすべてを把握しているわけではないのだから、やるべき。例えば、ドラッグしたままマウスをブラウザの外に出した、その後とか、想定外のことはいくらでもあるので。)

おわりに

これくらいなら、何かのライブラリを入れるとかせずとも、小手先の技で十分対応できることがわかりました。これでChrome機能拡張が捗ります。

なお今回の内容は、ChatGPTに聞いて進めした。以前は、話半分で参考にする程度だったような気がしますが、今やほぼ丸々使える答えが返ってきてすごいです。
わからない行はもっと詳しく教えてって言えば教えてもらえるから、動くものを作るだけでなく、普通に勉強にもなる。
さすがに、htmlとかJavaScriptの基礎くらいはわかっていないと、その情報を組み立てることすらできないですが。

あと宗教問題かもしれませんが、関数の書き方は、()=>{}より、function(){}が世界標準だとどこかで見ました。後者の方が速いケースがあるそうで。ChatGPTは後者でした。日本人は前者が好きだそうで、私も何となく前者が好き。読め/書けと言われればどちらでもできるけど、好きの感覚は説明できない。不思議。

参考

1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?