ドラッグ並べ替え
HTML5 Drag and Dropでリスト項目の順序を入れ替え。挿入位置のヒント線付きで、タスクの優先順位付けや並び替えUIに使えます。
ライブデモ
使用例(お題: アイドルグループ Sakura)
この技法を「アイドルグループ Sakura」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- Sakura:推し楽曲ランキングをドラッグで並べ替え -->
<div class="idol">
<header class="idol__bar">
<span class="idol__logo">🌸 Sakura</span>
<span class="idol__sub">あなたの推し曲ランキング</span>
</header>
<p class="idol__lead">ドラッグして好きな順に並べ替えてください</p>
<ul class="dnd" id="list">
<li class="dnd__item" draggable="true">
<span class="dnd__grip" aria-hidden="true">⠿</span>
<span class="dnd__thumb" style="background-image:url('https://picsum.photos/80/80?random=31')"></span>
<span class="dnd__body"><span class="dnd__title">春風センセーション</span><span class="dnd__meta">3rd Single</span></span>
</li>
<li class="dnd__item" draggable="true">
<span class="dnd__grip" aria-hidden="true">⠿</span>
<span class="dnd__thumb" style="background-image:url('https://picsum.photos/80/80?random=32')"></span>
<span class="dnd__body"><span class="dnd__title">夜空のラムネ</span><span class="dnd__meta">2nd Single</span></span>
</li>
<li class="dnd__item" draggable="true">
<span class="dnd__grip" aria-hidden="true">⠿</span>
<span class="dnd__thumb" style="background-image:url('https://picsum.photos/80/80?random=33')"></span>
<span class="dnd__body"><span class="dnd__title">きみとパレード</span><span class="dnd__meta">5th Single</span></span>
</li>
<li class="dnd__item" draggable="true">
<span class="dnd__grip" aria-hidden="true">⠿</span>
<span class="dnd__thumb" style="background-image:url('https://picsum.photos/80/80?random=34')"></span>
<span class="dnd__body"><span class="dnd__title">花びらメモリー</span><span class="dnd__meta">1st Single</span></span>
</li>
</ul>
</div>
CSS
/* Sakura アイドル テーマ */
:root{--pink:#ffd1e0;--deep:#e86a96;--ink:#4a3540;--line:#f0dde4;--muted:#9b8690;--gray:#f7f3f5}
*{box-sizing:border-box}
body{margin:0;min-height:100vh;font-family:"Hiragino Kaku Gothic ProN","Segoe UI",sans-serif;background:linear-gradient(180deg,#fff5f9,var(--gray));color:var(--ink)}
.idol{max-width:440px;margin:0 auto;padding:0 16px 20px}
.idol__bar{display:flex;align-items:center;justify-content:space-between;padding:16px 4px}
.idol__logo{font-weight:800;letter-spacing:.04em;color:var(--deep)}
.idol__sub{font-size:.76rem;color:var(--muted)}
.idol__lead{margin:0 0 14px;font-size:.82rem;color:var(--muted)}
.dnd{list-style:none;margin:0;padding:0;counter-reset:rank}
.dnd__item{
position:relative;display:flex;align-items:center;gap:12px;
background:#fff;border:1px solid var(--line);border-radius:14px;
padding:10px 14px 10px 40px;margin-bottom:10px;cursor:grab;
box-shadow:0 4px 14px rgba(232,106,150,.08);transition:box-shadow .2s,transform .12s;
}
.dnd__item::before{
counter-increment:rank;content:counter(rank);
position:absolute;left:12px;top:50%;transform:translateY(-50%);
width:20px;height:20px;border-radius:50%;background:var(--pink);color:var(--deep);
font-size:.72rem;font-weight:800;display:grid;place-items:center;
}
.dnd__item:active{cursor:grabbing}
.dnd__item.dragging{opacity:.5;box-shadow:0 10px 24px rgba(232,106,150,.2)}
.dnd__grip{color:var(--pink);font-size:1rem;flex:none}
.dnd__thumb{width:42px;height:42px;border-radius:10px;background-size:cover;background-position:center;flex:none}
.dnd__body{display:flex;flex-direction:column;gap:2px}
.dnd__title{font-weight:700;font-size:.92rem}
.dnd__meta{font-size:.72rem;color:var(--muted)}
/* ドロップ位置のヒント線 */
.dnd__item.drop-before{box-shadow:0 -3px 0 var(--deep)}
.dnd__item.drop-after{box-shadow:0 3px 0 var(--deep)}
@media (prefers-reduced-motion:reduce){.dnd__item{transition:none}}
JavaScript
// HTML5 Drag and Drop で推し曲ランキングを並べ替え
const list = document.getElementById('list');
let dragging = null;
if (list) {
// ドロップ位置ヒントをクリア
const clearHints = () => {
list.querySelectorAll('.drop-before,.drop-after')
.forEach((el) => el.classList.remove('drop-before', 'drop-after'));
};
list.addEventListener('dragstart', (e) => {
const item = e.target.closest('.dnd__item');
if (!item) return;
dragging = item;
requestAnimationFrame(() => item.classList.add('dragging')); // 次フレームで半透明化
e.dataTransfer.effectAllowed = 'move';
});
list.addEventListener('dragend', () => {
if (dragging) dragging.classList.remove('dragging');
clearHints();
dragging = null;
});
list.addEventListener('dragover', (e) => {
e.preventDefault(); // ドロップを許可
const over = e.target.closest('.dnd__item');
if (!over || over === dragging) return;
clearHints();
// 上半分/下半分で挿入位置を決定
const rect = over.getBoundingClientRect();
const after = e.clientY > rect.top + rect.height / 2;
over.classList.add(after ? 'drop-after' : 'drop-before');
});
list.addEventListener('drop', (e) => {
e.preventDefault();
const over = e.target.closest('.dnd__item');
if (!over || !dragging || over === dragging) return;
const rect = over.getBoundingClientRect();
const after = e.clientY > rect.top + rect.height / 2;
over.insertAdjacentElement(after ? 'afterend' : 'beforebegin', dragging);
clearHints();
});
}
コード
HTML
<!-- ドラッグ並べ替え:HTML5 Drag&Drop でリストの順序を入れ替え -->
<div class="board">
<h2 class="board__title">やることリスト</h2>
<p class="board__hint">行をドラッグして優先順位を入れ替えられます。</p>
<ul class="dnd" id="list">
<li class="dnd__item" draggable="true"><span class="dnd__grip" aria-hidden="true">⋮⋮</span>市場リサーチをまとめる</li>
<li class="dnd__item" draggable="true"><span class="dnd__grip" aria-hidden="true">⋮⋮</span>ワイヤーフレーム作成</li>
<li class="dnd__item" draggable="true"><span class="dnd__grip" aria-hidden="true">⋮⋮</span>デザインレビュー依頼</li>
<li class="dnd__item" draggable="true"><span class="dnd__grip" aria-hidden="true">⋮⋮</span>実装タスクへ分解</li>
<li class="dnd__item" draggable="true"><span class="dnd__grip" aria-hidden="true">⋮⋮</span>リリースノートを書く</li>
</ul>
</div>
CSS
:root{
--bg:#101826;
--card:#18233a;
--item:#1f2c47;
--accent:#34d399;
--text:#dbe4f3;
--muted:#8a97b3;
}
*{box-sizing:border-box}
body{
margin:0;min-height:100vh;
display:grid;place-items:center;padding:26px 16px;
font-family:"Segoe UI",system-ui,sans-serif;color:var(--text);
background:radial-gradient(700px 360px at 50% -10%,#1d3a4a,transparent),var(--bg);
}
.board{width:min(440px,100%)}
.board__title{margin:0 0 4px;font-size:1.15rem;display:flex;align-items:center;gap:10px}
.board__title::before{
content:"";width:8px;height:20px;border-radius:4px;
background:linear-gradient(var(--accent),#3b82f6);
}
.board__hint{margin:0 0 16px;color:var(--muted);font-size:.85rem}
.dnd{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:9px}
.dnd__item{
display:flex;align-items:center;gap:12px;
padding:14px 16px;
background:var(--item);
border:1px solid #2a3a5c;border-radius:12px;
cursor:grab;user-select:none;
font-size:.94rem;
transition:transform .18s,box-shadow .25s,background .2s,opacity .2s;
}
.dnd__item:hover{background:#243454}
/* つかんでいる要素 */
.dnd__item.dragging{
opacity:.4;cursor:grabbing;
box-shadow:0 14px 30px -12px rgba(0,0,0,.6);
}
/* ドロップ先のヒント線 */
.dnd__item.drop-before{box-shadow:inset 0 3px 0 -1px var(--accent)}
.dnd__item.drop-after{box-shadow:inset 0 -3px 0 -1px var(--accent)}
.dnd__grip{color:var(--muted);letter-spacing:-2px;font-size:1.1rem;line-height:1}
@media (prefers-reduced-motion:reduce){.dnd__item{transition:none}}
JavaScript
// HTML5 Drag and Drop で並べ替え
const list = document.getElementById('list');
let dragging = null;
if (list) {
// ドロップ位置ヒントをクリア
const clearHints = () => {
list.querySelectorAll('.drop-before,.drop-after')
.forEach((el) => el.classList.remove('drop-before', 'drop-after'));
};
list.addEventListener('dragstart', (e) => {
const item = e.target.closest('.dnd__item');
if (!item) return;
dragging = item;
// 次フレームで半透明化(ゴースト画像はそのまま)
requestAnimationFrame(() => item.classList.add('dragging'));
e.dataTransfer.effectAllowed = 'move';
});
list.addEventListener('dragend', () => {
if (dragging) dragging.classList.remove('dragging');
clearHints();
dragging = null;
});
list.addEventListener('dragover', (e) => {
e.preventDefault(); // ドロップを許可
const over = e.target.closest('.dnd__item');
if (!over || over === dragging) return;
clearHints();
// マウス位置が要素の上半分か下半分かで挿入位置を決定
const rect = over.getBoundingClientRect();
const after = e.clientY > rect.top + rect.height / 2;
over.classList.add(after ? 'drop-after' : 'drop-before');
});
list.addEventListener('drop', (e) => {
e.preventDefault();
const over = e.target.closest('.dnd__item');
if (!over || !dragging || over === dragging) return;
const rect = over.getBoundingClientRect();
const after = e.clientY > rect.top + rect.height / 2;
over.insertAdjacentElement(after ? 'afterend' : 'beforebegin', dragging);
clearHints();
});
}
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「ドラッグ並べ替え」の効果を追加してください。
# 追加してほしい効果
ドラッグ並べ替え(UIコンポーネント)
HTML5 Drag and Dropでリスト項目の順序を入れ替え。挿入位置のヒント線付きで、タスクの優先順位付けや並び替えUIに使えます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- ドラッグ並べ替え:HTML5 Drag&Drop でリストの順序を入れ替え -->
<div class="board">
<h2 class="board__title">やることリスト</h2>
<p class="board__hint">行をドラッグして優先順位を入れ替えられます。</p>
<ul class="dnd" id="list">
<li class="dnd__item" draggable="true"><span class="dnd__grip" aria-hidden="true">⋮⋮</span>市場リサーチをまとめる</li>
<li class="dnd__item" draggable="true"><span class="dnd__grip" aria-hidden="true">⋮⋮</span>ワイヤーフレーム作成</li>
<li class="dnd__item" draggable="true"><span class="dnd__grip" aria-hidden="true">⋮⋮</span>デザインレビュー依頼</li>
<li class="dnd__item" draggable="true"><span class="dnd__grip" aria-hidden="true">⋮⋮</span>実装タスクへ分解</li>
<li class="dnd__item" draggable="true"><span class="dnd__grip" aria-hidden="true">⋮⋮</span>リリースノートを書く</li>
</ul>
</div>
【CSS】
:root{
--bg:#101826;
--card:#18233a;
--item:#1f2c47;
--accent:#34d399;
--text:#dbe4f3;
--muted:#8a97b3;
}
*{box-sizing:border-box}
body{
margin:0;min-height:100vh;
display:grid;place-items:center;padding:26px 16px;
font-family:"Segoe UI",system-ui,sans-serif;color:var(--text);
background:radial-gradient(700px 360px at 50% -10%,#1d3a4a,transparent),var(--bg);
}
.board{width:min(440px,100%)}
.board__title{margin:0 0 4px;font-size:1.15rem;display:flex;align-items:center;gap:10px}
.board__title::before{
content:"";width:8px;height:20px;border-radius:4px;
background:linear-gradient(var(--accent),#3b82f6);
}
.board__hint{margin:0 0 16px;color:var(--muted);font-size:.85rem}
.dnd{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:9px}
.dnd__item{
display:flex;align-items:center;gap:12px;
padding:14px 16px;
background:var(--item);
border:1px solid #2a3a5c;border-radius:12px;
cursor:grab;user-select:none;
font-size:.94rem;
transition:transform .18s,box-shadow .25s,background .2s,opacity .2s;
}
.dnd__item:hover{background:#243454}
/* つかんでいる要素 */
.dnd__item.dragging{
opacity:.4;cursor:grabbing;
box-shadow:0 14px 30px -12px rgba(0,0,0,.6);
}
/* ドロップ先のヒント線 */
.dnd__item.drop-before{box-shadow:inset 0 3px 0 -1px var(--accent)}
.dnd__item.drop-after{box-shadow:inset 0 -3px 0 -1px var(--accent)}
.dnd__grip{color:var(--muted);letter-spacing:-2px;font-size:1.1rem;line-height:1}
@media (prefers-reduced-motion:reduce){.dnd__item{transition:none}}
【JavaScript】
// HTML5 Drag and Drop で並べ替え
const list = document.getElementById('list');
let dragging = null;
if (list) {
// ドロップ位置ヒントをクリア
const clearHints = () => {
list.querySelectorAll('.drop-before,.drop-after')
.forEach((el) => el.classList.remove('drop-before', 'drop-after'));
};
list.addEventListener('dragstart', (e) => {
const item = e.target.closest('.dnd__item');
if (!item) return;
dragging = item;
// 次フレームで半透明化(ゴースト画像はそのまま)
requestAnimationFrame(() => item.classList.add('dragging'));
e.dataTransfer.effectAllowed = 'move';
});
list.addEventListener('dragend', () => {
if (dragging) dragging.classList.remove('dragging');
clearHints();
dragging = null;
});
list.addEventListener('dragover', (e) => {
e.preventDefault(); // ドロップを許可
const over = e.target.closest('.dnd__item');
if (!over || over === dragging) return;
clearHints();
// マウス位置が要素の上半分か下半分かで挿入位置を決定
const rect = over.getBoundingClientRect();
const after = e.clientY > rect.top + rect.height / 2;
over.classList.add(after ? 'drop-after' : 'drop-before');
});
list.addEventListener('drop', (e) => {
e.preventDefault();
const over = e.target.closest('.dnd__item');
if (!over || !dragging || over === dragging) return;
const rect = over.getBoundingClientRect();
const after = e.clientY > rect.top + rect.height / 2;
over.insertAdjacentElement(after ? 'afterend' : 'beforebegin', dragging);
clearHints();
});
}
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。