Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Draggable split-pane windows in flexbox can't get past child elements [duplicate]

Tags:

I implemented my own split-pane with HTML/JS/CSS Flexbox.

I'm having trouble with the splitter in the following case- one of the panels has a fixed size (in px), and the other one is set to grow (flex-grow: 1).

In case the other panel has children with size, it won't scroll to the end. It gets stuck at the size of the children.

Can this be fixed with CSS on the split-pane panels but not on the children?

It's very important for me to use flex as I want to maintain responsiveness of my application, and want to avoid fixed sizes wherever I can.

This is a JSFiddle sample of my question.

Code snippet given below. Thanks!

function startDrag() {
  glass.style = 'display: block;';
  glass.addEventListener('mousemove', drag, false);
}

function endDrag() {
  glass.removeEventListener('mousemove', drag, false);
  glass.style = '';
}

function drag(event) {
  var splitter = getSplitter();
  var panel = document.getElementById('c2');
  var currentWidth = panel.offsetWidth;
  var currentLeft = panel.offsetLeft;
  panel.style.width = (currentWidth - (event.clientX - currentLeft)) + "px";
}

function getSplitter() {
  return document.getElementById('splitter');
}

var con = document.getElementById('container');
var splitter = document.createElement('div');
var glass = document.getElementById('glass');
splitter.className = 'splitter';
splitter.id = 'splitter';
con.insertBefore(splitter, con.lastElementChild);
splitter.addEventListener('mousedown', startDrag, false);
glass.addEventListener('mouseup', endDrag, false);
.container {
  display: flex;
  border: 1px solid;
  width: 500px;
  height: 300px;
  position: absolute;
}
.c1 {
  background-color: blue;
  flex: 1;
  height: 100%;
}
.c2 {
  background-color: green;
  width: 150px;
}
.splitter {
  width: 20px;
  cursor: col-resize;
}
.glass {
  height: 100%;
  width: 100%;
  cursor: col-resize;
  display: none;
  position: absolute;
}
.grandchild {
  background-color: red;
  width: 50px;
  height: 50px;
}
<div id="container" class="container">
  <div id="glass" class="glass"></div>
  <div class="c1">
    <div class="grandchild"></div>
  </div>
  <div id="c2" class="c2"></div>
</div>
like image 461
HadarZH Avatar asked Sep 29 '16 08:09

HadarZH


2 Answers

It gets stuck at the size of the children

This is expected behavior when using a flexbox. I guess if you want to scroll to the end then you can use position: absolute for the grandchild relative to c1:

.grandchild {
  background-color: red;
  width: 50px;
  height: 50px;
  position: absolute;
  top: 0;
  left: 0;
}

Give overflow: hidden to c1 too:

.c1 {
  background-color: blue;
  flex: 1;
  height: 100%;
  position: relative;
  overflow: hidden;
}

Cheers!

function startDrag() {
  glass.style = 'display: block;';
  glass.addEventListener('mousemove', drag, false);
}

function endDrag() {
  glass.removeEventListener('mousemove', drag, false);
  glass.style = '';
}

function drag(event) {
  var splitter = getSplitter();
  var panel = document.getElementById('c2');
  var currentWidth = panel.offsetWidth;
  var currentLeft = panel.offsetLeft;
  panel.style.width = (currentWidth - (event.clientX - currentLeft)) + "px";
}

function getSplitter() {
  return document.getElementById('splitter');
}

var con = document.getElementById('container');
var splitter = document.createElement('div');
var glass = document.getElementById('glass');
splitter.className = 'splitter';
splitter.id = 'splitter';
con.insertBefore(splitter, con.lastElementChild);
splitter.addEventListener('mousedown', startDrag, false);
glass.addEventListener('mouseup', endDrag, false);
.container {
  display: flex;
  border: 1px solid;
  width: 500px;
  height: 300px;
  position: absolute;
}

.c1 {
  background-color: blue;
  flex: 1;
  height: 100%;
  position: relative;
  overflow: hidden;
}

.c2 {
  background-color: green;
  width: 150px;
}

.splitter {
  width: 20px;
  cursor: col-resize;
}

.glass {
  height: 100%;
  width: 100%;
  cursor: col-resize;
  display: none;
  position: absolute;
}

.grandchild {
  background-color: red;
  width: 50px;
  height: 50px;
  position: absolute;
  top: 0;
  left: 0;
}
<div id="container" class="container">
  <div id="glass" class="glass"></div>
  <div class="c1">
    <div class="grandchild"></div>
  </div>
  <div id="c2" class="c2"></div>
</div>

Solution:

So I guess your strategy should be to use an absolute grandchild that fills the whole side-panel, and then put the content inside like:

<div class="grandchild">
  <div class="content"></div>
</div>

and change these styles:

.grandchild {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
.grandchild .content{
  background-color: red;
  width: 50px;
  height: 50px;
}

Example below:

function startDrag() {
  glass.style = 'display: block;';
  glass.addEventListener('mousemove', drag, false);
}

function endDrag() {
  glass.removeEventListener('mousemove', drag, false);
  glass.style = '';
}

function drag(event) {
  var splitter = getSplitter();
  var panel = document.getElementById('c2');
  var currentWidth = panel.offsetWidth;
  var currentLeft = panel.offsetLeft;
  panel.style.width = (currentWidth - (event.clientX - currentLeft)) + "px";
}

function getSplitter() {
  return document.getElementById('splitter');
}

var con = document.getElementById('container');
var splitter = document.createElement('div');
var glass = document.getElementById('glass');
splitter.className = 'splitter';
splitter.id = 'splitter';
con.insertBefore(splitter, con.lastElementChild);
splitter.addEventListener('mousedown', startDrag, false);
glass.addEventListener('mouseup', endDrag, false);
.container {
  display: flex;
  border: 1px solid;
  width: 500px;
  height: 300px;
  position: absolute;
}

.c1 {
  background-color: blue;
  flex: 1;
  height: 100%;
  position: relative;
  overflow: hidden;
}

.c2 {
  background-color: green;
  width: 150px;
}

.splitter {
  width: 20px;
  cursor: col-resize;
}

.glass {
  height: 100%;
  width: 100%;
  cursor: col-resize;
  display: none;
  position: absolute;
}
.grandchild {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
.grandchild .content{
  background-color: red;
  width: 50px;
  height: 50px;
}
<div id="container" class="container">
  <div id="glass" class="glass"></div>
  <div class="c1">
    <div class="grandchild">
      <div class="content"></div>
    </div>
  </div>
  <div id="c2" class="c2"></div>
</div>
like image 116
kukkuz Avatar answered Sep 23 '22 16:09

kukkuz


In case the other panel has children with size, it won't scroll to the end. It gets stuck at the size of the children.

This is because an initial setting of a flex container is min-width: auto on the flex items. This means that a flex item, by default, cannot be smaller than the size of its content.

Can this be fixed with CSS on the split-pane panels but not on the children?

Yes. Override the default with min-width: 0 or with any overflow other than visible:

.c1 {
  background-color: blue;
  flex: 1;
  height: 100%;
  overflow: hidden; /* or min-width: 0 */
}

revised fiddle

function startDrag() {
  glass.style = 'display: block;';
  glass.addEventListener('mousemove', drag, false);
}

function endDrag() {
  glass.removeEventListener('mousemove', drag, false);
  glass.style = '';
}

function drag(event) {
  var splitter = getSplitter();
  var panel = document.getElementById('c2');
  var currentWidth = panel.offsetWidth;
  var currentLeft = panel.offsetLeft;
  panel.style.width = (currentWidth - (event.clientX - currentLeft)) + "px";
}

function getSplitter() {
  return document.getElementById('splitter');
}

var con = document.getElementById('container');
var splitter = document.createElement('div');
var glass = document.getElementById('glass');
splitter.className = 'splitter';
splitter.id = 'splitter';
con.insertBefore(splitter, con.lastElementChild);
splitter.addEventListener('mousedown', startDrag, false);
glass.addEventListener('mouseup', endDrag, false);
.container {
  display: flex;
  border: 1px solid;
  width: 500px;
  height: 300px;
  position: absolute;
}
.c1 {
  background-color: blue;
  flex: 1;
  height: 100%;
  overflow: hidden;  
}
.c2 {
  background-color: green;
  width: 150px;
}
.splitter {
  width: 20px;
  cursor: col-resize;
}
.glass {
  height: 100%;
  width: 100%;
  cursor: col-resize;
  display: none;
  position: absolute;
}
.grandchild {
  background-color: red;
  width: 50px;
  height: 50px;
}
<div id="container" class="container">
  <div id="glass" class="glass"></div>
  <div class="c1">
    <div class="grandchild"></div>
  </div>
  <div id="c2" class="c2"></div>
</div>
like image 20
Michael Benjamin Avatar answered Sep 23 '22 16:09

Michael Benjamin