A bug in making draggle flexible splitters

109 views Asked by At

I would like to make draggle splitters among several panels. There are some libraries on the Internet. I have found a solution, which is short and independent of any library (except JQuery).

However, there is a bug: when we drag the first splitter, then the second, then the first, etc., sometimes dragging one splitter may impact the position of the other one. This is certainly not expected.

Does anyone know how to fix it?

(function($) {
  $.fn.drags = function(opt) {

    opt = $.extend({
      handle: "",
      cursor: "ew-resize",
      min: 10
    }, opt);

    if (opt.handle === "") {
      var $el = this;
    } else {
      var $el = this.find(opt.handle);
    }

    var priorCursor = $('body').css('cursor');

    return $el.css('cursor', opt.cursor).on("mousedown", function(e) {

      priorCursor = $('body').css('cursor');
      $('body').css('cursor', opt.cursor);

      if (opt.handle === "") {
        var $drag = $(this).addClass('draggable');
      } else {
        var $drag = $(this).addClass('active-handle').parent().addClass('draggable');
      }
      var z_idx = $drag.css('z-index'),
        drg_h = $drag.outerHeight(),
        drg_w = $drag.outerWidth(),
        pos_y = $drag.offset().top + drg_h - e.pageY,
        pos_x = $drag.offset().left + drg_w - e.pageX;
      $drag.css('z-index', 1000).parents().on("mousemove", function(e) {

        var prev = $('.draggable').prev();
        var next = $('.draggable').next();

        // Assume 50/50 split between prev and next then adjust to
        // the next X for prev

        var total = prev.outerWidth() + next.outerWidth();

        console.log('l: ' + prev.outerWidth() + ', r:' + next.outerWidth());

        var leftPercentage = (((e.pageX - prev.offset().left) + (pos_x - drg_w / 2)) / total);
        var rightPercentage = 1 - leftPercentage;

        if (leftPercentage * 100 < opt.min || rightPercentage * 100 < opt.min) {
          return;
        }

        console.log('l: ' + leftPercentage + ', r:' + rightPercentage);

        prev.css('flex', leftPercentage.toString());
        next.css('flex', rightPercentage.toString());

        $(document).on("mouseup", function() {
          $('body').css('cursor', priorCursor);
          $('.draggable').removeClass('draggable').css('z-index', z_idx);
        });
      });
      e.preventDefault(); // disable selection
    });

  }
})(jQuery);

$('.handle').drags();
.flex-box {
  display: flex;
  width: 100%;
  margin: 0;
  height: 300px;
}
.flex-box .col {
  border: 1px solid green;
  flex: 0.33;
  padding: 12px;
  overflow-y: auto;
  overflow-x: hide;
}
.handle {
  width: 5px;
  text-align: center;
  color: white;
  transition: all ease-in 0.1s;
}
.draggable {
  background: pink;
}
body {}
<!DOCTYPE html>
<html>
<head>
  <script src="https://code.jquery.com/jquery-3.1.0.js"></script>
  <title>JS Bin</title>
</head>
<body>
  <div class="flex-box">
    <div class="col">
      <h1>Drag The splitters</h1>
      <p>Pellentesque ...</p>
    </div>
    <div class="handle"></div>
    <div class="col">
      <h1>Magic</h1>
      <p>Pellentesque ...</p>
    </div>
    <div class="handle"></div>
    <div class="col">
      <h1>Magic 2</h1>
      <p>Pellentesque ...</p>
    </div>
  </div>
</body>
</html>

1

There are 1 answers

0
Piou On BEST ANSWER

There was several things going in there and it took me some time to figure all out (I do accept credit cards :P)

First some events were keeping getting hooked, like mouseup, I believe mousemove too.

Then regarding the sizing problem, you were considering that the 2 elements around the splitter were sharing 100% of the flex container. This is of course wrong when you have 3 or more elements. What I did was just to get the shared percentage of the flex box of the 2 elements being concerned by the resizing operation:

var totalPercentage = parseFloat(prev.css('flex')) +  parseFloat(next.css('flex'));

With that and correcting the formula accordingly, it works flawlessly.

(function($) {
  $.fn.drags = function(opt) {

    opt = $.extend({
      handle: "",
      cursor: "ew-resize",
      min: 10
    }, opt);

    if (opt.handle === "") {
      var $el = this;
    } else {
      var $el = this.find(opt.handle);
    }

    var priorCursor = $('body').css('cursor');

    return $el.css('cursor', opt.cursor).on("mousedown", function(e) {
      
      priorCursor = $('body').css('cursor');
      $('body').css('cursor', opt.cursor);

      if (opt.handle === "") {
        var $drag = $(this).addClass('draggable');
      } else {
        var $drag = $(this).addClass('active-handle').parent().addClass('draggable');
      }
      
      var z_idx = $drag.css('z-index'),
        drg_h = $drag.outerHeight(),
        drg_w = $drag.outerWidth(),
        pos_y = $drag.offset().top + drg_h - e.pageY,
        pos_x = $drag.offset().left + drg_w - e.pageX;
      
      var mouseMove = function(e) {
        
        //console.log("mousemove");
        var prev = $('.draggable').prev();
        var next = $('.draggable').next();

        // Assume 50/50 split between prev and next then adjust to
        // the next X for prev

        var total = prev.outerWidth() + next.outerWidth();
        
        var totalPercentage = parseFloat(prev.css('flex')) +  parseFloat(next.css('flex'));

        //console.log('l: ' + prev.outerWidth() + ', r:' + next.outerWidth());
        var offset = prev.offset();
        if(offset){
          
          var leftPercentage = ((e.pageX - offset.left - drg_w / 2) / total) * totalPercentage;
          var rightPercentage = totalPercentage - leftPercentage;

          if (leftPercentage * 100 < opt.min || rightPercentage * 100 < opt.min) {
            return;
          }

          //console.log('l: ' + leftPercentage + ', r:' + rightPercentage);
          
          prev.css('flex', leftPercentage.toString());
          next.css('flex', rightPercentage.toString());
        }
      }
      
      $drag.css('z-index', 1000).parent().on("mousemove", mouseMove).on("mouseup", function() {
            //console.log("mouseup");
            $(this).off("mousemove", mouseMove).off("mouseup");
            $('body').css('cursor', priorCursor);
            $('.draggable').removeClass('draggable').css('z-index', z_idx);
        });
      e.preventDefault(); // disable selection
    });
    
    
  }
})(jQuery);

$('.handle').drags();
.flex-box {
  display: flex;
  width: 100%;
  margin: 0;
  height: 300px;
}
.flex-box .col {
  border: 1px solid green;
  flex: 0.33;
  padding: 12px;
  overflow-y: auto;
  overflow-x: hide;
}
.handle {
  width: 5px;
  text-align: center;
  color: white;
  transition: all ease-in 0.1s;
}
.draggable {
  background: pink;
}
body {}
<!DOCTYPE html>
<html>
<head>
  <script src="https://code.jquery.com/jquery-3.1.0.js"></script>
  <title>JS Bin</title>
</head>
<body>
  <div class="flex-box">
    <div class="col">
      <h1>Drag The splitters</h1>
      <p>Pellentesque ...</p>
    </div>
    <div class="handle"></div>
    <div class="col">
      <h1>Magic</h1>
      <p>Pellentesque ...</p>
    </div>
    <div class="handle"></div>
    <div class="col">
      <h1>Magic 2</h1>
      <p>Pellentesque ...</p>
    </div>
  </div>
</body>
</html>