<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Menu Switcher with Sortable.js</title>
<style>
body {
font-family: sans-serif;
background: #f8f9fa;
padding: 40px;
}
.c-menu-switcher {
display: flex;
align-items: flex-start;
gap: 20px;
}
.c-menu-switcher__menu {
width: 220px;
min-height: 200px;
background: #fff;
border: 2px solid #ccc;
border-radius: 12px;
padding: 10px;
display: flex;
flex-direction: column;
gap: 8px;
}
.c-menu-switcher__item {
padding: 8px 10px;
background: #f0f0f0;
border-radius: 8px;
cursor: grab;
transition: 0.2s;
}
.c-menu-switcher__item.is-selected {
background: #007bff;
color: #fff;
}
.c-menu-switcher__buttons {
display: flex;
flex-direction: column;
gap: 10px;
justify-content: center;
}
button {
padding: 8px 12px;
border: none;
border-radius: 8px;
background: #007bff;
color: #fff;
cursor: pointer;
}
button:hover {
background: #0056b3;
}
/* Sortable.jsドラッグ中 */
.sortable-ghost {
opacity: 0.4;
}
.is-dragging {
background: #cce5ff !important;
}
</style>
</head>
<body>
<div class="c-menu-switcher js-menu-switcher">
<div class="c-menu-switcher__menu c-menu-switcher__menu--visible js-visible">
<div class="c-menu-switcher__item" data-id="home">ホーム</div>
<div class="c-menu-switcher__item" data-id="news">ニュース</div>
<div class="c-menu-switcher__item" data-id="contact">お問い合わせ</div>
</div>
<div class="c-menu-switcher__buttons">
<button class="js-add-btn">→ 追加</button>
<button class="js-remove-btn">← 削除</button>
</div>
<div class="c-menu-switcher__menu c-menu-switcher__menu--hidden js-hidden">
<div class="c-menu-switcher__item" data-id="about">会社情報</div>
<div class="c-menu-switcher__item" data-id="service">サービス</div>
<div class="c-menu-switcher__item" data-id="blog">ブログ</div>
</div>
</div>
<!-- Sortable.js CDN -->
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
function moveItem(item, targetMenu) {
if (!item || !targetMenu) return;
targetMenu.appendChild(item);
item.classList.remove('is-selected');
}
function initSelection(switcher) {
switcher.addEventListener('click', (e) => {
if (!e.target.classList.contains('c-menu-switcher__item')) return;
const menu = e.target.closest('.c-menu-switcher__menu');
menu.querySelectorAll('.c-menu-switcher__item').forEach(i => i.classList.remove('is-selected'));
e.target.classList.add('is-selected');
});
}
function sortableOptionsFactory(groupName) {
return {
group: {
name: groupName,
pull: true,
put: true
},
animation: 150,
ghostClass: 'sortable-ghost',
chosenClass: 'is-dragging',
onEnd: (evt) => {
console.log('Moved:', evt.item.dataset.id, '→', evt.to.className);
}
};
}
const switchers = document.querySelectorAll('.js-menu-switcher');
switchers.forEach((switcher, index) => {
initSelection(switcher);
const visibleMenu = switcher.querySelector('.js-visible');
const hiddenMenu = switcher.querySelector('.js-hidden');
const addBtn = switcher.querySelector('.js-add-btn');
const removeBtn = switcher.querySelector('.js-remove-btn');
// Sortable初期化(上下ドラッグ+左右移動OK)
const opts = sortableOptionsFactory('menuGroup-' + index);
new Sortable(visibleMenu, opts);
new Sortable(hiddenMenu, opts);
// ボタンでの移動
addBtn.addEventListener('click', () => {
const selected = hiddenMenu.querySelector('.is-selected');
if (selected) moveItem(selected, visibleMenu);
});
removeBtn.addEventListener('click', () => {
const selected = visibleMenu.querySelector('.is-selected');
if (selected) moveItem(selected, hiddenMenu);
});
});
});
</script>
</body>
</html>
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme