拖和放是连个复合事件,由多个事件组成,所以不能用普通的事件处理来处理拖放遇到的问题,其中一个问题就是接收拖拽的元素是一组层叠嵌套的元素时,会出现多次响应
drop
事件的问题,这里提供一个解决此问题的方案。
通过一个静态辅助对象来解决此问题。droppable
事件有 over
和 out
事件,事件触发时记录进入了哪些元素又从哪些元素中出来,这些记录分别保存在两个数组中,每次触发事件后调用一个函数处理这些数据,得到最后 hover
的元素,这个元素就可以被视为需要接收拖拽的元素。
静态类的代码如下:
var dragDropFramework = {
// 当处于激活态时需要添加的Class
activeClass: 'active',
// 处于激活状态的 DOM 元素,默认为 null,没有激活元素时也为 null
activeElement: null,
/**
* 清理本次或上次拖拽产生的数据
*/
clean: function () {
this._overElements = [];
this._outElements = [];
// 先移除当前激活态元素的激活样式
if (this.activeElement !== null) {
$(this.activeElement).removeClass(this.activeClass);
}
this.activeElement = null;
},
/**
* 将 hover 的元素推送到队列中,并且计算当前的激活元素
* 注:为了解决“内层元素溢出时拖动时可能先进入内层元素后进入外层元素”的问题,
* 添加 over 元素时会做内外排序处理
*
* @param {Object} element over过的元素
*/
pushOverElements: function (element) {
// 如果新 hover 的元素包含已存在的元素,那么放在已存在元素的前面
var overElements = this._overElements;
for (var i = 0; i < overElements.length; i++) {
var overItem = overElements[i];
// 判断 overItem 是否在 element中
if (this._isIn(overItem, element)) {
overElements.splice(i, 0, element);
break;
}
}
if (i === overElements.length) {
this._overElements.push(element);
}
this._resetActiveElement();
},
/**
* 将 out 的元素推送到队列中,并且计算当前的激活元素
*
* @param {Object} element out的元素
*/
pushOutElements: function (element) {
this._outElements.push(element);
this._resetActiveElement();
},
/**
* 激活元素改变时触发的默认事件
*
* @param {Object} oldElement 改变前的激活元素
* @param {Object} newElement 改变后的激活元素
* @private
*/
_activeElementChange: function (oldElement, newElement) {
var activeClass = this.activeClass;
// 先移除当前激活态元素的激活样式
if (oldElement !== null) {
$(oldElement).removeClass(activeClass);
}
// 添加当前激活态元素的激活样式
if (newElement !== null) {
$(newElement).addClass(activeClass);
}
},
/**
* 判断参数是否为函数
*
* @param {Function} fn 待判断的函数
* @returns {boolean} 是否是函数
* @private
*/
_isFuntion: function (fn) {
return Object.prototype.toString.call(fn) === 'object Function';
},
/**
* 判断第一个元素是否在第二个元素中
*
* @param {Object} son 子元素
* @param {Object} father 父元素
* @returns {boolean} 判断结果
* @private
*/
_isIn: function (son, father) {
var result = false;
var parent;
do {
parent = son.parentNode;
if (parent === father) {
result = true;
break;
}
son = parent;
} while (parent.tagName !== 'BODY');
return result;
},
// hover 过的元素队列,当 out 队列中有 hover 种的元素时或将两边的元素都移除
_overElements: [],
// out 的元素队列
_outElements: [],
/**
* 根据 over 和 out 队列数据,重设激活态元素
* 注:当激活元素有改变时,会调用 activeElementChange 自定义回调函数
* @private
*/
_resetActiveElement: function () {
// 将 out 队列中有的元素从 over 和 out 队列中移除
var outElements = this._outElements;
var overElements = this._overElements;
for (var i = 0; i < outElements.length; i++) {
var outItem = outElements[i];
for (var j = 0; j < overElements.length; j++) {
var overItem = overElements[j];
if (outItem === overItem) {
outElements.splice(i, 1);
overElements.splice(j, 1);
break;
}
}
}
// 取最后 hover 的元素
var result;
if (overElements.length !== 0) {
result = overElements[overElements.length - 1];
}
else {
result = null;
}
var oldActiveElement = this.activeElement;
this.activeElement = result;
if (oldActiveElement !== result) {
// 自定义回调函数
if (this._isFuntion(this.activeElementChange)) {
this.activeElementChange(oldActiveElement, result);
}
this._activeElementChange(oldActiveElement, result);
}
}
};
示例模拟了三种情况:
这三种情况基本可以覆盖实际应用的全部场景,查看示例,打开控制台可以到更多关于事件的信息。