はじめに
Chromeの機能拡張を作っていて、li
要素を変更できる機能を実装しようと思いました。
その部分だけを抜き出して説明します。
ソースは下記にあります。(この記事中ですべて説明できていますが、一応)
動作サンプルと最低限必要な全ソースはこちら
See the Pen draggable_li by yoichiro ikeda (@yo16) on CodePen.
手順
1.各li
に、draggable="true"
を設定する
<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>
そのままですが、li
にdraggable="true"
を追加します。
styleは、ドラッグを視覚的にわかりやすくしているだけです。
2.ul
にドラッグ系の3イベントを追加する
3イベントとは、dragstart
とdragover
とdragend
です。
2.1. 初期化
ドラッグ対象のul
を捕まえておきます。
draggingElm
は、li
が入る予定の変数です。
const list = document.getElementById('myUl');
let draggingElm = null; // ドラッグしているli要素
2.2. dragstart
ハンドラーを追加
ul
にdragstart
イベントのハンドラーを追加します。「とある要素のドラッグを始めた!」というイベントです。
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は後者でした。日本人は前者が好きだそうで、私も何となく前者が好き。読め/書けと言われればどちらでもできるけど、好きの感覚は説明できない。不思議。
参考