<!DOCTYPE html>
<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')
  })
})

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.