Best way to do a split pane in HTML [closed]

Is there a good technique to make a resizable split pane in HTML?

May it be done using CSS / jQuery / JavaScript or is there a good JavaScript library that have been used?

(An example of a split pane is the favorites bar in Internet Explorer which you may have docked to the left of your main browser window.)


I wanted a vanilla, lightweight (jQuery UI Layout weighs in at 185 KB), no dependency option (all existing libraries require jQuery), so I wrote Split.js.

It weights less than 2 KB and does not require any special markup. It supports older browsers back to Internet Explorer 9 (or Internet Explorer 8 with polyfills). For modern browsers, you can use it with Flexbox and grid layouts.


Improving on Reza's answer:

  • prevent the browser from interfering with a drag
  • prevent setting an element to a negative size
  • prevent drag getting out of sync with the mouse due to incremental delta interaction with element width saturation

<html><head><style>

.splitter {
    width: 100%;
    height: 100px;
    display: flex;
}

#separator {
    cursor: col-resize;
    background-color: #aaa;
    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>");
    background-repeat: no-repeat;
    background-position: center;
    width: 10px;
    height: 100%;

    /* Prevent the browser's built-in drag from interfering */
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

#first {
    background-color: #dde;
    width: 20%;
    height: 100%;
    min-width: 10px;
}

#second {
    background-color: #eee;
    width: 80%;
    height: 100%;
    min-width: 10px;
}

</style></head><body>

<div class="splitter">
    <div id="first"></div>
    <div id="separator" ></div>
    <div id="second" ></div>
</div>

<script>

// A function is used for dragging and moving
function dragElement(element, direction)
{
    var   md; // remember mouse down info
    const first  = document.getElementById("first");
    const second = document.getElementById("second");

    element.onmousedown = onMouseDown;

    function onMouseDown(e)
    {
        //console.log("mouse down: " + e.clientX);
        md = {e,
              offsetLeft:  element.offsetLeft,
              offsetTop:   element.offsetTop,
              firstWidth:  first.offsetWidth,
              secondWidth: second.offsetWidth
             };

        document.onmousemove = onMouseMove;
        document.onmouseup = () => {
            //console.log("mouse up");
            document.onmousemove = document.onmouseup = null;
        }
    }

    function onMouseMove(e)
    {
        //console.log("mouse move: " + e.clientX);
        var delta = {x: e.clientX - md.e.clientX,
                     y: e.clientY - md.e.clientY};

        if (direction === "H" ) // Horizontal
        {
            // Prevent negative-sized elements
            delta.x = Math.min(Math.max(delta.x, -md.firstWidth),
                       md.secondWidth);

            element.style.left = md.offsetLeft + delta.x + "px";
            first.style.width = (md.firstWidth + delta.x) + "px";
            second.style.width = (md.secondWidth - delta.x) + "px";
        }
    }
}


dragElement( document.getElementById("separator"), "H" );

</script></body></html>

Simplest HTML + CSS accordion, with just CSS resize.

div {
  resize: vertical;
  overflow: auto;
  border: 1px solid
}
.menu {
  display: grid
  /* Try height: 100% or height: 100vh */
}
<div class="menu">
  <div>
    Hello, World!
  </div>
  <div>
    Hello, World!
  </div>
  <div>
    Hello, World!
  </div>
</div>

Simplest HTML + CSS vertical resizable panes:

div {
  resize: horizontal;
  overflow: auto;
  border: 1px solid;
  display: inline-flex;
  height: 90vh
}
<div>
  Hello, World!
</div>
<div>
  Hello, World!
</div>

The plain HTML, details element!.

<details>
  <summary>Morning</summary>
  <p>Hello, World!</p>
</details>
<details>
  <summary>Evening</summary>
  <p>How sweat?</p>
</details>

Simplest HTML + CSS topbar foldable menu

div{
 display: flex
}
summary,p{
 margin: 0px 0 -1px 0px;
 padding: 0 0 0 0.5rem;
 border: 1px black solid
}
summary {
  padding: 0 1rem 0 0.5rem
}
<div>
  <details>
    <summary>FILE</summary>
    <p>Save</p>
    <p>Save as</p>
  </details>
  <details>
    <summary>EDIT</summary>
    <p>Pump</p>
    <p>Transfer</p>
    <p>Review</p>
    <p>Compile</p>
  </details>
  <details>
    <summary>PREFERENCES</summary>
    <p>How sweat?</p>
    <p>Powered by HTML</p>
  </details>
</div>

Fixed bottom menu bar, unfolding upward.

div{
 display: flex;
 position: fixed;
 bottom: 0;
 transform: rotate(180deg)
}
summary,p{
 margin: 0px 0 -1px 0px;
 padding: 0 0 0 0.5rem;
 border: 1px black solid;
 transform: rotate(180deg)
}
summary {
  padding: 0 1rem 0 0.5rem;
}
<div>
  <details>
    <summary>FILE</summary>
    <p>Save</p>
    <p>Save as</p>
  </details>
  <details>
    <summary>EDIT</summary>
    <p>Pump</p>
    <p>Transfer</p>
    <p>Review</p>
    <p>Compile</p>
  </details>
  <details>
    <summary>PREF</summary>
    <p>How?</p>
    <p>Power</p>
  </details>
</div>

Simplest resizable pane, using JavaScript.

let ismdwn = 0
rpanrResize.addEventListener('mousedown', mD)

function mD(event) {
  ismdwn = 1
  document.body.addEventListener('mousemove', mV)
  document.body.addEventListener('mouseup', end)
}

function mV(event) {
  if (ismdwn === 1) {
    pan1.style.flexBasis = event.clientX + "px"
  } else {
    end()
  }
}
const end = (e) => {
  ismdwn = 0
  document.body.removeEventListener('mouseup', end)
  rpanrResize.removeEventListener('mousemove', mV)
}
div {
  display: flex;
  border: 1px black solid;
  width: 100%;
  height: 200px;
}

#pan1 {
  flex-grow: 1;
  flex-shrink: 0;
  flex-basis: 50%; // initial status
}

#pan2 {
  flex-grow: 0;
  flex-shrink: 1;
  overflow-x: auto;
}

#rpanrResize {
  flex-grow: 0;
  flex-shrink: 0;
  background: #1b1b51;
  width: 0.2rem;
  cursor: col-resize;
  margin: 0 0 0 auto;
}
<div>
  <div id="pan1">MENU</div>
  <div id="rpanrResize">&nbsp;</div>
  <div id="pan2">BODY</div>
</div>

I wrote simple code for it without any third-party library. This code is only for a horizontal splitter (vertical is the same).

function onload()
{
    dragElement( document.getElementById("separator"), "H" );
}

// This function is used for dragging and moving
function dragElement( element, direction, handler )
{
  // Two variables for tracking positions of the cursor
  const drag = { x : 0, y : 0 };
  const delta = { x : 0, y : 0 };
  /* If present, the handler is where you move the DIV from
     otherwise, move the DIV from anywhere inside the DIV */
  handler ? ( handler.onmousedown = dragMouseDown ): ( element.onmousedown = dragMouseDown );

  // A function that will be called whenever the down event of the mouse is raised
  function dragMouseDown( e )
  {
    drag.x = e.clientX;
    drag.y = e.clientY;
    document.onmousemove = onMouseMove;
    document.onmouseup = () => { document.onmousemove = document.onmouseup = null; }
  }

  // A function that will be called whenever the up event of the mouse is raised
  function onMouseMove( e )
  {
    const currentX = e.clientX;
    const currentY = e.clientY;

    delta.x = currentX - drag.x;
    delta.y = currentY - drag.y;

    const offsetLeft = element.offsetLeft;
    const offsetTop = element.offsetTop;


    const first = document.getElementById("first");
    const second = document.getElementById("second");
    let firstWidth = first.offsetWidth;
    let secondWidth = second.offsetWidth;
    if (direction === "H" ) // Horizontal
    {
        element.style.left = offsetLeft + delta.x + "px";
        firstWidth += delta.x;
        secondWidth -= delta.x;
    }
    drag.x = currentX;
    drag.y = currentY;
    first.style.width = firstWidth + "px";
    second.style.width = secondWidth + "px";
  }
}
.splitter {
    width: 500px;
    height: 100px;
    display: flex;
}

#separator {
    cursor: col-resize;
    background: url(https://raw.githubusercontent.com/RickStrahl/jquery-resizable/master/assets/vsizegrip.png) center center no-repeat #535353;
    width: 10px;
    height: 100px;
    min-width: 10px;
}

#first {
    background-color: green;
    width: 100px;
    height: 100px;
    min-width: 10px;
}

#second {
    background-color: red;
    width: 390px;
    height: 100px;
    min-width: 10px;
}
<html>

    <head>
        <link rel="stylesheet" href="T10-Splitter.css">
        <script src="T10-Splitter.js"></script>
    </head>

    <body onload="onload()">
        <div class="splitter">
            <div id="first"></div>
            <div id="separator"></div>
            <div id="second"></div>
        </div>
    </body>

</html>