JSFiddle - React, Tailwind, and code Playground

by Victor Alekseev

HTML

<div id="grid">
  <div class="site">
    <a class="link" href="#">
      <span class="thumbnail" style="color:'+randomColor+'">Item 1</span>
    </a>
    <span class="title">Item 1</span>
  </div>
  <div class="site">
    <a class="link" href="#">
      <span class="thumbnail" style="color:'+randomColor+'">Item 2</span>
    </a>
    <span class="title">Item 2</span>
  </div>
  <div class="site">
    <a class="link" href="#">
      <span class="thumbnail" style="color:'+randomColor+'">Item 3</span>
    </a>
    <span class="title">Item 3</span>
  </div>
  <div class="site">
    <a class="link" href="#">
      <span class="thumbnail" style="color:'+randomColor+'">Item 4</span>
    </a>
    <span class="title">Item 4</span>
  </div>
  <div class="site">
    <a class="link" href="#">
      <span class="thumbnail" style="color:'+randomColor+'">Item 5</span>
    </a>
    <span class="title">Item 5</span>
  </div>
</div>

CSS

#grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
	grid-row-gap: 30px;
	grid-column-gap: 30px;
	margin: 60px 60px 60px 120px;
	align-content: start;
	justify-content: space-between;
}
.site {
	position: relative;
	width: 190px;
  height: 124px;
}
.ghost {
	opacity: .4;
}
.link {
	display: block;
	max-height: 100%;
	max-width: 100%;
	border-radius: 5px;
	box-shadow: 0 7px 15px rgba(0,0,0,.5);
	overflow: hidden;
	text-decoration: none;
  background: #666;
}
.thumbnail {
	max-height: 100%;
	max-width: 100%;
	display: block;
	line-height: 124px;
	text-align: center;
	font-size: xx-large;
	font-weight: 600;
	filter: invert(100%);
}
.title {
	display: block;
	text-align: center;
	color: #eee;
	padding: 10px 0 0 0;
	width: 190px;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	font-size: 14px;
}

JavaScript

function sortable(rootEl, onUpdate){
    var dragEl, nextEl;
    
    // Делаем всех детей перетаскиваемыми
    [].slice.call(rootEl.children).forEach(function (itemEl){
        itemEl.draggable = true;
    });
    
    // Фнукция отвечающая за сортировку
    function _onDragOver(evt){
        evt.preventDefault();
        evt.dataTransfer.dropEffect = 'move';
       
        var target = evt.target;
        if( target && target !== dragEl && target.nodeName == 'DIV' ){
            // Сортируем
            var rect = target.getBoundingClientRect();
            var next = (evt.clientY - rect.top)/(rect.bottom - rect.top) > .5;
            rootEl.insertBefore(dragEl, next && target.nextSibling || target);
        }
    }
    
    // Окончание сортировки
    function _onDragEnd(evt){
        evt.preventDefault();
       
        dragEl.classList.remove('ghost');
        rootEl.removeEventListener('dragover', _onDragOver, false);
        rootEl.removeEventListener('dragend', _onDragEnd, false);

        if( nextEl !== dragEl.nextSibling ){
            // Сообщаем об окончании сортировки
            onUpdate(dragEl);
        }
    }
    
    // Начало сортировки
    rootEl.addEventListener('dragstart', function (evt){
        dragEl = evt.target; // Запоминаем элемент который будет перемещать
        nextEl = dragEl.nextSibling;
        
        // Ограничиваем тип перетаскивания
        evt.dataTransfer.effectAllowed = 'move';
        evt.dataTransfer.setData('Text', dragEl.textContent);

        // Пописываемся на события при dnd
        rootEl.addEventListener('dragover', _onDragOver, false);
        rootEl.addEventListener('dragend', _onDragEnd, false);

        setTimeout(function (){
            // Если выполнить данное действие без setTimeout, то
            // перетаскиваемый объект, будет иметь этот класс.
            dragEl.classList.add('ghost');
        }, 0)
    }, false);
}
                        
// Используем                    
sortable(...