Of course, we can study the source code of the js library, or we can invent the wheel ourselves and try it out. The process is quite interesting... Next, I will implement the drag and drop function of page elements
Let’s start implementing it now. Let’s start with the top-level method, which is used to initialize a drag object. The method is declared as follows
function DragObject(cfg)
We use an object to pass in the cfg here, which is a bit like the configuration properties in Extjs
var dragObj = new DragObject({
el: 'exampleB ',
attachEl: 'exampleBHandle',
lowerBound: new Position(0, 0), //position represents a point with attributes x and y, which will be discussed in detail below
upperBound: new Position(500 , 500),
startCallback: ..., // The callback triggered when dragging is omitted here
moveCallback: ..., // The callback triggered during dragging
endCallback: . .., // Callback triggered when dragging ends
attachLater: ... // Whether to start monitoring drag events immediately
});
el in the configuration parameters can be The id of a specific element can also be directly a DOM object. attachEl is the handle element in the example. You can drag the element by dragging it. LowerBound and upperBound are used to limit the dragging range. They are both Position objects. Regarding the encapsulation of this object We will analyze the function below, don’t worry: ), if there is no input, there is no limit to the dragging range. startCallback, moveCallback, endCallback are all callback functions, attachLater is true or false. If you don’t understand clearly, check it out After the following analysis, I think you will definitely understand it soon...
Let’s write Position, the code is as follows:
function Position(x, y) {
this.X = x;
thix.Y = y;
}
Position .prototype = {
constructor: Position,
add : function(val) {
var newPos = new Position(this.X, this.Y);
if (val) {
newPos.X = val.X;
newPos.Y = val.Y;
}
return newPos; new Position(this.X, this.Y);
if (val) {
newPos.X -= val. >return newPos;
},
min : function(val) {
var newPos = new Position(this.X, this.Y);
if (val) {
newPos. X = this.X > val.X ? val.X : this.X;
newPos.Y = this.Y > val.Y ? val.Y : this.Y; 🎜>}
return newPos;
},
max : function(val) {
var newPos = new Position(this.X, this.Y);
if (val) {
newPos.X = this.X < val.X ? val.X : this.X;
newPos.Y = this.Y < val.Y ? val.Y : this.Y;
return newPos;
}
return newPos;
},
bound : function(lower, upper) {
var newPos = this.max(lower);
return newPos. min(upper);
},
check : function() {
var newPos = new Position(this.X, this.Y);
if (isNaN(newPos.X))
newPos.X = 0;
if (isNaN(newPos.Y))
newPos.Y = 0;
return newPos;
if(typeof el == 'string')
el = document.getElementById(el);
if(!el) return;
el.style.left = this.X 'px' ;
el.style.top = this.Y 'px';
}
};
A simple package of coordinate points, which saves two values: x, y coordinate. We can perform calculations and - operations with other coordinate points through the add and substract methods to return a calculated new coordinate point. The min and max functions, as their names suggest, are used to compare with other coordinate points and return the smaller sum. The value of the education.bound method returns a coordinate point within a limited range. The check method is used to ensure that the values of the attributes x and y are of numeric type, otherwise it will be set to 0. The final apply method is to apply the attributes x and y to the element style.left and top. Then I took out most of the remaining code and looked at it bit by bit:
Copy code
The code is as follows:
function DragObject(cfg) {
var el = cfg.el,
attachEl = cfg.attachEl,
lowerBound = cfg.lowerBound,
upperBound = cfg.upperBound,
startCallback = cfg.startCallback,
moveCallback = cfg.moveCallback,
endCallback = cfg.endCallback,
attachLater = cfg.attachLater;
if(typeof el == 'string')
el = document.getElementById(el);
if(!el) return;
if(lowerBound != undefined && upperBound != undefined) {
var tempPos = lowerBound.min(upperBound);
upperBound = lowerBound.max(upperBound);
lowerBound = tempPos;
}
var cursorStartPos,
elementStartPos,
dragging = false,
listening = false,
disposed = false;
function dragStart(eventObj) {
if(dragging || !listening || disposed) return;
dragging = true;
if(startCallback)
startCallback(eventObj, el);
cursorStartPos = absoluteCursorPosition(eventObj);
elementStartPos = new Position(parseInt(getStyle(el, 'left')), parseInt(getStyle(el, 'top')));
elementStartPos = elementStartPos.check();
hookEvent(document, 'mousemove', dragGo);
hookEvent(document, 'mouseup', dragStopHook);
return cancelEvent(eventObj);
}
function dragGo(e) {
if(!dragging || disposed) return;
var newPos = absoluteCursorPosition(e);
newPos = newPos.add(elementStartPos)
.subtract(cursorStartPos)
.bound(lowerBound, upperBound);
newPos.apply(el);
if(moveCallback)
moveCallback(newPos, el);
return cancelEvent(e);
}
function dragStopHook(e) {
dragStop();
return cancelEvent(e);
}
function dragStop() {
if(!dragging || disposed) return;
unhookEvent(document, 'mousemove', dragGo);
unhookEvent(document, 'mouseup', dragStopHook);
cursorStartPos = null;
elementStartPos = null;
if(endCallback)
endCallback(el);
dragging = false;
}
this.startListening = function() {
if(listening || disposed) return;
listening = true;
hookEvent(attachEl, 'mousedown', dragStart);
};
this.stopListening = function(stopCurrentDragging) {
if(!listening || disposed)
return;
unhookEvent(attachEl, 'mousedown', dragStart);
listening = false;
if(stopCurrentDragging && dragging)
dragStop();
};
this.dispose = function() {
if(disposed) return;
this.stopListening(true);
el = null;
attachEl = null;
lowerBound = null;
upperBound = null;
startCallback = null;
moveCallback = null;
endCallback = null;
disposed = true;
};
this.isDragging = function() {
return dragging;
};
this.isListening = function() {
return listening;
};
this.isDisposed = function() {
return disposed;
};
if(typeof attachEl == 'string')
attachEl = document.getElementById(attachEl);
// 如果没有配置, 或者没找到该Dom对象, 则用el
if(!attachEl) attachEl = el;
if(!attachLater)
this.startListening();
}
其中一些未给出方法, 在往下分析的过程中, 会一一给出....
我们先通过cfg来使el和attachEl指向实际的Dom对象, 如果attachEl没配置或者没找到对应元素则用el替代. 我们同时设置了一些在拖拽中要用到的变量. cursorStartPos用于保存鼠标按下开始拖拽时鼠标的坐标点. elementStartPos用于保存元素开始拖拽时的起始点. dragging, listening, disposed是一些状态变量. listening: 指drag object是否正在监听拖拽开始事件. dragging: 元素是否正在被拖拽. disposed: drag object被清理, 不能再被拖拽了.
在代码的最后, 我们看到如果attachLater不为true, 那么就调用startListening, 这是一个 public方法定义在drag object中, 让我们看下它的实现
this.startListening = function() {
if(listening || disposed) return;
listening = true;
hookEvent(attachEl, 'mousedown', dragStart);
};
前两行就是做个判断, 如果已经开始对拖拽事件进行监听或者清理过了, 就什么都不做直接return. 否则把listening状态设为true, 表示我们开始监听啦, 把dragStart函数关联到attachEl的mousedown事件上. 这里碰到个hookEvent函数, 我们来看看它的样子:
function hookEvent(el, eventName, callback) {
if(typeof el == 'string')
el = document.getElementById(el);
if(!el) return ;
if(el.addEventListener)
el.addEventListener(eventName, callback, false);
else if (el.attachEvent)
el.attachEvent('on' eventName, callback);
}
In fact, it’s nothing. It just makes a cross-browser package for monitoring element events. The same unhookEvent method is as follows
function unhookEvent(el, eventName, callback) {
if(typeof el == 'string')
el = document.getElementById(el);
if(!el) return;
if(el.removeEventListener)
el.removeEventListener(eventName, callback, false);
else if(el. detachEvent)
el.detachEvent('on' eventName, callback);
}
Then let’s take a look at the implementation of the dragStart function, which is a private function of the drag object
function dragStart(eventObj) {
if(dragging || !listening || disposed) return;
dragging = true;
if(startCallback)
startCallback(eventObj, el);
cursorStartPos = absoluteCursorPosition(eventObj);
elementStartPos = new Position( parseInt(getStyle(el, 'left')), parseInt(getStyle(el, 'top')));
elementStartPos = elementStartPos.check();
hookEvent(document, 'mousemove', dragGo);
hookEvent(document, 'mouseup', dragStopHook);
return cancelEvent(eventObj);
}
This function is called after the DOM object pointed to by attachEl captures the mousedown event . First, we first make sure that the drag object is in a state suitable for dragging. If dragging is in progress, or the drag event is not being monitored, or the "last things" have been processed, then do nothing. If everything is ok , we set the dragging status to true, and then "start". If startCallback is defined, then we call it, with mousedown event and el as parameters. Then we locate the absolute position of the mouse and save it to cursorStartPos. Then get Go to the current top and left of the dragged element, encapsulate it into a Position object and save it in elementStartPos. To be on the safe side, let’s check whether the attributes in elementStartPos are legal. Let’s look at the two hookEvent calls. One is the mousemove event, which means dragging is in progress and the dragGo function is called. . One is the mouseup event, which represents the end of dragging and calls the dragStopHook function. You may ask why the event is bound to the document instead of the element to be dragged, such as our el or attachEl here. Because considering the direct Binding events to elements may affect the effect due to some delays in the browser, so bind the events directly to the document. If you really don’t understand it, maybe the impact will not be big: P.... Read the last sentence cancelEvent(eventObj)
function cancelEvent(e) {
e = e ? e : window.event;
if(e.stopPropagation)
e.stopPropagation();
if(e.preventDefault)
e.preventDefault();
e.cancelBubble = true;
e.returnValue = false;
return false;
}
is used to stop bubbling and prevent default events, which can be understood as Security considerations.... There are some methods in dragStart that need to be introduced. Let’s take a look at absoluteCursorPosition first, and then look at getStyle
function absoluteCursorPosition(e) {
e = e ? e : window.event;
var x = e.clientX (document.documentElement || document.body ).scrollLeft;
var y = e.clientY (document.documentElement || document.body).scrollTop;
return new Position(x, y);
}
This method is only used to obtain the absolute position of the mouse in the browser, just take the scroll bar into account
function getStyle(el, property) {
if(typeof el == 'string')
el = document.getElementById(el);
if(!el || !property ) return;
var value = el.style[property];
if(!value) {
if(document.defaultView && document.defaultView.getComputedStyle) {
var css = document.defaultView .getComputedStyle(el, null);
value = css ? css.getPropertyValue(property) : null;
} else if (el.currentStyle) {
value = el.currentStyle[property];
}
}
return value == 'auto' ? '' : value;
}
The getStyle method is used to get the css attribute value of the element, so it doesn't matter your style Whether it is written inline or defined in css, we can get the correct value. Of course, we only need to get the top and left attributes of the element. The following method actually handles drag and drop work: dragGo
function dragGo(e) {
if(!dragging || disposed) return ;
var newPos = absoluteCursorPosition(e);
newPos = newPos.add(elementStartPos)
.subtract(cursorStartPos)
.bound(lowerBound, upperBound);
newPos.apply(el );
if(moveCallback)
moveCallback(newPos, el);
return cancelEvent(e);
}
This method is not complicated, like others The method is the same. We first check the status. If it is not being dragged or has been cleaned up, then do nothing. If everything goes well, we use the current position of the mouse, the initial position of the element, the initial position of the mouse, and the limited range (if Configure upperBound, lowerBound) to calculate a result point. Through the apply method, we assign the calculated coordinates to the elements style.top and style.left, and let the drag element determine its position. If moveCallback is configured, then call, Finally, here comes the cancelEvent... The new coordinate operation here is similar to the operation of jquery, because each method of the Position object returns a Position object... There is also a method dragStopHook in dragStart
function dragStopHook(e) {
dragStop();
return cancelEvent( e);
}
function dragStop() {
if(!dragging || disposed) return;
unhookEvent(document, 'mousemove', dragGo);
unhookEvent(document, ' mouseup', dragStopHook);
cursorStartPos = null;
elementStartPos = null;
if(endCallback)
endCallback(el);
dragging = false;
}
The key is to look at the dragStop method. Also determine the status first. If everything is ok, we remove the event bindings mousemove and mouseup, and release the values of cursorStartPos and elementStartPos. Once the drag is over...if After configuring endCallback, call it, and finally set the dragging status to false... Finally, the public methods that will be used are given
this.stopListening = function(stopCurrentDragging) {
if(!listening || disposed)
return;
unhookEvent(attachEl, 'mousedown', dragStart);
listening = false;
if(stopCurrentDragging && dragging)
dragStop();
};
this.dispose = function() {
if (disposed) return;
this.stopListening(true);
el = null;
attachEl = null;
lowerBound = null;
upperBound = null;
startCallback = null;
moveCallback = null;
endCallback = null;
disposed = true;
};
this.isDragging = function() {
return dragging;
};
this.isListening = function() {
return listening;
};
this.isDisposed = function() {
return disposed;
};
stopListening removes the mousedown event that listens to dragging, and sets the listening state listening to false. There is a parameter stopCurrentDragging which is well-known. The dispose method is used for some processing work. If you do not want the drag object to be dragged, then call Just dispose. As for the following three small methods isDragging, isListening, isDisposed, you can know at a glance and return the relevant status. Finally, give me a drop-down link to the source code
Download and click me Welcome friends to leave messages and communicate. !