JSFiddle - React, Tailwind, and code Playground

by sazakharov

HTML

<div class="grid">
  <div class="grid_inner">
    <div class="item image1"></div>
    <div class="item image2"></div>
    <div class="item image3"></div>
    <div class="item image4"></div>
    <div class="item image5"></div>
    <div class="item image6"></div>
    <div class="item image7"></div>
    <div class="item image8"></div>
    <div class="item image9"></div>
    <div class="item image10"></div>
    <div class="item image11"></div>
    <div class="item image12"></div>
    <div class="item image13"></div>
    <div class="item image14"></div>
    <div class="item image15"></div>
    <div class="item image16"></div>
    <div class="item image17"></div>
    <div class="item image18"></div>
    <div class="item image19"></div>
    <div class="item image20"></div>
    <div class="item image21"></div>
  </div>
</div>

CSS

* {
  box-sizing: border-box;
}

html, body {
  height: 100%;
  overflow: hidden;
}

body {
  margin: 0;
  display: flex;
}

.grid {
  width: 90%;
  margin: auto;
  padding: 10px;
  position: relative;
  overflow: hidden;
  background-color: #333;
}

.grid_inner {
  display: flex;
  flex-wrap: wrap;
  margin: -10px 0 0 -10px;
}

.item {
  margin: 10px 0 0 10px;
  width: calc(25% - 10px);
  height: 100px;
  transform: translate(0, 0);
  cursor: pointer;
 
  background-repeat: no-repeat;
  background-size: cover;
  background-position: 50% 50%;
}

.item-opened {
  z-index: 999;
}

.image1, .image10, .image21, .image7 {
  background-image: url('https://images.unsplash.com/photo-1494256997604-768d1f608cac?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=801&q=80');
}
.image2, .image9, .image20, .image14 {
  background-image: url('https://images.unsplash.com/photo-1526336024174-e58f5cdd8e13?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=334&q=80');
}
.image3, .image15, .image12, .image13 {
  background-image: url('https://images.unsplash.com/photo-1518843025960-d60217f226f5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=334&q=80');
}
.image4, .image11, .image19 {
  background-image: url('https://images.unsplash.com/photo-1496890666403-e6cf521841e6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEzMDA0fQ&auto=format&fit=crop&w=1500&q=80');
}

.image5, .image8, .image17 {
  background-image: url('https://images.unsplash.com/photo-1516125073169-9e3ecdee83e7?ixlib=rb-1.2.1&auto=format&fit=crop&w=1500&q=80');
}
.image6, .image18, .image16 {
  background-image: url('https://images.unsplash.com/photo-1466618786657-4df462be674e?ixlib=rb-1.2.1&auto=format&fit=crop&w=1500&q=80');
}

JavaScript

const grid = document.querySelector('.grid');
const items = document.querySelectorAll('.grid .item');

const onSelect = (function(items, transitionDuration = 0) {
  let opened = null;
  let neighbors = [];
  let closingTimer = null;

  items = Array.from(items);
  items.forEach(el => {
  	el.style.transitionDuration = transitionDuration + 'ms';
    el.style.transitionProperty = 'transform';
  });
  
  function calcBox(el) {
    const parrentBox = el.offsetParent.getBoundingClientRect();
    const elBox = el.getBoundingClientRect();
 
    return {
      top: elBox.top - parrentBox.top,
      left: elBox.left - parrentBox.left,
      right: parrentBox.right - elBox.right,
      bottom: parrentBox.bottom - elBox.bottom,
    };
  }
  
  function dublicateItem(originEl) {
    const cloneEl = originEl.cloneNode(true);
    cloneEl.classList.add('item-opened');
    cloneEl.style.margin = 0;
    cloneEl.style.width = 'auto';
    cloneEl.style.height = 'auto';
    cloneEl.style.position = 'absolute';

    cloneEl.style.transitionDuration = transitionDuration + 'ms';
    cloneEl.style.transitionProperty = 'top, left, right, bottom';

    const cloneBox = calcBox(originEl);
    cloneEl.style.top = cloneBox.top + 'px';
    cloneEl.style.left = cloneBox.left + 'px';
    cloneEl.style.bottom = cloneBox.bottom + 'px';
    cloneEl.style.right = cloneBox.right + 'px';

    originEl.parentElement.insertBefore(cloneEl, originEl);
    originEl.style.visibility = 'hidden';

    return { originEl, cloneEl };
  }

  function openItem(target) {
    opened = dublicateItem(target);

    const parrentRect = target.offsetParent.getBoundingClientRect();
    const targetRect = opened.cloneEl.getBoundingClientRect();
    const targetCx = targetRect.left - targetRect.width / 2;
    const targetCy = targetRect.top - targetRect.height / 2;

    neighbors = items.filter(el => el !== target);
    
    for (let i = 0; i < neighbors.length; i++) {
      const el = neighbors[i];
      const rect =...