JSFiddle - React, Tailwind, and code Playground

by Andrei Gheorghiu

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>

<div class="dropzone">
  <div id="draggable" draggable="true" ondragstart="event.dataTransfer.setData('text/plain',null)"></div>
</div>
<div class="dropzone"></div>
<div class="dropzone"></div>
<div class="dropzone"></div>
<div id="app">
  <table>
    <thead><tr>
      <th>Count</th>
      <th>Min</th>
      <th>Max</th>
      <th>Avg</th>
    </tr></thead>
    <tbody><tr>
      <td v-text="c.length" />
      <td v-text="min" />
      <td v-text="max" />
      <td v-text="avg" />
    </tr></tbody>
  </table>
  <p>
    <em>* Values in miliseconds</em>
  </p>
</div>
<div class="visual-test">
  <h4>
    <code>250ms</code>
  </h4>
  <h4>
    <span>No delay</span>
    <span>Delayed</span>
  </h4>
  <div id="instant"></div>
  <div id="delayed"></div>
</div>

CSS

#draggable {
  width: 200px;
  height: 20px;
  text-align: center;
  background: white;
}

.dropzone {
  width: 200px;
  height: 20px;
  background: blueviolet;
  margin-bottom: 10px;
  padding: 10px;
}
body {
  background-color: #eee;
}
table {
  background-color: white;
  width: 220px;
  padding: 10px;
  
}
td, th {
  font-family: monospace;
  text-align: right;
  width: 25%;
}
.visual-test {
  display: flex;
  justify-content: space-around;
  flex-wrap: wrap;
  position: fixed;
  right: 0;
  top: 0;
  width: 250px;
}
h4 {
  flex: 100%;
  margin: 1rem 0;
  display: flex;
  justify-content: space-around;
}
#delayed, #instant {
  width: 40px;
  height: 40px;
  background-color: #212121;
  border-radius: 20px;
}
#delayed.active, #instant.active {
  transform: translateY(100px);
}
#delayed.active {
  transition-delay: 250ms;
}

JavaScript

var dragged;
const delayed = document.querySelector('#delayed');
const instant = document.querySelector('#instant');
var t_0 = performance.now();
var t_1;
var t_2;
var collector = [];

document.addEventListener("dragstart", function(event) {
  dragged = event.target;
  t_0 = performance.now();
  t_1 = undefined;
  t_2 = undefined;
}, false);

document.addEventListener("dragend", function(event) {
  t_2 = performance.now() - t_0;
  if (t_2 && t_1) {
  	delayed.classList.add('active');
		instant.classList.add('active');
    collector.push(t_2 - t_1);
  }
}, false);

document.addEventListener("dragover", function(event) {
  event.preventDefault();
}, false);

document.addEventListener("drop", function(event) {
  t_1 = performance.now() - t_0;
  event.preventDefault();
  if (event.target.className == "dropzone") {
    dragged.parentNode.removeChild(dragged);
    event.target.appendChild(dragged);
  }
}, false);

new Vue({
  el: '#app',
  data: () => ({
    c: []
  }),
  computed: {
    min() {
      return this.c.length ? this.log(Math.min(...this.c)) : undefined
    },
    max() {
      return this.c.length ? this.log(Math.max(...this.c)) : undefined
    },
    avg() {
      return this.c.length ?
        this.log(this.c.reduce((a, b) => a + b, 0) / this.c.length) :
        undefined
    }
  },
  mounted() {
    document.addEventListener('drop', () => {
      setTimeout(() => {
        this.c = window.collector;
      });
      setTimeout(() => {
      	delayed.classList.remove('active');
				instant.classList.remove('active');
      }, '1e3')
    })
  },
  methods: {
    log(n) {
    	return Math.round(n * 1e3)/1e3
    }
  }
})