2

引言

前不久,老大给我分配一个比较吊炸天的任务。要我实现:在一张图片上,可以用鼠标画框。除此以外,画出来的框,可以实现resize,也就是说可以通过鼠标操作缩放,也可以进行拖拽。我就不提除此以外还需要实现的一些业务上的细节交互了,本篇文章只讲讲我实现的拖拽功能的原理。

初始画框对象

由于也是上的需求,要求不止画一个框,有可能用户会画好多框,如果只是简单的检测鼠标事件定位一个个框,很容易导致缩放和拖拽功能的紊乱,因此,运用面向对象的思想,将拖拽和缩放的框变成一个对象,其所有其他鼠标事件的绑定都在这个对象上。

废话不多说,直接上源码,思路很简单。

function newBox(options) {
    this.container = $('<div class="new_rect"></div>');
    this.dragLeft = $('<div class="new_react-left"><div class="dots"></div></div>');
    this.dragTop = $('<div class="new_react-top"><div class="dots"></div></div>');
    this.dragRight = $('<div class="new_react-right"><div class="dots"></div></div>');
    this.dragBottom = $('<div class="new_react-bottom"><div class="dots"></div></div>');
    this.dragTopLeft = $('<div class="new_react-dots new_react-top-left"></div>');
    this.dragTopRight = $('<div class="new_react-dots new_react-top-right"></div>');
    this.dragBottomLeft = $('<div class="new_react-dots new_react-bottom-left"></div>');
    this.dragBottomRight = $('<div class="new_react-dots new_react-bottom-right"></div>');
    this.sureBtn = $('<div class="sureBtn"></div>');
    this.cancelBtn = $('<div class="cancelBtn"></div>');
    this.enableEdit = true;
    this.init();
    this.options = options;
}

newBox.prototype = {
    init: function() {
        this.container.append(this.dragLeft);
        this.container.append(this.dragTop);
        this.container.append(this.dragRight);
        this.container.append(this.dragBottom);
        this.container.append(this.dragTopLeft);
        this.container.append(this.dragTopRight);
        this.container.append(this.dragBottomLeft);
        this.container.append(this.dragBottomRight);
        this.container.append(this.sureBtn);
        this.container.append(this.cancelBtn);

        this.container.on({
            'mousedown': this.containerMouseDown.bind(this),
            'click': this.enableEditFunc.bind(this)
          });
        this.dragRight.on('mousedown', this.dragRightMouseDown.bind(this));
        this.dragLeft.on('mousedown', this.dragLeftMouseDown.bind(this));
        this.dragTop.on('mousedown', this.dragTopMouseDown.bind(this));
        this.dragBottom.on('mousedown', this.dragBottomMouseDown.bind(this));
        this.dragTopLeft.on('mousedown', this.dragTopLeftMouseDown.bind(this));
        this.dragTopRight.on('mousedown', this.dragTopRightMouseDown.bind(this));
        this.dragBottomRight.on('mousedown', this.dragBottomRightMouseDown.bind(this));
        this.dragBottomLeft.on('mousedown', this.dragBottomLeftMouseDown.bind(this));

        this.sureBtn.on('click', this.sureFunc.bind(this));
        this.cancelBtn.on('click', this.cancelFunc.bind(this));
    },
    sureFunc: function() {
      this.disableEditFunc();
        if(this.options.sureFunc)
            this.options.sureFunc(this.container);
        return false;
    },
    cancelFunc: function() {
        this.container.remove();
        if(this.options.cancelFunc)
            this.options.cancelFunc(this.container);
    },
    enableEditFunc: function() {
        var sureBtn = this.sureBtn;
      
      // 设置正在编辑的框的zindex为最大
      this.container.css('z-index','100000000')

        // 先保存未保存的
        $('.new_rect .sureBtn').each(function() {
            if($(this)[0] == sureBtn[0]){
                return;
            }
        $(this).is(':visible') && $(this).click() && $(this).mouseleave();
      })

        this.enableEdit = true;
        this.container.find('.new_react-dots, .dots').show();
        this.sureBtn.show();
        this.cancelBtn.show();
    },
    disableEditFunc: function() {
      var width=this.container.width();
      var height=this.container.height();
      var area=parseInt(width*height);
      this.container.css('z-index',100000000-area);
        this.enableEdit = false;
        this.container.find('.new_react-dots, .dots').hide();
        this.sureBtn.hide();
        this.cancelBtn.hide();
    },
    setPosition: function(position) {
        this.container.css(position)
    },
    dragRightMouseDown: function(ev) {
      if(!this.enableEdit)
          return false;
      var rightElem = this.dragRight;
      var o_x = ev.pageX;
      var o_width = this.container.width();
      var o_right = parseFloat(this.container.css('right'));

      if(rightElem.setCapture) {
        rightElem.setCapture();
      }
      $(document).on("mousemove.dragRight", doDragRight.bind(this));
      $(document).on("mouseup.dragRight", stopDragRight.bind(this));

      return false;

      function doDragRight(e) {
        var right = o_right - e.pageX + o_x;
        if(right < 0)
            right = 0;
        if(right > o_width + o_right)
            right = o_width + o_right
        this.setPosition({
            right: right
        });
        return false;
        }
        function stopDragRight(e) {
        if(rightElem.releaseCapture) {
          rightElem.releaseCapture();
        }
        $(document).off("mousemove.dragRight");
        $(document).off("mouseup.dragRight");
        return false;
        }
    },
    dragLeftMouseDown: function(ev) {
      if(!this.enableEdit)
          return false;
      var leftElem = this.dragLeft;
      var o_x = ev.pageX;
      var o_width = this.container.width();
      var o_left = parseFloat(this.container.css('left'));

      if(leftElem.setCapture) {
        leftElem.setCapture();
      }
      $(document).on("mousemove.dragLeft", doDragLeft.bind(this));
      $(document).on("mouseup.dragLeft", stopDragLeft.bind(this));

      return false;

      function doDragLeft(e) {
        var left = o_left + e.pageX - o_x;
        if(left < 0)
            left = 0;
        if(left > o_width + o_left)
            left = o_width + o_left
        this.setPosition({
            left: left
        });
        return false;
        }
        function stopDragLeft(e) {
        if(leftElem.releaseCapture) {
          leftElem.releaseCapture();
        }
        $(document).off("mousemove.dragLeft");
        $(document).off("mouseup.dragLeft");
        return false;
        }
    },
    dragTopMouseDown: function(ev) {
      if(!this.enableEdit)
          return false;
      var topElem = this.dragTop;
      var o_y = ev.pageY;
      var o_height = this.container.height();
      var o_top = parseFloat(this.container.css('top'));

      if(topElem.setCapture) {
        topElem.setCapture();
      }
      $(document).on("mousemove.dragTop", doDragTop.bind(this));
      $(document).on("mouseup.dragTop", stopDragTop.bind(this));

      return false;

      function doDragTop(e) {
        var top = o_top + e.pageY - o_y;
        if(top < 0)
            top = 0;
        if(top > o_top + o_height)
            top = o_top + o_height
        this.setPosition({
          top: top
        });
        return false;
        }
        function stopDragTop(e) {
        if(topElem.releaseCapture) {
          topElem.releaseCapture();
        }
        $(document).off("mousemove.dragTop");
        $(document).off("mouseup.dragTop");
        return false;
        }
    },
    dragBottomMouseDown: function(ev) {
      if(!this.enableEdit)
          return false;
      var bottomElem = this.dragBottom;
      var o_y = ev.pageY;
      var o_height = this.container.height();
      var o_bottom = parseFloat(this.container.css('bottom'));

      if(bottomElem.setCapture) {
        bottomElem.setCapture();
      }
      $(document).on("mousemove.dragBottom", doDragBottom.bind(this));
      $(document).on("mouseup.dragBottom", stopDragBottom.bind(this));

      return false;

      function doDragBottom(e) {
        var bottom = o_bottom - e.pageY + o_y;
        if(bottom < 0)
            bottom = 0;
        if(bottom > o_bottom + o_height)
            bottom = o_bottom + o_height
        this.setPosition({
          bottom: bottom
        });
        return false;
        }
        function stopDragBottom(e) {
        if(bottomElem.releaseCapture) {
          bottomElem.releaseCapture();
        }
        $(document).off("mousemove.dragBottom");
        $(document).off("mouseup.dragBottom");
        return false;
        }
    },
    dragTopLeftMouseDown: function(ev) {
        if(!this.enableEdit)
          return false;
      var topLeftElem = this.dragTopLeft;
      var o_y = ev.pageY;
      var o_x = ev.pageX;
      var o_height = this.container.height();
      var o_width = this.container.width();
      var o_top = parseFloat(this.container.css('top'));
      var o_left = parseFloat(this.container.css('left'));

      if(topLeftElem.setCapture) {
        topLeftElem.setCapture();
      }
      $(document).on("mousemove.dragTopLeft", doDragTopLeft.bind(this));
      $(document).on("mouseup.dragTopLeft", stopDragTopLeft.bind(this));

      return false;

      function doDragTopLeft(e) {
        var top = o_top + e.pageY - o_y;
        var left = o_left + e.pageX - o_x;
        if(top < 0)
            top = 0;
        if(top > o_top + o_height)
            top = o_top + o_height;

        if(left < 0)
            left = 0;
        if(left > o_width + o_left)
            left = o_width + o_left;

        this.setPosition({
          top: top,
          left: left
        });
        return false;
        }
        function stopDragTopLeft(e) {
        if(topLeftElem.releaseCapture) {
          topLeftElem.releaseCapture();
        }
        $(document).off("mousemove.dragTopLeft");
        $(document).off("mouseup.dragTopLeft");
        return false;
        }
    },
    dragTopRightMouseDown: function(ev) {
        if(!this.enableEdit)
          return false;
      var topRightElem = this.dragTopRight;
      var o_y = ev.pageY;
      var o_x = ev.pageX;
      var o_height = this.container.height();
      var o_width = this.container.width();
      var o_top = parseFloat(this.container.css('top'));
      var o_right = parseFloat(this.container.css('right'));

      if(topRightElem.setCapture) {
        topRightElem.setCapture();
      }
      $(document).on("mousemove.dragTopRight", doDragTopRight.bind(this));
      $(document).on("mouseup.dragTopRight", stopDragTopRight.bind(this));

      return false;

      function doDragTopRight(e) {
        var top = o_top + e.pageY - o_y;
        var right = o_right - e.pageX + o_x;
        if(top < 0)
            top = 0;
        if(top > o_top + o_height)
            top = o_top + o_height;

        if(right < 0)
            right = 0;
        if(right > o_width + o_right)
            right = o_width + o_right;

        this.setPosition({
          top: top,
          right: right
        });
        return false;
        }
        function stopDragTopRight(e) {
        if(topRightElem.releaseCapture) {
          topRightElem.releaseCapture();
        }
        $(document).off("mousemove.dragTopRight");
        $(document).off("mouseup.dragTopRight");
        return false;
        }
    },
    dragBottomRightMouseDown: function(ev) {
        if(!this.enableEdit)
          return false;
      var bottomRightElem = this.dragBottomRight;
      var o_y = ev.pageY;
      var o_x = ev.pageX;
      var o_height = this.container.height();
      var o_width = this.container.width();
      var o_bottom = parseFloat(this.container.css('bottom'));
      var o_right = parseFloat(this.container.css('right'));

      if(bottomRightElem.setCapture) {
        bottomRightElem.setCapture();
      }
      $(document).on("mousemove.dragBottomRight", doDragTopRight.bind(this));
      $(document).on("mouseup.dragBottomRight", stopDragTopRight.bind(this));

      return false;

      function doDragTopRight(e) {
        var bottom = o_bottom - e.pageY + o_y;
        var right = o_right - e.pageX + o_x;
        if(bottom < 0)
            bottom = 0;
        if(bottom > o_bottom + o_height)
            bottom = o_bottom + o_height;

        if(right < 0)
            right = 0;
        if(right > o_width + o_right)
            right = o_width + o_right;

        this.setPosition({
          bottom: bottom,
          right: right
        });
        return false;
        }
        function stopDragTopRight(e) {
        if(bottomRightElem.releaseCapture) {
          bottomRightElem.releaseCapture();
        }
        $(document).off("mousemove.dragBottomRight");
        $(document).off("mouseup.dragBottomRight");
        return false;
        }
    },
    dragBottomLeftMouseDown: function(ev) {
        if(!this.enableEdit)
          return false;
      var bottomLeftElem = this.dragBottomLeft;
      var o_y = ev.pageY;
      var o_x = ev.pageX;
      var o_height = this.container.height();
      var o_width = this.container.width();
      var o_bottom = parseFloat(this.container.css('bottom'));
      var o_left = parseFloat(this.container.css('left'));

      if(bottomLeftElem.setCapture) {
        bottomLeftElem.setCapture();
      }
      $(document).on("mousemove.dragBottomLeft", doDragTopLeft.bind(this));
      $(document).on("mouseup.dragBottomLeft", stopDragTopLeft.bind(this));

      return false;

      function doDragTopLeft(e) {
        var bottom = o_bottom - e.pageY + o_y;
        var left = o_left + e.pageX - o_x;
        if(bottom < 0)
            bottom = 0;
        if(bottom > o_bottom + o_height)
            bottom = o_bottom + o_height;

        if(left < 0)
            left = 0;
        if(left > o_width + o_left)
            left = o_width + o_left;

        this.setPosition({
          bottom: bottom,
          left: left
        });
        return false;
        }
        function stopDragTopLeft(e) {
        if(bottomLeftElem.releaseCapture) {
          bottomLeftElem.releaseCapture();
        }
        $(document).off("mousemove.dragBottomLeft");
        $(document).off("mouseup.dragBottomLeft");
        return false;
        }
    },
    containerMouseDown: function(ev) {
            if(!this.enableEdit)
          return false;
            var o_x = ev.pageX;
      var o_y = ev.pageY;
      var containerWidth = this.container.width();
      var containerHeight = this.container.height();

      var o_top = parseFloat(this.container.css('top'));
      var o_left = parseFloat(this.container.css('left'));
      var o_right = parseFloat(this.container.css('right'));
      var o_bottom = parseFloat(this.container.css('bottom'));

      if(this.container.setCapture) {
        this.container.setCapture();
      }
      $(document).on("mousemove.container", doDrag.bind(this));
      $(document).on("mouseup.container", stopDrag.bind(this));
      return false;

      function doDrag(e) {
                var disY = e.pageY - o_y;
            var disX = e.pageX - o_x;
        var top = o_top + disY;
        var left = o_left + disX;
        var right = o_right - disX;
        var bottom = o_bottom - disY;

        if(top < 0)  {
            top = 0;
            bottom = o_bottom + o_top;
        }
        if(bottom < 0) {
            bottom = 0;
            top = o_top + o_bottom;
        }
        if(left < 0) {
            left = 0;
            right = o_right + o_left;
        }
        if(right < 0) {
            right = 0;
            left = o_right + o_left;
        }

        this.setPosition({top: top, bottom: bottom, left: left, right: right});
        return false;
            }

            function stopDrag(ev) {
                if(this.container.releaseCapture) {
            this.container.releaseCapture();
          }
        $(document).off("mousemove.container");
        $(document).off("mouseup.container");
          return false;
          }
        }
}

至此,一个支持缩放和拖拽功能的box对象已经创建好了,但是,还没有实现初始画框功能,其实画初始框的思路更加简单,就是记录mousedown的时候鼠标地的x,y坐标和mouseup时鼠标的x,y坐标,然后根据坐标值,设置新建的div的宽度和高度和定位就可以了。下面是源码,我直接把它做成了jquery插件。

$.fn.draw_drag_test = function(options) {
    var position = {}
    var elemWidth, elemHeight, o_x, o_y, d_x, d_y, rect, o_left, o_top;

    var mouseDown = function(e) {
      $(this).on({
        'mousemove': mouseMove,
        'mouseup': mouseUp
      })

      var elemLeft = $(this).offset().left,
          elemTop = $(this).offset().top;

      elemWidth = $(this).width();
      elemHeight = $(this).height();
      o_x = e.pageX;
      o_y = e.pageY;

      position.left = o_left = o_x - elemLeft;
      position.top = o_top = o_y - elemTop;

      rect = new newBox(options);
      rect.container.css('z-index','100000000');
      $(this).append(rect.container);

    }

    var mouseMove = function(e) {
      var m_x = e.pageX,
          m_y = e.pageY;

      d_x = m_x - o_x;
      d_y = m_y - o_y;

      position.right = elemWidth - position.left - d_x;
      position.bottom = elemHeight - position.top - d_y;

      if(d_x < 0) {
          position.right = elemWidth - o_left;
          position.left = o_left + d_x;
      }else {
          position.left = o_left;
      }

      if(d_y < 0) {
          position.bottom = elemHeight - o_top;
          position.top = o_top + d_y;
      }else {
          position.top = o_top;
      }

      rect.setPosition(position);
    }

    var mouseUp = function(e) {
      $(this).off('mouseup', mouseUp);
      $(this).off('mousemove', mouseMove);
      $(this).off('mousedown', mouseDown);
    }

    return this.each(function() {
      $(this).off('mousedown').on({
        'mousedown': mouseDown
      })
      return this;
    })

}

这里其实大家就可以看出来了,得益于面向对象的机制,我们可以很简单的实现一些画框拖拽功能。并且该方法还留了回调的接口,方便根据不同的业务需求进行一些交互操作。


mengera88
1.4k 声望65 粉丝

欢迎和我一起讨论前端方面的知识