<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="pointer.css" />
</head>
<body>
<div class="wrapper">
<header>
<h1>Drag and Drop</h1>
</header>
<main>
<div class="list-wrapper">
<div class="column">
<div class="column-header">To Do</div>
<div class="card-wrapper">
<div class="card">
<div class="header">
<div class="title">
<span>๐โโ๏ธ ์ด๋ํ๊ธฐ</span>
</div>
</div>
<div class="body">
<p>์์นจ์ ์ผ์ด๋ ์ด๋์ ํ๋ค.</p>
</div>
<div class="caption">padosum</div>
</div>
<div class="card">
<div class="header">
<div class="title">
<span>๐งโ๐ณ ์ ์ฌ ๋ง๋ค๊ธฐ</span>
</div>
</div>
<div class="body">
<p>๋ฎ๋ฐฅ์ ๋ง๋ ๋ค.</p>
</div>
<div class="caption">padosum</div>
</div>
</div>
</div>
<div class="column">
<div class="column-header">Doing</div>
<div class="card-wrapper">
<div class="card">
<div class="header">
<div class="title">๐โโ๏ธ ์ํํ๊ธฐ</div>
</div>
<div class="body">
<p>๐โโ๏ธ ๐โโ๏ธ ๐</p>
</div>
<div class="caption">padosum</div>
</div>
</div>
</div>
<div class="column">
<div class="column-header">Done</div>
<div class="card-wrapper">
<div class="card">
<div class="header">
<div class="title">๐งโ๐ ๊ณต๋ถํ๊ธฐ</div>
</div>
<div class="body">
<p>
https://developer.mozilla.org/ko/docs/Web/API/HTMLElement/drag_event
</p>
</div>
<div class="caption">padosum</div>
</div>
</div>
</div>
</div>
<div class="ghost"></div>
</main>
<footer></footer>
</div>
<script src="pointer.js"></script>
</body>
</html>
* {
box-sizing: border-box;
}
html {
height: 100%;
}
body {
height: 100%;
background-color: #f5f5f7;
}
main {
height: 100%;
}
.wrapper {
padding: 0 80px;
height: 100%;
}
.list-wrapper {
display: flex;
gap: 20px;
}
.column {
width: 350px;
display: flex;
flex-direction: column;
gap: 10px;
}
.column > .column-header {
padding: 0 8px;
}
.column > .card-wrapper {
display: flex;
flex-direction: column;
gap: 10px;
padding-bottom: 100px;
}
.card {
user-select: none;
padding: 16px;
border-radius: 6px;
background-color: white;
box-shadow: 0px 1px 30px rgb(224 224 224 / 30%);
cursor: move;
}
.card.dragging {
opacity: 0.8;
box-shadow: 0px 0px 4px rgba(204, 204, 204, 0.5),
0px 2px 4px rgba(0, 0, 0, 0.25);
}
.card.afterimage {
opacity: 0.4;
border: 1px solid #0075de;
}
.card .title {
font-size: 16px;
font-weight: 700;
}
.card .body {
font-size: 14px;
overflow-wrap: break-word;
}
.card .caption {
font-size: 12px;
color: #bdbdbd;
}
.ghost {
top: 0;
left: 0;
position: absolute;
width: 350px;
}
document.addEventListener('DOMContentLoaded', e => {
const list = document.querySelector('.list-wrapper')
let pointerDown = false
let shiftX = 0
let shiftY = 0
window.addEventListener(
'pointerdown',
({ clientX, clientY, pageX, pageY, target }) => {
const card = target.closest('.card')
if (!card) return
const cloneCard = card.cloneNode(true)
cloneCard.classList.add('dragging')
const ghost = document.querySelector('.ghost')
ghost.appendChild(cloneCard)
shiftX = clientX - card.getBoundingClientRect().left
shiftY = clientY - card.getBoundingClientRect().top
ghost.style.cssText = `width: ${
card.offsetWidth
}px; transform: translateX(${pageX - shiftX}px) translateY(${
pageY - shiftY
}px)`
pointerDown = true
card.classList.add('afterimage')
}
)
window.addEventListener(
'pointermove',
({ clientX, clientY, pageX, pageY, target }) => {
if (!pointerDown) {
return
}
const ghost = document.querySelector('.ghost')
ghost.hidden = true
const pointedEl = document.elementFromPoint(clientX, clientY)
const closestCard = pointedEl.closest('.card')
const column = pointedEl.closest('.column')
ghost.hidden = false
ghost.style.cssText = `width: ${
ghost.offsetWidth
}px; transform: translateX(${pageX - shiftX}px) translateY(${
pageY - shiftY
}px)`
if (!column) {
return
}
// ์ฅ๊ณ ์๋ ์นด๋ ๋ณต์ฌ
const placeCard = ghost.firstChild.cloneNode(true)
placeCard.classList.replace('dragging', 'afterimage')
const fromCard = document.querySelector('.afterimage')
if (closestCard) {
if (closestCard.classList.contains('afterimage')) {
return
}
closestCard.before(placeCard)
} else {
const cardWrapper = column.querySelector('.card-wrapper')
cardWrapper.appendChild(placeCard)
}
fromCard.remove()
}
)
window.addEventListener('pointerup', e => {
if (!pointerDown) {
return
}
pointerDown = false
const ghost = document.querySelector('.ghost')
ghost.innerHTML = ''
const activeCard = document.querySelector('.afterimage')
activeCard.classList.remove('afterimage')
})
})
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.