Skip to content

Instantly share code, notes, and snippets.

@bwindels
Last active August 16, 2018 17:29
Show Gist options
  • Save bwindels/3fbdd9a4675e6aa53099bbc662587245 to your computer and use it in GitHub Desktop.
Save bwindels/3fbdd9a4675e6aa53099bbc662587245 to your computer and use it in GitHub Desktop.
html splitter with flexbox and injectable distribution algorithm
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
body {
padding: 0;
margin: 0;
}
#container {
height: 100vh;
display: flex;
flex-direction: column;
}
.item {
flex: 1 1 auto;
background: red;
}
.item2 {
flex: 2 1 auto;
background: orange;
}
.resizing .item {
background: blue;
}
.resize-handle {
cursor: row-resize;
flex: 0 0 auto;
background: green;
height: 1px;
padding: 2px
}
.resize-handle.vertical {
cursor: row-resize;
}
.resize-handle.horizontal {
cursor: col-resize;
}
</style>
</head>
<body>
<div id="container">
<div class="item"></div>
<div class="resize-handle vertical"></div>
<div class="item2"></div>
<div class="resize-handle vertical"></div>
<div class="item"></div>
<div class="resize-handle vertical"></div>
<div class="item"></div>
<!--
<div class="resize-handle vertical"></div>
<div class="item"></div>
-->
</div>
<script type="text/javascript">
class Sizer {
constructor(container, vertical) {
this.container = container;
this.vertical = vertical;
this.containerRect = container.getBoundingClientRect();
}
getItemPercentage(item) {
const flexGrow = window.getComputedStyle(item).flexGrow;
if (flexGrow === "") {
return null;
}
return parseInt(flexGrow) / 1000;
}
setItemPercentage(item, percent) {
item.style.flexGrow = Math.round(percent * 1000);
}
getItemOffset(item) {
const rect = item.getBoundingClientRect();
const offset = this.vertical ? rect.y : rect.x;
return offset - this.getOffset();
}
getItemSize(item) {
return parseInt(window.getComputedStyle(item).width);
}
getTotalSize() {
return this.vertical ? this.containerRect.height : this.containerRect.width;
}
getOffset() {
return this.vertical ? this.containerRect.y : this.containerRect.x;
}
setItemSize(item, size) {
item.style.flexGrow = 0;
item.style.flexShrink = 0;
item.style.flexBasis = `${Math.round(size)}px`;
}
localSizeFromEvent(event) {
const pos = this.vertical ? event.pageY : event.pageX;
return pos - this.getOffset();
}
}
class FixedDistributor {
constructor(container, items, handleIndex, sizer) {
this.beforeItem = items[handleIndex - 1];
//this.afterItem = items[handleIndex];
//this.beforeSize = sizer.getItemSize(beforeItem);
//this.afterSize = sizer.getItemSize(afterSize);
this.beforeOffset = sizer.getItemOffset(this.beforeItem);
this.sizer = sizer;
}
resize(offset) {
const itemSize = offset - this.beforeOffset;
this.sizer.setItemSize(this.beforeItem, itemSize);
}
}
class PercentageDistributor {
constructor(container, items, handleIndex, sizer) {
this.container = container;
this.totalSize = sizer.getTotalSize();
this.sizer = sizer;
this.beforeItems = items.slice(0, handleIndex);
this.afterItems = items.slice(handleIndex);
const percentages = PercentageDistributor._getPercentages(sizer, items);
this.beforePercentages = percentages.slice(0, handleIndex);
this.afterPercentages = percentages.slice(handleIndex);
}
resize(offset) {
const percent = offset / this.totalSize;
const beforeSum =
this.beforePercentages.reduce((total, p) => total + p, 0);
const beforePercentages =
this.beforePercentages.map(p => (p / beforeSum) * percent);
const afterSum =
this.afterPercentages.reduce((total, p) => total + p, 0);
const afterPercentages =
this.afterPercentages.map(p => (p / afterSum) * (1 - percent));
this.beforeItems.forEach((item, index) => {
this.sizer.setItemPercentage(item, beforePercentages[index]);
});
this.afterItems.forEach((item, index) => {
this.sizer.setItemPercentage(item, afterPercentages[index]);
});
}
static _getPercentages(sizer, items) {
const percentages = items.map(i => sizer.getItemPercentage(i));
const setPercentages = percentages.filter(p => p !== null);
const unsetCount = percentages.length - setPercentages.length;
const setTotal = setPercentages.reduce((total, p) => total + p, 0);
const implicitPercentage = (1 - setTotal) / unsetCount;
return percentages.map(p => p === null ? implicitPercentage : p);
}
static setPercentage(el, percent) {
el.style.flexGrow = Math.round(percent * 1000);
}
}
const RESIZE_HANDLE_CLASS = "resize-handle";
function makeResizeable(container, distributorCtor) {
container.addEventListener("mousedown", (event) => {
if (!event.target.classList.contains(RESIZE_HANDLE_CLASS)) {
return;
}
// prevent starting a drag operation
event.preventDefault();
container.classList.add("resizing");
const resizeHandle = event.target;
const vertical = resizeHandle.classList.contains("vertical");
const sizer = new Sizer(container, vertical);
const items = Array.prototype.slice.apply(
container.querySelectorAll(`:not(.${RESIZE_HANDLE_CLASS})`));
const prevItem = resizeHandle.previousElementSibling;
const handleIndex = items.indexOf(prevItem) + 1;
const distributor = new distributorCtor(container, items, handleIndex, sizer);
const onMouseMove = (event) => {
const offset = sizer.localSizeFromEvent(event);
distributor.resize(offset);
};
const body = document.body;
const onMouseUp = (event) => {
container.classList.remove("resizing");
body.removeEventListener("mouseup", onMouseUp, false);
body.removeEventListener("mousemove", onMouseMove, false);
};
body.addEventListener("mouseup", onMouseUp, false);
body.addEventListener("mousemove", onMouseMove, false);
}, false);
}
makeResizeable(document.getElementById("container"), PercentageDistributor);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment