引言
前不久,老大给我分配一个比较吊炸天的任务。要我实现:在一张图片上,可以用鼠标画框。除此以外,画出来的框,可以实现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;
})
}
这里其实大家就可以看出来了,得益于面向对象的机制,我们可以很简单的实现一些画框拖拽功能。并且该方法还留了回调的接口,方便根据不同的业务需求进行一些交互操作。