JSFiddle - React, Tailwind, and code Playground

by Andy You

HTML

<div class="columns">
  <div class="column" id="col-1">
    <div class="overlay" draggable="true"></div>
    <header id="header-1" draggable="false">A</header>
  </div>
  <div class="column"  id="col-2">
    <div class="overlay" draggable="true"></div>
    <header id="header-2" draggable="false">B</header>
  </div>
  <div class="column" id="col-3">
    <div class="overlay" draggable="true"></div>
    <header id="header-3" draggable="false">C</header>
  </div>
</div>

SCSS

/**
 * 防止可以被拖移的元素內部的文字被選取造成局部拖移
 */
[draggable] {
  -moz-user-select: none;
  -khtml-user-select: none;
  -webkit-user-select: none;
  user-select: none;
  /**
   * 舊版 WebKit 需要此設定讓元素可以被拖移
   */
  -khtml-user-drag: element;
  -webkit-user-drag: element;
}

.column {
  width: 150px;
  height: 150px;
  float: left;
  border: 10px solid pink;
  background-color: pink;
  margin-right: 5px;
  text-align: center;
  cursor: move;
  position: relative;
  box-sizing: border-box;
  background-origin: border-box;
  margin-top: -10px;
  margin-bottom: -10px;
  
  .overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 150px;
    height: 150px;
    background: rgba(255, 255, 0, .5);
  }
  
  header {
    text-shadow: #fff 0 1px;
  }
  
  &.over {
    border: 10px dashed gray;
  }
}

Babel + JSX

var columns = document.querySelectorAll('.columns .column')

;[...columns].forEach( (col) => { 
	col.addEventListener('dragstart', dragStart, false)
  col.addEventListener('dragover', dragOver, false)
  col.addEventListener('dragenter', dragEnter, false)
  col.addEventListener('dragleave', dragLeave, false)
  
  col.addEventListener('drop', drop, false)
  col.addEventListener('dragend', dragEnd, false)

  /*;[...col.childNodes].forEach((el) => {
  	el.addEventListener('dragsover', (e) => {
    		console.log('child execute start', e.target.id);
    		e.stopPropagation();
        e.preventDefault();
      	return false;
      }, false)
  	el.addEventListener('dragenter', (e) => {
    		console.log('child execute enter', e.target.id);
    		e.stopPropagation();
        e.preventDefault();
      	return false;
      }, false)
  })*/
})


var dragSrcEl;

function dragStart (e) {
	console.log('drag start')
  this.style.opacity = .4
  dragSrcEl = this
  e.dataTransfer.effectAllowed = 'move'
  e.dataTransfer.setData('text/html', this.innerHTML)
}

function dragOver (e) {
  if (e.preventDefault) {
  	// 觸發 drop 需要這行
  	e.preventDefault()
  }
  return false
}

function dragEnter (e) {
	console.log('enter')
	this.classList.add('over')
  this.parentNode.insertBefore(this, dragSrcEl)
}

function dragLeave (e) {
	console.log('leave')
  this.classList.remove('over')
}

function drop (e) {
	if (e.stopPropagation) {
  	e.stopPropagation()
  }
  
  if (dragSrcEl != this) {
  	dragSrcEl.innerHTML = this.innerHTML
    this.innerHTML = e.dataTransfer.getData('text/html')
  }
  
  return false
}

function dragEnd (e) {
  this.style.opacity = 1
  
	;[...columns].forEach( (col) => { 
    col.classList.remove('over')
  })
}

function _css (el, prop, val) {
		var style = el && el.style

		if (style) {
			if (val === void 0) {
				if (document.defaultView && document.defaultView.getComputedStyle) {
					val = document.defaultView.getComputedStyle(el, '');
				} else if (el.currentStyle)...