Re-invent the draggable elements of the wheel

Source: Internet
Author: User

Although JavaScript frameworks are everywhere, they all encapsulate a lot of practical functions that can quickly enable us to implement functions such as animation and drag-and-drop elements. However, driven by curiosity, I sometimes want to find out what is going on, let's look at how some functions are implemented. Of course, we can study the source code of the JS library, or try to invent the wheel on our own. The process is quite interesting... next I will implement the drag and drop function of the page elements.

Handle

Now let's start implementation. Let's start with the top-level method, which is used to initialize a drag object. The method declaration is as follows:

 
Function dragobject (CFG)

The CFG here is passed in with an object, a bit like configuring attributes in extjs

 
VaR dragobj = new dragobject ({El: 'exampleb', attachel: 'examplebhandle', lowerbound: New Position (0, 0), // position represents a vertex, with the property X, y: upperbound: New Position (500,500), startcallback :..., // The callback triggered when the drag operation starts is omitted here. movecallback :..., // The callback endcallback triggered during the Drag and Drop Process :..., // call back attachlater :... // whether to immediately start the drag event listener });

In the configuration parameter, El can be the ID of a specific element or a DOM object.
Attachel is the handle element in the example. Drag it to drag the element,
Lowerbound and upperbound are used to limit the drag range. They are both position objects. We will analyze the encapsulation and function of this object. don't worry:). If not, there is no limit on the drag range.
Startcallback, movecallback, and endcallback are all callback functions,
The value of attachlater is true or false. If you do not understand the analysis below, I think it will be clear soon ..

Write the position below,CodeAs 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;}, subtract: function (VAL) {var newpos = new position (this. x, this. y); If (VAL) {newpos. x-= Val. x; newpos. y-= Val. y;} return newpos;}, Min: function (VAL) {var newpos = new position (T His. 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;}, 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;}, apply: function (EL) {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 encapsulation of coordinate points, which stores two values: X and Y. we can use the ADD and substract methods to perform + operations and-operations with other coordinate points, and return a new coordinate point that has been computed. the min and Max functions are used to compare with other coordinate points and return smaller and larger values. the bound method returns a coordinate point within the specified range. the check method is used to ensure that the value of attribute X and Y is of the numerical type. Otherwise, 0 is set. the final apply method is to apply the attribute X and Y to the element style. left and top. next, I took out most of the remaining code and looked at it at 1.1 points:

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 = callback (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 d Ragging;}; this. islistening = function () {return listening;}; this. isdisposed = function () {return disposed;}; If (typeof attachel = 'string') attachel = document. getelementbyid (attachel); // use el if (! Attachel) attachel = El; If (! Attachlater) This. startlistening ();}

Some of the methods are not provided. In the process of further analysis, we will provide one by one ....

We first use CFG to direct El and attachel to actual DOM objects. If attachel is not configured or corresponding elements are not found, use el instead.
We also set some variables to be used in the drag and drop operation.
Cursorstartpos is used to save the coordinates of the mouse when the mouse is pressed and started to drag.
Elementstartpos is used to save the starting point when an element is dragged.
Dragging, listening, and disposed are some state variables.
Listening: indicates whether the drag object is listening for the drag start event.
Dragging: whether the element is being dragged.
Disposed: The drag object is cleared and cannot be dragged.

At the end of the code, we can see that if attachlater is not true, startlistening is called. This is
The public method is defined in drag object. Let's take a look at its implementation.

 
This. startlistening = function () {If (Listening | disposed) return; listening = true; hookevent (attachel, 'mousedown', dragstart );};

The first two rows are a judgment. If you have already listened to or cleared the drag and drop events, you will not directly return anything.
Otherwise, the listening status is set to true, indicating that the listener is started and the dragstart function is associated with the mousedown event of attachel.
Here we come across a hookevent function. Let's take a look at it:

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, there is nothing, that is, the listening of element events is encapsulated across browsers. 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 );}

Next 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 = callback (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 specified by attachel captures the mousedown event. First, we determine that the drag object is in a suitable drag-and-drop state,
If the drag is in progress, or you are not listening for the drag event, or you have completed the "post" operation, you can do nothing. If everything is OK
The dragging status is set to true, and then "started". If startcallback is defined, we will call it with the mousedown event and El parameters. next, locate the absolute position of the mouse and save it to cursorstartpos.
Then, we get the current top and left of the drag element and encapsulate it into the position object and save it to elementstartpos. For the sake of insurance, we will check whether the attribute in elementstartpos is legal.
Let's look at two hookevent calls. One is the mousemove event, which indicates that dragging is in progress and the draggo function is called.
One is the mouseup event, which indicates that the drag is over and the dragstophook function is called. you may ask why the event is bound to the document, not the elements to be dragged, such as El or attachel. because events are directly bound to elements, some browser latency may affect the effect, so events are directly bound to the document. if you do not really understand it, it may have little impact: p ....
View cancelevent (eventobj) In the last sentence)

 
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 ;}

It is used to stop bubbling and prevent default events. It can be understood as security considerations... some methods in dragstart need to be introduced. First
Let's take a look at absolutecursorposition. Let's take a look at getstyle.

Function absolutecursorposition (e) {e = e? E: window. event; var x = E. clientx + (document.doc umentelement | document. body ). scrollleft; var y = E. clienty + (document.doc umentelement | document. body ). scrolltop; return new position (x, y );}

This method is only used to obtain the absolute position of the mouse in the browser and take the scroll bar into consideration.

 
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 obtain the CSS attribute value of an element. In this way, no matter whether the style is written in inline or defined in CSS, we can get the correct value,
Of course, we only need to obtain the top and left attributes of the element here. The following is the true method for dealing with drag and drop operations.

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 complex. Like other methods, let's first check the status. If it is not being dragged or cleaned up, do nothing.
If everything goes well, we use the current cursor position, the initial element position, the initial mouse position, and the limited range (if upperbound and lowerbound are configured) to calculate a result point, the apply method is used to assign the coordinates of the calculation to the element style. top and style. left, let the drag element determine its position.
If movecallback is configured, call the next cancelevent... the new coordinate operation here is similar to the jquery operation, because each method of the position object returns a position object... there is also a method in 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 and determine the status first. If everything is OK, we will remove the BIND of the event mousemove and mouseup, and
The values of cursorstartpos and elementstartpos are released, and a drag and drop operation ends. If endcallback is configured, call the following command,
Finally, set the dragging status to false ...... Finally, provide the public method that will be used.

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 drag-and-drop mousedown event from the listener and sets the listener status listening to false. Here, the stopcurrentdragging parameter is known. the dispose method is used for processing. If you do not want the drag object to be dragged, call dispose. The following three small methods are available: isdragging, islistening, isdisposed returns the relevant status at a glance.
Finally, click the drop-down link of the source code to download a message!

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.