#app
	.control
		.control__input-element
			input.control__input(
				type="number",
				v-model="ratioWidth",
				placeholder="aspect"
			)
			span.control__dot .
			input.control__input(
				type="number",
				v-model="ratioHeight",
				placeholder="ratio"
			)
		.control__input-element
			input.control__input(
				type="number",
				v-model="columns",
				placeholder="Columns"
			)

	transition-group.table(tag="div", name="table", :style="tableStyles")
		.table__item(
			@drop="drop",
			@dragleave="dragLeave",
			@dragenter="dragEnter",
			@dragover="allowDrop",
			:style="itemStyles",
			v-for="(image, index) in images",
			:key="image.id",
			:data-id="image.id"
		)
			img.table__img(@dragstart="drag", :src="image.src", :data-id="image.id")
View Compiled
$tableColor: #fff;

html, body {
	height: 100%;
}

body {
	background-color: #333;
	display: flex;
	align-items: center;
	justify-content: center;
}

.table-wrap {
	position: relative;
	z-index: 2;
}

.table {
		min-width: 300px;
		display: grid;
		grid-gap: 10px;
	 padding: 10px;
		background-color: $tableColor;
		box-shadow: 5px 3px 4px rgba(0, 0, 0, .8);
	
	&__item {
		position: relative;
	}
	
	&__img {
 	position: absolute;
  object-fit: cover;
		left: 0;
		top: 0;
  width: 100%;
  height: 100%;
		box-sizing: border-box;
		
		&.holder {
			border: 10px dashed $tableColor;
		}

		&:hover {
			border: 2px dashed red;
			cursor: move;
		}
	}
}

.table-enter-active,
.table-leave-active,
.table-move {
  transition: 500ms cubic-bezier(0.59, 0.12, 0.34, 0.95);
  transition-property: opacity, transform;
}

.table-enter {
  opacity: 0;
  transform: translateX(50px) scaleY(0.5);
}

.table-enter-to {
  opacity: 1;
  transform: translateX(0) scaleY(1);
}

.table-leave-active {
  position: absolute;
}

.table-leave-to {
  opacity: 0;
  transform: scaleY(0);
  transform-origin: center top;
}

.control {
	width: 100px;
	padding: 10px;
	background-color: #000;
	position: fixed;
	z-index: 99;
	top: 50%;
	transform: translatey(-50%);
	left: 0;
	display: flex;
	flex-direction: column;
	
	&__dot {
			display: inline-block;
			color: #fff;
			font-size: 20px;
	}
	
	&__input-element {
			display: flex;
			align-items: flex-end;
	}
	
	&__input-element + &__input-element {
		margin-top: 10px;
	}
 	
	&__input {
		padding: 10px 5px;
		width: 100%;
	}
}
View Compiled
const app = new Vue({
	el: "#app",
	data: {
		columns: 2,
		ratioWidth: 1,
		ratioHeight: 1,
		imageData: [
			{
				id: 1,
				order: 1,
				src:
					"https://images.unsplash.com/photo-1587918515749-a2a68b40a124?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ"
			},
			{
				id: 2,
				order: 2,
				src:
					"https://images.unsplash.com/photo-1588231793676-7bf33c466184?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ"
			},
			{
				id: 3,
				order: 3,
				src:
					"https://images.unsplash.com/photo-1587467238707-0f9b326d56bd?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ"
			},
			{
				id: 4,
				order: 4,
				src:
					"https://images.unsplash.com/photo-1586854146097-6d9fb7b031fc?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjE0NTg5fQ"
			}
		]
	},
	computed: {
		images() {
			return this.imageData.sort((img1, img2) => img1.order - img2.order);
		},
		tableStyles() {
			return {
				gridTemplateColumns: `repeat(${this.columns}, 1fr)`
			};
		},
		itemStyles() {
			return {
				paddingTop: `${(this.ratioWidth / this.ratioHeight) * 100}%`
			};
		}
	},
	methods: {
		dragEnter(ev) {
			ev.target.classList.add("holder");
		},
		dragLeave(ev) {
			ev.target.classList.remove("holder");
		},
		allowDrop(ev) {
			ev.preventDefault();
		},
		drag(ev) {
			ev.dataTransfer.setData("id", ev.target.dataset.id);
		},
		drop(ev) {
			ev.preventDefault();
			ev.target.classList.remove("holder");

			const dragImageId = Number(ev.dataTransfer.getData("id"));
			const dropImageId = Number(ev.target.dataset.id);

			const dragImage = this.imageData.find(img => img.id === dragImageId);
			const dropImage = this.imageData.find(img => img.id === dropImageId);

			const dragImageOrder = dragImage.order;
			const dropImageOrder = dropImage.order;

			dragImage.order = dropImageOrder;
			dropImage.order = dragImageOrder;
		}
	}
});
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js