Search code examples
javascriptjquery

Drag to expand element (desktop and mobile)


enter image description here

How to make the above? It can drag up and down by dragging the menu text area (menu image or some inner div), and the div comes down or up (with max and min limits) as its dragged.

I cant make this code working, can any one give a practical code?

whole_menu.onPress = function() {
  startDrag(this);
};
whole_menu.onRelease = function () {
  stopDrag();
};


whole_menu.onPress = function() {
  startDrag(this, false, 0, this._y, 800-this._width, this._y);
};
whole_menu.onRelease = function () {
  stopDrag();
};

EDIT: added more explanation and code, so
no need to close this question


Solution

  • A responsive draggable/expandable menu using pure JavaScript can be achieved using: pointer events, set/releasePointerCapture, CSS height: max-content; min-height and max-height, and set dynamically a CSS Custom Property --h (for the menu height) using JS on drag start and move:

    const elMenu = document.querySelector("#menu");
    const elMenuHandle = document.querySelector("#menu-handle");
    
    let h = 0;
    
    const dragStart = (ev) => {
      elMenuHandle.setPointerCapture(ev.pointerId);
      h = elMenu.offsetHeight;
    };
    const dragEnd = (ev) => {
      elMenuHandle.releasePointerCapture(ev.pointerId);
    };
    const drag = (ev) => {
      if (!elMenuHandle.hasPointerCapture(ev.pointerId)) return;
      h = Math.max(0, h + ev.movementY);
      elMenu.style.setProperty("--h", h);
    };
    
    elMenuHandle.addEventListener("pointerdown", dragStart);
    elMenuHandle.addEventListener("pointerup", dragEnd);
    elMenuHandle.addEventListener("pointermove", drag);
    * { box-sizing: border-box; margin: 0; }
    body{ font: 1em/1.4 sans-serif; }
    p { padding: 1em 0;}
    
    #menu-wrapper{
      position: absolute;
      color: #ccc;
      background: #444;
    }
    #menu {
      --h: 0;
      position: relative;
      overflow: hidden;
      height: calc(var(--h) * 1px);
      min-height: 3px;
      max-height: max-content;
    }
    #menu-content {
      padding: 2em;
    }
    #menu-handle{
      cursor: row-resize;
      background: #444;
      padding: 1em 2em;
      position: absolute;
      top: 100%;
      right: 0;
      left: 0;
      width: max-content;
      margin: 0 auto;
      user-select: none; /* prevent text selection */
      touch-action: none; /* allow pointermove */
    }
    <p>Some stuff before (if needed)</p>
    <div id="menu-wrapper">
      <div id="menu">
        <div id="menu-content">
          Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloremque saepe cum corrupti, expedita, tenetur ipsa! Distinctio quam, dolor iusto corporis blanditiis velit suscipit eligendi commodi accusamus, voluptate earum at ipsa.
        </div>
      </div>
      <div id="menu-handle">DRAG &darr; TO REVEAL</div>
    </div>
    <p>Some other stuff after</p>

    If you want the content that comes after to be pushed down as the menu expands, use CSS position: relative; (instead of absolute) on the #menu-wrapper element:

    const elMenu = document.querySelector("#menu");
    const elMenuHandle = document.querySelector("#menu-handle");
    
    let h = 0;
    
    const dragStart = (ev) => {
      elMenuHandle.setPointerCapture(ev.pointerId);
      h = elMenu.offsetHeight;
    };
    const dragEnd = (ev) => {
      elMenuHandle.releasePointerCapture(ev.pointerId);
    };
    const drag = (ev) => {
      if (!elMenuHandle.hasPointerCapture(ev.pointerId)) return;
      h = Math.max(0, h + ev.movementY);
      elMenu.style.setProperty("--h", h);
    };
    
    elMenuHandle.addEventListener("pointerdown", dragStart);
    elMenuHandle.addEventListener("pointerup", dragEnd);
    elMenuHandle.addEventListener("pointermove", drag);
    * { box-sizing: border-box; margin: 0; }
    body{ font: 1em/1.4 sans-serif; }
    p { padding: 1em 0;}
    
    #menu-wrapper{
      position: absolute;
      color: #ccc;
      background: #444;
    }
    #menu {
      --h: 0;
      position: relative;
      overflow: hidden;
      height: calc(var(--h) * 1px);
      min-height: 3px;
      max-height: max-content;
    }
    #menu-content {
      padding: 2em;
    }
    #menu-handle{
      cursor: row-resize;
      background: #444;
      padding: 1em 2em;
      position: absolute;
      top: 100%;
      right: 0;
      left: 0;
      width: max-content;
      margin: 0 auto;
      user-select: none; /* prevent text selection */
      touch-action: none; /* allow pointermove */
    }
    <p>Some stuff before (if needed)</p>
    <div id="menu-wrapper">
      <div id="menu">
        <div id="menu-content">
          Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloremque saepe cum corrupti, expedita, tenetur ipsa! Distinctio quam, dolor iusto corporis blanditiis velit suscipit eligendi commodi accusamus, voluptate earum at ipsa.
        </div>
      </div>
      <div id="menu-handle">DRAG &darr; TO REVEAL</div>
    </div>
    <p>Some other stuff after</p>

    Works on both desktop, touch displays, and mobile.