I\'m trying to come up with an effective way of defining a nested flexbox and allow it to be resized. I think it\'s almost there:
http://jsfiddle.net/6j10L3x2/1/
Note: There is also the new, basic resize CSS property, but it's only for bottom right corner dragging.
I did some research on this, and the first 3 framework-free, fully baked results I came across were, in order of appearances (untested):
https://daybrush.com/moveable
https://split.js.org
https://jspanel.de
I also found this: http://w2ui.com/web/home https://github.com/vitmalina/w2ui
Wow. I am impressed how you resize the flexbox elements with vanilla javascript using 'flexGrow', excelent idea and code.
I have improve your code in a few ways and it is working very well.
What I did?
1.- I simplified the HTML:
Do not use a flex element inside a flex-item.
Use a flex or flex-item element, always!, inside another flex element.
2.- Solved! Splitter's jump when the visible flex-item size is smaller that its content size.
3.- I'd added different cursors to signal a state's change (setupResizerEvents, onMouseUp) to improve usability.
4.- I've added code to prevent the cursor from flickering when dragging.
5.- use of offsetWidth and offsetHeight in manageResize() versus scrollWidth and scrollHeight to avoid splitter's jump on resize when a flex-item content overflow (overflow: auto).
Here is the code:
function manageResize(md, sizeProp, posProp) {
var r = md.target;
var prev = r.previousElementSibling;
var next = r.nextElementSibling;
if (!prev || !next) {
return;
}
md.preventDefault();
var prevSize = prev[sizeProp];
var nextSize = next[sizeProp];
var sumSize = prevSize + nextSize;
var prevGrow = Number(prev.style.flexGrow);
var nextGrow = Number(next.style.flexGrow);
var sumGrow = prevGrow + nextGrow;
var lastPos = md[posProp];
function onMouseMove(mm) {
var pos = mm[posProp];
var d = pos - lastPos;
prevSize += d;
nextSize -= d;
if (prevSize < 0) {
nextSize += prevSize;
pos -= prevSize;
prevSize = 0;
}
if (nextSize < 0) {
prevSize += nextSize;
pos += nextSize;
nextSize = 0;
}
var prevGrowNew = sumGrow * (prevSize / sumSize);
var nextGrowNew = sumGrow * (nextSize / sumSize);
prev.style.flexGrow = prevGrowNew;
next.style.flexGrow = nextGrowNew;
lastPos = pos;
}
function onMouseUp(mu) {
// Change cursor to signal a state's change: stop resizing.
const html = document.querySelector('html');
html.style.cursor = 'default';
if (posProp === 'pageX') {
r.style.cursor = 'ew-resize';
} else {
r.style.cursor = 'ns-resize';
}
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", onMouseUp);
}
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", onMouseUp);
}
function setupResizerEvents() {
document.body.addEventListener("mousedown", function (md) {
// Used to avoid cursor's flickering
const html = document.querySelector('html');
var target = md.target;
if (target.nodeType !== 1 || target.tagName !== "FLEX-RESIZER") {
return;
}
var parent = target.parentNode;
var h = parent.classList.contains("h");
var v = parent.classList.contains("v");
if (h && v) {
return;
} else if (h) {
// Change cursor to signal a state's change: begin resizing on H.
target.style.cursor = 'col-resize';
html.style.cursor = 'col-resize'; // avoid cursor's flickering
// use offsetWidth versus scrollWidth (and clientWidth) to avoid splitter's jump on resize when a flex-item content overflow (overflow: auto).
manageResize(md, "offsetWidth", "pageX");
} else if (v) {
// Change cursor to signal a state's change: begin resizing on V.
target.style.cursor = 'row-resize';
html.style.cursor = 'row-resize'; // avoid cursor's flickering
manageResize(md, "offsetHeight", "pageY");
}
});
}
setupResizerEvents();
body {
/* margin:0; */
border: 10px solid #aaa;
}
flex {
display: flex;
overflow: hidden;
}
/* flex-item > flex {
position: absolute;
width: 100%;
height: 100%;
} */
flex.h {
flex-direction: row;
}
flex.v {
flex-direction: column;
}
flex-item {
/* display: flex; */
/* position: relative; */
/* overflow: hidden; */
overflow: auto;
}
flex > flex-resizer {
flex: 0 0 10px;
/* background: white; */
background-color: #aaa;
background-repeat: no-repeat;
background-position: center;
}
flex.h > flex-resizer {
cursor: ew-resize;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='30'><path d='M2 0 v30 M5 0 v30 M8 0 v30' fill='none' stroke='black'/></svg>");
}
flex.v > flex-resizer {
cursor: ns-resize;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='10'><path d='M0 2 h30 M0 5 h30 M0 8 h30' fill='none' stroke='black'/></svg>");
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>flex-splitter</title>
<link rel="stylesheet" href="./src/styles.css">
<script src="./src/index.js" defer></script>
</head>
<body>
<flex class="v" style="flex: 1; height: 500px;">
<flex-item style="flex: 1;">Flex 1</flex-item>
<flex-resizer></flex-resizer>
<flex class="h" style="flex: 1;">
<flex-item style="flex: 1; background-color: aqua;">
<!--
The next section is an example to test the splitter when there is content inside a flex-item
-->
<section>
<div>
<label for="CursorCoor" style="display: block;">showCursorCoor: </label>
<textarea id="CursorCoor" rows="6" cols="50" wrap="soft" readonly></textarea>
</div>
<br />
<div>
<label for="boxInfo" style="display: block;">showBoxInfo: </label>
<textarea id="boxInfo" rows="6" cols="50" wrap="soft" readonly></textarea>
</div>
</section>
</flex-item>
<flex-resizer></flex-resizer>
<flex class="v" style="flex: 2; ">
<flex-item style="flex: 1; background: pink;">Flex 3</flex-item>
<flex-resizer></flex-resizer>
<flex class="h" style="flex: 1">
<flex-item style="flex: 1; background: green;">Flex 4</flex-item>
<flex-resizer></flex-resizer>
<flex-item style="flex: 2;">Flex 5</flex-item>
<!-- <flex-resizer></flex-resizer> -->
<flex-item style="flex: 3; background: darkorange;">Flex 6</flex-item>
</flex>
</flex>
</flex>
</flex>
</body>
</html>
Or see it on Codesandbox:
I hope it helps!