JSFiddle - React, Tailwind, and code Playground
HTML
<script src="https://code.jquery.com/ui/1.11.0/jquery-ui.min.js"></script>
<ul class="chapters">
<li>
<div class="chapter unselectable">
<table>
<thead>
<tr>
<th>Category 1</th>
</tr>
</thead>
<tbody>
<tr>
<th>Subcategory</th>
</tr>
<tr>
<td>Item</td>
</tr>
<tr>
<td>Item</td>
</tr>
</tbody>
<tbody>
<tr>
<th>Subcategory</th>
</tr>
<tr>
<td>Item</td>
</tr>
<tr>
<td>Item</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>Category 2</th>
</tr>
</thead>
<tbody>
<tr>
<th>Subcategory</th>
</tr>
<tr>
<td>Item</td>
</tr>
<tr>
<td>Item</td>
</tr>
</tbody>
<tbody>
<tr>
<th>Subcategory</th>
</tr>
<tr>
<td>Item</td>
</tr>
<tr>
<td>Item</td>
</tr>
</tbody>
</table>
</div>
</li>
<li>
<div class="chapter unselectable">
<table>
<thead>
<tr>
<th>Category 3</th>
</tr>
</thead>
<tbody>
<tr>
<th>Subcategory</th>
</tr>
<tr>
<td>Item</td>
</tr>
<tr>
<td>Item</td>
</tr>
</tbody>
<tbody>
<tr>
<th>Subcategory</th>
</tr>
<tr>
<td>Item</td>
</tr>
<tr>
<td>Item</td>
</tr>
</tbody>
</table>
</div>
</li>
<li>
<div class="chapter unselectable">
<table>
<thead>
<tr>
<th>Category 4</th>
...
CSS
* {
margin: 0;
padding: 0;
border: medium none;
border-spacing: 0;
outline: none;
outline: 0;
color: inherit;
font-family: inherit;
font-size: inherit;
font-weight: inherit;
line-height: 1.5em;
text-align: inherit;
text-decoration: none;
text-indent: 0;
list-style: none outside none;
background: none repeat scroll 0 0 transparent;
}
*::-moz-focus-inner {
border: 0;
padding: 0;
}
.unselectable,
.unselectable * {
cursor: default;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
}
html {
color: #222;
font-family: Arial, Helvetica, sans-serif;
font-size: 10px;
font-weight: normal;
text-align: left;
min-height: 100%;
}
body {
font-size: 1.25rem;
}
ul.chapters > li {
box-sizing: border-box;
display: inline-block;
float: left;
margin: 1%;
width: 31%;
height: 17.4375vw;
vertical-align: top;
border: 1px solid #ccc;
border-radius: 1%;
}
div.chapter {
box-sizing: border-box;
position: relative;
width: 300%;
height: 300%;
padding: 1em;
overflow: hidden;
transform-origin: 0 0;
transform: scale( 0.3334);
}
table {
position: relative;
float: left;
margin: 0 20px 20px 0;
width: 100%;
border-collapse: collapse;
border: 1px solid black;
}
th {
font-weight: bold;
text-align: left;
}
th,
td {
box-sizing: border-box;
padding: .5rem;
font-size: 3rem;
border: 1px solid black;
}
thead th {
color: #fff;
background-color: #d00;
}
tbody + tbody::before {
content: '';
display: block;
height: 5px;
}
JavaScript
function getScale( $el ) {
var scale = {
x: 1,
y: 1
}
var matches;
var transform = $el.css( 'transform' );
if( null != ( matches = transform.match( /matrix\(([^)]+)\)/ ) ) ) {
var matrix = matches[ 1 ].split( /,\s*/ );
scale.x = parseFloat( matrix[ 0 ] );
scale.y = parseFloat( matrix[ 3 ] );
}
return scale;
}
function fixStart( e, ui ) {
var chapter = ui.item.closest( 'div.chapter' );
var scale = getScale( chapter );
// since I'm using jQuery 1.11.0: 'uiSortable' instead of 'ui-sortable'
var items = $( this ).data()[ 'uiSortable' ].items;
items.forEach( function( item ) {
item.left = item.item.offset().left;
item.top = item.item.offset().top;
item.width = item.item.width() * scale.x;
item.height = item.item.height() * scale.y;
} );
ui.helper.css( {
transformOrigin: chapter.css( 'transform-origin' ),
transform: chapter.css( 'transform' )
} );
}
function fixSort( e, ui ) {
console.log( ui.helper.offset() );
}
$( 'div.chapter' ).sortable( {
items: '> table',
connectWith: 'div.chapter',
delay: 150,
opacity: 0.5,
appendTo: document.body,
helper: 'clone',
tolerance: 'pointer',
cursor: 'move',
start: fixStart,
sort: fixSort,
update: function( e, ui ) {
console.log( 'category sorted!' );
}
} );