diff --git a/README.md b/README.md
index e6cd482..31c3140 100644
--- a/README.md
+++ b/README.md
@@ -1,31 +1,48 @@
-# Draggable.js #
-##### Make your dom elements draggable easily. #####
-
-### Examples
-DOM:
-
-
-
-To make the whole element draggable:
-
- var elementToDrag = document.getElementById('elementToDrag');
- draggable(elementToDrag);
-
-To make it draggable only when dragging the handle element:
-
- var elementToDrag = document.getElementById('elementToDrag');
- var handle = elementToDrag.getElementsByClassName('handle')[0];
- draggable(elementToDrag, handle);
-
-#### Notes
-* You have to provide the raw element, not the one wrapped by your favorite dom query lib. Using jQuery, for example, you'd need to do something like `var elementToDrag = $('#elementToDrag').get(0);`
-* If you are using AMD (e.g. require.js) this lib becomes a module. Otherwise it'll create a global `draggable`.
-
-### Browser Compatibility
-I've ran the tests in Chrome and Firefox.
-If you find any incompatibility or want to support other browsers, please do a pull request with the fix! :-)
-
-### License
-This is licensed under the feel-free-to-do-whatever-you-want-to-do license.
\ No newline at end of file
+# Draggable.js #
+##### Make your dom elements draggable easily. #####
+
+### Examples
+DOM:
+
+
+
+To make the whole element draggable:
+
+ var elementToDrag = document.getElementById('elementToDrag');
+ draggable(elementToDrag);
+
+To make it draggable only when dragging the handle element:
+
+ var elementToDrag = document.getElementById('elementToDrag');
+ var handle = elementToDrag.getElementsByClassName('handle')[0];
+ draggable(elementToDrag, handle);
+
+### Notes
+* You have to provide the raw element, not the one wrapped by your favorite dom query lib. Using jQuery, for example, you'd need to do something like `var elementToDrag = $('#elementToDrag').get(0);`
+* If you are using AMD (e.g. require.js) this lib becomes a module. Otherwise it'll create a global `draggable`.
+
+### Browser Compatibility
+* Chrome
+* Firefox
+* Internet Explorer 7+ (probably 6+, but untested)
+* Safari
+* Opera
+
+### To Do
+* Add iOS support
+* Defer to CSS3 transitions for hardware accelerated animation in modern browsers
+* Defer to HTML5 drag and drop API in modern browsers
+
+### License
+This is licensed under the feel-free-to-do-whatever-you-want-to-do license.
+
+### Changelog
+
+v2.0 (modified by Boris Cherny)
+* Minimized repaints by eliminating style queries on every step of the drag
+* Generally improved performance
+* Improved browser compatibility
+* Standardized event names (start -> dragstart, stop -> dragend)
+* Lots of bugfixes (eliminated outline dragging, fixed border/margin/zindex querying)
\ No newline at end of file
diff --git a/demo.html b/demo.html
new file mode 100644
index 0000000..d1fc7ed
--- /dev/null
+++ b/demo.html
@@ -0,0 +1,34 @@
+
+
+
+ draggable.js > Demo
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/draggable.js b/draggable.js
index 2886b11..1fab2e8 100644
--- a/draggable.js
+++ b/draggable.js
@@ -1,144 +1,212 @@
-!(function(moduleName, definition) {
- // Whether to expose Draggable as an AMD module or to the global object.
- if (typeof define === 'function' && typeof define.amd === 'object') define(definition);
- else this[moduleName] = definition();
-
-})('draggable', function definition() {
- var currentElement;
- var fairlyHighZIndex = '10';
-
- function draggable(element, handle) {
- handle = handle || element;
- setPositionType(element);
- setDraggableListeners(element);
- handle.addEventListener('mousedown', function(event) {
- startDragging(event, element);
- });
- }
-
- function setPositionType(element) {
- element.style.position = 'absolute';
- }
-
- function setDraggableListeners(element) {
- element.draggableListeners = {
- start: [],
- drag: [],
- stop: []
- };
- element.whenDragStarts = addListener(element, 'start');
- element.whenDragging = addListener(element, 'drag');
- element.whenDragStops = addListener(element, 'stop');
- }
-
- function startDragging(event, element) {
- currentElement && sendToBack(currentElement);
- currentElement = bringToFront(element);
-
-
- var initialPosition = getInitialPosition(currentElement);
- currentElement.style.left = inPixels(initialPosition.left);
- currentElement.style.top = inPixels(initialPosition.top);
- currentElement.lastXPosition = event.clientX;
- currentElement.lastYPosition = event.clientY;
-
- var okToGoOn = triggerEvent('start', { x: initialPosition.left, y: initialPosition.top, mouseEvent: event });
- if (!okToGoOn) return;
-
- addDocumentListeners();
- }
-
- function addListener(element, type) {
- return function(listener) {
- element.draggableListeners[type].push(listener);
- };
- }
-
- function triggerEvent(type, args) {
- var result = true;
- var listeners = currentElement.draggableListeners[type];
- for (var i = listeners.length - 1; i >= 0; i--) {
- if (listeners[i](args) === false) result = false;
- };
- return result;
- }
-
- function sendToBack(element) {
- var decreasedZIndex = fairlyHighZIndex - 1;
- element.style['z-index'] = decreasedZIndex;
- element.style['zIndex'] = decreasedZIndex;
- }
-
- function bringToFront(element) {
- element.style['z-index'] = fairlyHighZIndex;
- element.style['zIndex'] = fairlyHighZIndex;
- return element;
- }
-
- function addDocumentListeners() {
- document.addEventListener('selectstart', cancelDocumentSelection);
- document.addEventListener('mousemove', repositionElement);
- document.addEventListener('mouseup', removeDocumentListeners);
- }
-
- function getInitialPosition(element) {
- var top = 0;
- var left = 0;
- var currentElement = element;
- do {
- top += currentElement.offsetTop;
- left += currentElement.offsetLeft;
- } while (currentElement = currentElement.offsetParent);
-
- var computedStyle = getComputedStyle? getComputedStyle(element) : false;
- if (computedStyle) {
- left = left - (parseInt(computedStyle['margin-left']) || 0) - (parseInt(computedStyle['border-left']) || 0);
- top = top - (parseInt(computedStyle['margin-top']) || 0) - (parseInt(computedStyle['border-top']) || 0);
- }
-
- return {
- top: top,
- left: left
- };
- }
-
- function inPixels(value) {
- return value + 'px';
- }
-
- function cancelDocumentSelection(event) {
- event.preventDefault && event.preventDefault();
- event.stopPropagation && event.stopPropagation();
- event.returnValue = false;
- return false;
- }
-
- function repositionElement(event) {
- var style = currentElement.style;
- var elementXPosition = parseInt(style.left, 10);
- var elementYPosition = parseInt(style.top, 10);
-
- var elementNewXPosition = elementXPosition + (event.clientX - currentElement.lastXPosition);
- var elementNewYPosition = elementYPosition + (event.clientY - currentElement.lastYPosition);
-
- style.left = inPixels(elementNewXPosition);
- style.top = inPixels(elementNewYPosition);
-
- currentElement.lastXPosition = event.clientX;
- currentElement.lastYPosition = event.clientY;
-
- triggerEvent('drag', { x: elementNewXPosition, y: elementNewYPosition, mouseEvent: event });
- }
-
- function removeDocumentListeners(event) {
- document.removeEventListener('selectstart', cancelDocumentSelection);
- document.removeEventListener('mousemove', repositionElement);
- document.removeEventListener('mouseup', removeDocumentListeners);
-
- var left = parseInt(currentElement.style.left, 10);
- var top = parseInt(currentElement.style.top, 10);
- triggerEvent('stop', { x: left, y: top, mouseEvent: event });
- }
-
- return draggable;
-});
\ No newline at end of file
+function Draggable(element, handle, opts) {
+
+ 'use strict';
+
+ // options
+ this.options = {
+ setCursor: true, // change cursor to reflect draggable?
+ setPosition: true, // change draggable position to absolute?
+ direction: { // which directions to enable drag in
+ x: true, // true|false
+ y: true // true|false
+ },
+ limit: { // limit the drag bounds
+ x: null, // [minimum position, maximum position]
+ y: null // [minimum position, maximum position]
+ },
+ onDrag: null, // function(element, X position, Y position, event)
+ onDragStart: null, // function(element, X position, Y position, event)
+ onDragEnd: null // function(element, X position, Y position, event)
+ };
+
+ var options = this.options;
+
+ // set user-defined options
+
+ for (var opt in opts) {
+ if (options.hasOwnProperty(opt)) {
+ options[opt] = opts[opt];
+ }
+ }
+
+ // internal vars
+ var cursorOffsetX, cursorOffsetY, elementHeight, elementWidth;
+ var self = this;
+ var isIE = navigator.appName === 'Microsoft Internet Explorer';
+ var hasTouch = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
+
+ // public vars
+ this.element = handle || element;
+ this.X = 0;
+ this.Y = 0;
+
+ this.move = function(x ,y) {
+ var style = this.element.style;
+ var direction = options.direction;
+ var limit = options.limit;
+ var lowIsOk, highIsOk;
+
+ if (direction.x) {
+ if (limit.x !== null) {
+ lowIsOk = x > limit.x[0];
+ highIsOk = x + elementWidth <= limit.x[1];
+ }
+ else lowIsOk = highIsOk = 1;
+
+ self.X = x;
+ style.left =
+ lowIsOk && highIsOk ? x + 'px' : (!lowIsOk ? 0 : (limit.x[1]-elementWidth) + 'px');
+ }
+
+ if (direction.y) {
+ if (limit.y !== null) {
+ lowIsOk = y > limit.y[0],
+ highIsOk = y + elementHeight <= limit.y[1];
+ }
+ else lowIsOk = highIsOk = 1;
+
+ self.Y = y;
+ style.top = lowIsOk && highIsOk ? y + 'px' : (!lowIsOk ? 0 : (limit.y[1]-elementHeight) + 'px');
+ }
+ };
+
+ var init = function() {
+ // set the element
+ var element = self.element;
+ if (!element) throw new Error('Invalid element passed to draggable: ' + element);
+
+ // get element dimensions
+ var compStyle = getStyle(element);
+ elementHeight = nopx(compStyle.height);
+ elementWidth = nopx(compStyle.width);
+
+ // optional styling
+ var style = element.style;
+ if (options.setPosition) {
+ style.left = element.offsetLeft + 'px';
+ style.top = element.offsetTop + 'px';
+ style.right = 'auto';
+ style.bottom = 'auto';
+ style.position = 'absolute';
+ }
+ if (options.setCursor) style.cursor = 'move';
+
+ // attach mousedown event
+ addEvent(element, (hasTouch ? 'touchstart' : 'mousedown'), start);
+ };
+
+ var start = function(e) {
+ // cross-browser event
+ var ev = e || window.event;
+
+ // prevent browsers from visually dragging the element's outline
+ stopEvent(ev);
+
+ // set a high z-index, just in case
+ var element = self.element;
+ element.oldZindex = element.style.zIndex;
+ element.style.zIndex = 10000;
+
+ // set initial position
+ var initialPosition = getInitialPosition(element);
+ cursorOffsetX = (self.X=initialPosition.x) - (hasTouch ? ev.targetTouches[0] : ev).clientX;
+ cursorOffsetY = (self.Y=initialPosition.y) - (hasTouch ? ev.targetTouches[0] : ev).clientY;
+
+ // add event listeners
+ var doc = document;
+ addEvent(doc, 'selectstart', stopEvent);
+ if (hasTouch) {
+ addEvent(doc, 'touchmove', drag);
+ addEvent(doc, 'touchend', stop);
+ }
+ else {
+ addEvent(doc, 'mousemove', drag);
+ addEvent(doc, 'mouseup', stop);
+ }
+
+ // trigger start event
+ if (options.onDragStart) options.onDragStart(element, self.X, self.Y, ev);
+ };
+
+ var drag = function(e) {
+ // cross-browser event
+ var ev = e || window.event;
+
+ // compute new coordinates
+ var x = (hasTouch ? ev.targetTouches[0] : ev).clientX + cursorOffsetX;
+ var y = (hasTouch ? ev.targetTouches[0] : ev).clientY + cursorOffsetY;
+
+ // move the element
+ self.move(x, y);
+
+ // trigger drag event
+ if (options.onDrag) options.onDrag(self.element, x, y, ev);
+ };
+
+ var stop = function(e) {
+ // cross-browser event
+ var ev = e || window.event;
+
+ // remove event listeners
+ var doc = document;
+ removeEvent(doc, 'selectstart', stopEvent);
+ if (hasTouch) {
+ removeEvent(doc, 'touchmove', drag);
+ removeEvent(doc, 'touchend', stop);
+ }
+ else {
+ removeEvent(doc, 'mousemove', drag);
+ removeEvent(doc, 'mouseup', stop);
+ }
+
+ // resent element's z-index
+ self.element.style.zIndex = self.element.oldZindex;
+
+ // trigger dragend event
+ if (options.onDragEnd) options.onDragEnd(self.element, self.X, self.Y, ev);
+ };
+
+ var getInitialPosition = function(element) {
+
+ var top = 0;
+ var left = 0;
+ var i = element;
+
+ // compute element offset relative to the window
+ do {
+ top += i.offsetTop;
+ left += i.offsetLeft;
+ } while (i = i.offsetParent && !getStyle(i.parentNode).position);
+
+ // subtract margin and border widths
+ var style = getStyle(element);
+ if (style) {
+ left -= (nopx(style.marginLeft) || 0) - (nopx(style.borderLeftWidth) || 0);
+ top -= (nopx(style.marginTop) || 0) - (nopx(style.borderTopWidth) || 0);
+ }
+
+ return {x: left, y: top};
+ };
+
+ function addEvent(element, e, func) {
+ return isIE ? element.attachEvent('on'+e, func) : element.addEventListener(e, func, false);
+ }
+
+ function removeEvent(element, e, func) {
+ return isIE ? element.detachEvent('on'+e, func) : element.removeEventListener(e, func, false);
+ }
+
+ function getStyle(element) {
+ return isIE ? element.currentStyle : getComputedStyle(element);
+ }
+
+ var nopx = function(string) {
+ return parseInt(string, 10);
+ };
+
+ var stopEvent = function(e) {
+ isIE ? e.returnValue = false : e.preventDefault();
+ };
+
+ init();
+}
\ No newline at end of file
diff --git a/draggable.min.js b/draggable.min.js
index fd78f8b..da68329 100644
--- a/draggable.min.js
+++ b/draggable.min.js
@@ -1,4 +1 @@
-!function(h,g){"function"===typeof define&&"object"===typeof define.amd?define(g):this[h]=g()}("draggable",function(){function h(a){a.draggableListeners={start:[],drag:[],stop:[]};a.whenDragStarts=g(a,"start");a.whenDragging=g(a,"drag");a.whenDragStops=g(a,"stop")}function g(a,c){return function(d){a.draggableListeners[c].push(d)}}function i(a,b){for(var d=!0,e=c.draggableListeners[a],f=e.length-1;0<=f;f--)!1===e[f](b)&&(d=!1);return d}function j(a){a.preventDefault&&a.preventDefault();a.stopPropagation&&
-a.stopPropagation();return a.returnValue=!1}function k(a){var b=c.style,d=parseInt(b.left,10),e=parseInt(b.top,10),d=d+(a.clientX-c.lastXPosition),e=e+(a.clientY-c.lastYPosition);b.left=d+"px";b.top=e+"px";c.lastXPosition=a.clientX;c.lastYPosition=a.clientY;i("drag",{x:d,y:e,mouseEvent:a})}function l(a){document.removeEventListener("selectstart",j);document.removeEventListener("mousemove",k);document.removeEventListener("mouseup",l);var b=parseInt(c.style.left,10),d=parseInt(c.style.top,10);i("stop",
-{x:b,y:d,mouseEvent:a})}var c;return function(a,b){b=b||a;a.style.position="absolute";h(a);b.addEventListener("mousedown",function(d){var e,f;c&&(f=c,f.style["z-index"]=9,f.style.zIndex=9);a.style["z-index"]="10";a.style.zIndex="10";c=a;f=e=0;var b=c;do e+=b.offsetTop,f+=b.offsetLeft;while(b=b.offsetParent);if(b=getComputedStyle?getComputedStyle(c):!1)f=f-(parseInt(b["margin-left"])||0)-(parseInt(b["border-left"])||0),e=e-(parseInt(b["margin-top"])||0)-(parseInt(b["border-top"])||0);c.style.left=
-f+"px";c.style.top=e+"px";c.lastXPosition=d.clientX;c.lastYPosition=d.clientY;i("start",{x:f,y:e,mouseEvent:d})&&(document.addEventListener("selectstart",j),document.addEventListener("mousemove",k),document.addEventListener("mouseup",l))})}});
+function Drag(a,u,i){this.options={setCursor:true,setPosition:true,direction:{x:true,y:true},limit:{x:null,y:null},onDrag:null,onDragStart:null,onDragEnd:null};var b=this.options;for(opt in i){if(b.hasOwnProperty(opt)){b[opt]=i[opt]}}var g,d,k,t;var l=this;var m=navigator.appName==="Microsoft Internet Explorer";this.element=u||a;this.X;this.Y;this.move=function(v,D){var z=this.element.style;var B=b.direction;var w=b.limit;if(B.x){if(w.x!==null){var A=v>w.x[0],C=v+t<=w.x[1]}else{var A=C=1}l.X=v;z.left=A&&C?v+"px":(!A?0:(w.x[1]-t)+"px")}if(B.y){if(w.y!==null){var A=D>w.y[0],C=D+k<=w.y[1]}else{var A=C=1}l.Y=D;z.top=A&&C?D+"px":(!A?0:(w.y[1]-k)+"px")}};var p=function(){var w=l.element;if(!w){throw new Error("Invalid element passed to draggable: "+w)}var v=c(w);k=h(v.height);t=h(v.width);if(b.setPosition){var x=w.style;x.left=w.offsetLeft+"px";x.top=w.offsetTop+"px";x.right="auto";x.bottom="auto";x.position="absolute"}if(b.setCursor){x.cursor="move"}o(w,"mousedown",e)};var e=function(y){var y=y||event;s(y);var w=l.element;w.oldZindex=w.style.zIndex;w.style.zIndex=10000;var v=j(w);g=(l.X=v.x)-y.clientX;d=(l.Y=v.y)-y.clientY;var x=document;o(x,"selectstart",s);o(x,"mousemove",r);o(x,"mouseup",n);if(f=b.onDragStart){f(w,l.X,l.Y,y)}};var r=function(w){var w=w||event;var v=w.clientX+g;var z=w.clientY+d;l.move(v,z);if(f=b.onDrag){f(l.element,v,z,w)}};var n=function(w){var w=w||event;var v=document;q(v,"mousemove",r);q(v,"mouseup",n);q(v,"selectstart",s);l.element.style.zIndex=l.element.oldZindex;if(f=b.onDragEnd){f(l.element,l.X,l.Y,w)}};var j=function(w){var y=0;var x=0;var v=w;do{y+=v.offsetTop;x+=v.offsetLeft}while(v=v.offsetParent&&!c(v.parentNode).position);if(style=c(w)){x-=(h(style.marginLeft)||0)-(h(style.borderLeftWidth)||0);y-=(h(style.marginTop)||0)-(h(style.borderTopWidth)||0)}return{x:x,y:y}};function o(v,x,w){return m?v.attachEvent("on"+x,w):v.addEventListener(x,w,false)}function q(v,x,w){return m?v.detachEvent("on"+x,w):v.removeEventListener(x,w,false)}function c(v){return m?v.currentStyle:getComputedStyle(v)}var h=function(v){return parseInt(v,10)};var s=function(v){m?v.returnValue=false:v.preventDefault()};p()};
\ No newline at end of file