Search code examples
javascriptcssdrag-and-dropdraggablehammer.js

iPhoneX-like show/hide menu/container by drag


I want to implement ability to collapse the gray .content-wrapper block (with blue sqare inside) smoothly based on drag value when user drags .drag element. What is the best and efficient way nowadays to do this for mobile touch drag?

* {
  box-sizing: border-box;
  color: white;
}

.drag {
  width: 50px;
  height: 3px;
  border-radius: 3px;
  opacity: 0.3;
  margin: 0 auto;
  background: linear-gradient(to left, white, rgba(255, 255, 255, 0.8) 50%, white 100%);
  transition: opacity 0.4s;
}

.screen {
  padding-top: 40px;
  margin: 0 auto;
  border-radius: 5px;
  overflow: hidden;
  background: #2b2d5b;
  width: 320px;
  height: 568px;
}

.controls {
  background: linear-gradient(to bottom, #fd2929, #fd5c5c);
  height: 100px;
  
}

.drag-wrapper {
  display: block;
  padding: 20px;
}

.drag-wrapper:active .drag,
.drag-wrapper:hover .drag,
.drag-wrapper:focus .drag {
  opacity: 0.7;
}

.board {
  padding: 0 20px;
}

.content-wrapper {
  padding: 20px;
  height: 320px;
}

.content-wrapper {
  background: #444;
}

.content {
display: flex;
justify-content: center;
align-items: center;
font-family: Helvetica;
  background: linear-gradient(to bottom, #3adffd, #00abfb);
  height: 100%;
}
<div class="screen">
  <div class="content-wrapper">
    <div class="content">
    .content
    </div>
  </div>
  <a href="javascript:void(0)" class="drag-wrapper">
    <div class="drag"></div>
  </a>
  <div class="board">
    <div class="controls"></div>
  </div>
</div>


Solution

  • here's my implementation for this case with hammer.js

    http://hammerjs.github.io

    movement of the board could have more complex calculations based on e.velocityY, but I'm using quick CSS transition when pan ended which looks good for my case

    $(document).ready(function() {
        let sliderManager = new Hammer.Manager(document.querySelector('.drag-wrapper'));
        let board = document.querySelector('.board');
        let threshold = 150;
        let boardTopInitial = board.style.top = board.offsetTop;
        let boardTopCollaped = 30;
      
        sliderManager.add(new Hammer.Pan({
          threshold: 0,
          pointers: 0
        }));
        sliderManager.on('pan', function(e) {
          e.preventDefault();
          board.classList.remove("transitable");
          board.classList.add("full-height");
          if (!board.classList.contains('pinned-top') && e.deltaY < 0) {
            board.style.top = boardTopInitial + e.deltaY + "px";
            if (e.isFinal) {
              board.classList.add("transitable");
              if (Math.abs(e.deltaY) > threshold) {
                board.style.top = boardTopCollaped + "px";
                board.classList.add("pinned-top");
              } else {
                board.setAttribute('style', '');
                board.classList.remove("full-height");
              }
            }
          } else if (board.classList.contains('pinned-top') && e.deltaY > 0) {
            board.style.top = boardTopCollaped + e.deltaY + "px";
            if (e.isFinal) {
              board.classList.add("transitable");
              if (Math.abs(e.deltaY) > threshold) {
                board.setAttribute('style', '');
                board.classList.remove("pinned-top");
                board.classList.remove("full-height");
              } else {
                board.style.top = boardTopCollaped + "px";
                board.classList.add("top");
              }
            }
          }
        })
      })
    * {
      box-sizing: border-box;
      color: white;
    }
    
    .drag {
      width: 50px;
      height: 3px;
      border-radius: 3px;
      opacity: 0.3;
      margin: 0 auto;
      background: linear-gradient(to left, white, rgba(255, 255, 255, 0.8) 50%, white 100%);
      transition: opacity 0.4s;
    }
    
    .pinned-top {
      top: 30px;
    }
    
    .full-height {
      box-shadow: none;
      min-height: 100vh;
    }
    
    .transitable {
      transition: top .2s ease-out;
    }
    
    .screen {
      position: relative;
      padding-top: 40px;
      margin: 0 auto;
      border-radius: 5px;
      overflow: hidden;
      background: #2b2d5b;
      width: 320px;
      height: 568px;
    }
    
    .controls {
      background: linear-gradient(to bottom, #fd2929, #fd5c5c);
      height: 100px;
    }
    
    .drag-wrapper {
      display: block;
      padding: 20px;
    }
    
    .drag-wrapper:active .drag,
    .drag-wrapper:hover .drag,
    .drag-wrapper:focus .drag {
      opacity: 0.7;
    }
    
    .board {
      padding: 0 20px;
      position: absolute;
      background: #2b2d5b;
      top: 360px;
      left: 0;
      right: 0;
    }
    
    .content-wrapper {
      padding: 20px;
      height: 320px;
    }
    
    .content-wrapper {
      background: #444;
    }
    
    .content {
      display: flex;
      justify-content: center;
      align-items: center;
      font-family: Helvetica;
      background: linear-gradient(to bottom, #3adffd, #00abfb);
      height: 100%;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="http://hammerjs.github.io/dist/hammer.min.js"></script>
    <div class="screen">
      <div class="content-wrapper">
        <div class="content">
          .content
        </div>
      </div>
      <div class="board">
        <a href="javascript:void(0)" class="drag-wrapper">
          <div class="drag"></div>
        </a>
        <div class="controls"></div>
      </div>
    </div>