Pure javascript: Set border for draggable elements - javascript

Good day,
Learning Javascript and trying to make draggable elements inside a container.
How to set the draggable border so that elements won't be able to move outside it ?
Right now i have a problem when you drag something to the bottom or right border the element moves outside the container.
fiddle
my HTML looks like this :
<div id="container">
<div id="comboCon1"></div>
<div id="comboCon2"></div>
</div>
Here is the function where i get all positions and call the onmousemove Event :
function OnMouseClickDown(event) {
var target; // -> Element that triggered the event
if (event.target != null) { // -> If Browser is IE than use 'srcElement'
target = event.target;
} else {
target = event.srcElement;
}
// Check which button was clicked and if element has class 'draggable'
if ((event.button == 1 || event.button == 0) && target.className == "draggable") {
// Current Mouse position
startX = event.clientX;
startY = event.clientY;
// Current Element position
offsetX = ExtractNumber(target.style.left); // -> Convert to INT
offsetY = ExtractNumber(target.style.top);
// Border ( Div Container )
minBoundX = target.parentNode.offsetLeft; // Minimal -> Top Position.
minBoundY = target.parentNode.offsetTop;
maxBoundX = minBoundX + target.parentNode.offsetWidth - target.offsetWidth; // Maximal.
maxBoundY = minBoundY + target.parentNode.offsetHeight - target.offsetHeight;
oldZIndex = target.style.zIndex;
target.style.zIndex = 10; // -> Move element infront of others
dragElement = target; // -> Pass to onMouseMove
document.onmousemove = OnMouseMove; // -> Begin drag.
document.body.focus() // -> Cancel selections
document.onselectstart = function () { return false }; // -> Cancel selection in IE.
}
}
And here is onmousemove Event :
function OnMouseMove(event) {
dragElement.style.left = Math.max(minBoundX, Math.min(offsetX + event.clientX - startX, maxBoundX)) + "px";
dragElement.style.top = Math.max(minBoundY, Math.min(offsetY + event.clientY - startY, maxBoundY)) + "px";
}

there is a little change in css for solve this problem, because you are usingf position "relative" the offset of container is given to the child is draged
so in my demo put drag element in position absolute, and change offsetWidth for clientWidth and seems works ( horizontal):
// Draggable Div 1
document.getElementById("comboCon1").style.position = "relative"; // -> Add position relative
document.getElementById("comboCon1").style.width = "151px";
document.getElementById("comboCon1").style.height = "10px";
document.getElementById("comboCon1").setAttribute("class", "draggable");
document.getElementById("comboCon1").style.border = "1px solid black";
document.getElementById("comboCon1").style.padding = "0px";
// Draggable Div 2
document.getElementById("comboCon2").style.position = "relative";
document.getElementById("comboCon2").style.width = "151px";
document.getElementById("comboCon2").setAttribute("class", "draggable");
document.getElementById("comboCon2").style.border = "1px solid black";
document.getElementById("comboCon2").style.padding = "10px";
// Container
document.getElementById("container").style.border = "1px solid black";
document.getElementById("container").style.width = "500px";
document.getElementById("container").style.height = "500px";
//////////////////////
// Begin Drag events
//////////////////////
var startX = 0; //-> Mouse position.
var startY = 0;
var offsetX = 0; // -> Element position
var offsetY = 0;
var minBoundX = 0; // -> Top Drag Position ( Minimum )
var minBoundY = 0;
var maxBoundX = 0; // -> Bottom Drag Position ( Maximum )
var maxBoundY = 0;
var dragElement; // -> Pass the target to OnMouseMove Event
var oldZIndex = 0; // -> Increase Z-Index while drag
// 1)
initDragDrop(); // -> initialize 2 Events.
function initDragDrop() {
document.onmousedown = OnMouseClickDown;
document.onmouseup = OnMouseClickUp;
}
// 2) Click on Element
function OnMouseClickDown(event) {
var target; // -> Element that triggered the event
if (event.target != null) { // -> If Browser is IE than use 'srcElement'
target = event.target;
} else {
target = event.srcElement;
}
// Check which button was clicked and if element has class 'draggable'
if ((event.button == 1 || event.button == 0) && target.className == "draggable") {
// Current Mouse position
startX = event.clientX;
startY = event.clientY;
// Current Element position
offsetX = ExtractNumber(target.style.left); // -> Convert to INT
offsetY = ExtractNumber(target.style.top);
// Border ( Div Container )
minBoundX = target.parentNode.offsetLeft; // Minimal -> Top Position.
console.log(target.parentNode.getBoundingClientRect(), target)
minBoundY = target.parentNode.offsetTop;
maxBoundX = minBoundX + target.parentNode.clientWidth - target.clientWidth; // Maximal.
console.log(maxBoundX, target.parentNode.clientWidth, target.clientWidth);
maxBoundY = minBoundY + target.parentNode.offsetHeight - target.offsetHeight;
oldZIndex = target.style.zIndex;
target.style.zIndex = 10; // -> Move element infront of others
target.style.position = 'absolute'
dragElement = target; // -> Pass to onMouseMove
document.onmousemove = OnMouseMove; // -> Begin drag.
document.body.focus() // -> Cancel selections
document.onselectstart = function () { return false }; // -> Cancel selection in IE.
}
}
// 3) Convert current Element position in INT
function ExtractNumber(value) {
var number = parseInt(value);
if (number == null || isNaN(number)) {
return 0;
}
else {
return number;
}
}
// 4) Drag
function OnMouseMove(event) {
dragElement.style.left = Math.max(minBoundX, Math.min(offsetX + event.clientX - startX, maxBoundX)) + "px";
dragElement.style.top = Math.max(minBoundY, Math.min(offsetY + event.clientY - startY, maxBoundY)) + "px";
}
// 5) Drop
function OnMouseClickUp(event) {
if (dragElement != null) {
dragElement.style.zIndex = oldZIndex; // -> set Z-index 0.
document.onmousemove = null;
document.onselectstart = null;
dragElement = null; // -> No more element to drag.
}
}

Related

Object alignment in fabricJS after zoom and pan

I'm using fabricJS with Angular when I want to align an element to left for example I'm using the following method
alignLeft() {
// Get Selected Elements
var obj = this.canvas.getActiveObject();
// if no element is selected
if (obj !== undefined) {
// Bounding Box of the selected element
var bound = obj.getBoundingRect();
obj.set('left', (obj.left - bound.left));
//this.canvas.getActiveObject().setCoords();
this.canvas.renderAll();
}
}
The alignment works like a charm only then the zoom is in 100%, if I change the zoom or pan the canvas everything goes in the wrong way. Bellow is the methods I use for zooming In/Out
this.canvas.on('mouse:wheel', function (opt) {
var delta = opt.e.deltaY;
var zoom = this.canvas.getZoom();
zoom *= 0.999 ** delta;
if (zoom > 20) zoom = 20;
if (zoom < 0.01) zoom = 0.01;
this.canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom.toFixed(4));
opt.e.preventDefault();
opt.e.stopPropagation();
}.bind(this));
this.canvas.on('mouse:down', function (opt) {
var evt = opt.e;
if (evt.altKey === true) {
this.isDragging = true;
this.selection = false;
this.lastPosX = evt.clientX;
this.lastPosY = evt.clientY;
}
});
this.canvas.on('mouse:move', function (opt) {
if (this.isDragging) {
var e = opt.e;
var vpt = this.viewportTransform;
vpt[4] += e.clientX - this.lastPosX;
vpt[5] += e.clientY - this.lastPosY;
this.requestRenderAll();
this.lastPosX = e.clientX;
this.lastPosY = e.clientY;
}
});
this.canvas.on('mouse:up', function (opt) {
// on mouse up we want to recalculate new interaction
// for all objects, so we call setViewportTransform
this.setViewportTransform(this.viewportTransform);
this.isDragging = false;
this.selection = true;
});
After I read multiple questions and the fabricJS Documentation I changed the alignLeft() method to the following:
alignLeft() {
// Get Selected Elements
var obj = this.canvas.getActiveObject();
// if no element is selected
if (obj !== undefined) {
// Bounding Box of the selected element
var bound = obj.getBoundingRect();
let p = {x: (obj.width / 2), y: obj.top}
var invertedMatrix = fabric.util.invertTransform(this.canvas.viewportTransform);
let newp = fabric.util.transformPoint(p, invertedMatrix);
obj.set('left', newp.x);
//this.canvas.getActiveObject().setCoords();
this.canvas.renderAll();
}
}
You need to user ViewPortCoordinates (see fabricJS Docs) to get top left, top right, bottom left, bottom right coordinates of the canvas according the the view port.
Describe canvas element extension over design properties are tl,tr,bl,br. if canvas is not zoomed/panned those points are the four corner of canvas if canvas is viewportTransformed you those points indicate the extension of canvas element in plain untrasformed coordinates The coordinates get updated with #method calcViewportBoundaries.
for alignLeft() method you can update it to the following:
alignLeft() {
// Get Selected Elements
var obj = this.canvas.getActiveObject();
// if no element is selected
if (obj !== undefined) {
const viwePortCoords = this.canvas.vptCoords;
obj.set('left', viwePortCoords.bl.x + (obj.width / 2));
this.canvas.renderAll();
}
this.canvas.fire('object:modified', { target: obj });
}

How to make mousemove into touchmove?

Im new to mobile touch events.
Im trying to make this code work on mobile too, but im afraid that im just wasting my time. I would not like to add some sort of library, just vanilla javascript.
Its a roadmap that i move inside div.
Any hints please?
Best regards,
Christian
function startDrag(e) {
// determine event object
if (!e) {
var e = window.event;
}
if(e.preventDefault) e.preventDefault();
// IE uses srcElement, others use target
targ = e.target ? e.target : e.srcElement;
if (targ.className != 'roadmap') {return};
// calculate event X, Y coordinates
offsetX = e.clientX;
offsetY = e.clientY;
// assign default values for top and left properties
if (!targ.style.left) { targ.style.left='-140px'};
if (!targ.style.top) { targ.style.top='-300px'};
// calculate integer values for top and left
// properties
coordX = parseInt(targ.style.left);
coordY = parseInt(targ.style.top);
drag = true;
// move div element
document.ontouchmove=dragDiv;
return false;
}
function dragDiv(e) {
if (!drag) {return};
if (!e) { var e= window.event};
// target
console.log(e.target)
var t = e.target,
img = t,
parent = img.parentElement,
imgWidth = img.clientWidth,
imgHeight = img.clientHeight;
// maxes
var y = coordY+e.clientY-offsetY,
x = coordX+e.clientX-offsetX;
// set boundies
if ( parent.clientHeight == null ) {
parent.clientHeight = 1;
targ.style.left=1+'px';
}
var imgBottom = parent.clientHeight-imgHeight
imgRight = parent.clientWidth-imgWidth;
// stop drag on overflow
if ( // left
/^-\d+$/.test(y) &&
// // top
/^-\d+$/.test(x) &&
// // bottom
imgBottom < y &&
// // bottom
imgRight < x
) {
targ.style.left=coordX+e.clientX-offsetX+'px';
targ.style.top=coordY+e.clientY-offsetY+'px';
};
return false;
}
function stopDrag() {
drag=false;
}
window.onload = function() {
document.onmousedown = startDrag;
document.onmouseup = stopDrag;
// mobile
document.addEventListener("touchmove", startDrag, false);
document.addEventListener("touchend", stopDrag, false);
}
Try using the 'touchstart' event instead of 'touchmove':
document.addEventListener("touchstart", startDrag, false);
document.addEventListener("touchend", stopDrag, false);
Then set your 'offsetX' and 'offsetY' variables to use either the mouse or touch coordinates:
offsetX = e.clientX || e.touches[0].clientX;
offsetY = e.clientY || e.touches[0].clientY;
Hope this helps.

convert function to use css translate3d

I've written a function in Javascript to make images draggable within a container. Even if the image is enlarged it can be dragged all over the screen without disappearing from it. My function relies heaving on using style.top and style.left. Now I've heard that using translate3d might provide better performance. This is interesting because I changed my image scale function, which uses a slider, to scale3d and the scaling is clearly smoother, no doubt. So could anyone help me convert this function I've written to use translate3d? I've tried and tried but have kept failing. Many Thanks:
EDIT: I put up a jsfiddle
https://jsfiddle.net/bx4073tr/
Please note that imgRect is the parent div while img is the image itself (it's in an img tag contained in the div).
function makeImageDraggable(event) {
// Make an image draggable but within bounds of container
let overflow_vertical = false;
let overflow_horizontal = false;
// bounding rectangles to hold image and imageContainer
let imgRect = img.getBoundingClientRect();
let imgContainerRect = imageContainer.getBoundingClientRect();
// find out if image overflows it's container div
// check for vertical overflow, getBoundingClientRect().height will get the real height after the image is scaled
if ( imgRect.height > imageContainer.offsetHeight ) {
overflow_vertical = true;
}
// check for horizontal overflow
if ( imgRect.width > imageContainer.offsetWidth ) {
overflow_horizontal = true;
}
// if there is no overflow, either horizontal or vertical, then do absolutely nothing
if (!overflow_horizontal && !overflow_vertical) {
// nothing to do
} else {
// otherwise make image draggable
event = event || window.event;
// get initial mouse position
let startX = event.clientX;
let startY = event.clientY;
// get position of image to be dragged
let offsetX = pixelToFloat(img.style.left);
let offsetY = pixelToFloat(img.style.top);
// add onmousemove event now we are sure user has initiated a mousedown event
window.onmousemove = function(mousemove_event) {
if (mousemove_event == null) {
mousemove_event = window.event;
}
// calculate bounds so that image does not go off the page
// if there is an overflow, the image will be bigger than the container
// so we need to find the maximum distance we can go upwards, downwards and sideways
// using img.getBoundingClientRect, we can get the width of the scaled image, we also get the width of the container
// divide it by 2 so we can move the same number of pixels in either direction
// max right and left
let max_right = -1 * ( ((imgRect.right - imgRect.left) - (imgContainerRect.right - imgContainerRect.left))/2 );
// should be a positive number
let max_left = -1 * (max_right);
// max bottom and top
let max_bottom = -1 * ( ((imgRect.bottom - imgRect.top) - (imgContainerRect.bottom - imgContainerRect.top))/2 );
// should be a positive number
let max_top = -1 * (max_bottom);
// Dragging image left and right
if (!overflow_horizontal) {
} else {
let scrollX = (offsetX + mousemove_event.clientX - startX);
// img.style.left will keep increasing or decreasing, check if it approaches max_left or max_right
if (scrollX >= max_left || scrollX <= max_right) {
//return false;imageContainer.style.webkitTransform = 'translate3d(' + newX + 'px,' + newY + 'px, 0)';
} else {
if (scrollX < max_left) { img.style.left = min(scrollX, max_left) + 'px'; }
if (scrollX > max_right) { img.style.left = max(scrollX, max_right) + 'px'; }
}
}
// Dragging image top to bottom
if (!overflow_vertical) {
} else {
let scrollY = (offsetY + mousemove_event.clientY - startY);
// as an expanded image is pulled downwards, img.style.top keeps increasing to approach max_top
// if it reaches max top, simply do nothing, else keep increasing
// check for both conditions, approaching max_top and approaching max_bottom
if (scrollY >= max_top || scrollY <= max_bottom) {
// return false;
} else {
if (scrollY < max_top) { img.style.top = min(scrollY, max_top) + 'px'; }
if (scrollY > max_bottom) { img.style.top = max(scrollY, max_bottom) + 'px'; }
}
}
// return
return false;
}
}
// cancel mousemove event on mouseup
window.onmouseup = function(mouseup_event) {
window.onmousemove = null;
// Should not return false as it will interfere with range slider
}
// return false
return false;
}
Works now.
See makeDraggable method in the fiddle below:
https://jsfiddle.net/daibatzu/0u74faz6/6/
All you have to do is add this function to the event listener for the image like:
var img = document.getElementById('myImage');
img.addEventListener('mousedown', function(event) { makeDraggable(event); });
Code
function makeDraggable(event) {
// get bounding rectangle
let imgRect = img.getBoundingClientRect();
let parentRect = parent.getBoundingClientRect();
// check overflow
let overflow_horizontal = (imgRect.width > parent.offsetWidth ? true : false);
let overflow_vertical = (imgRect.height > parent.offsetHeight ? true : false);
// get start position
let startX = event.pageX - translateX, startY = event.pageY - translateY;
let max_left = parentRect.left - imgRect.left;
let max_top = parentRect.top - imgRect.top;
window.onmousemove = function(evt) {
// set event object
if (evt == null) { evt = window.event; }
// Say max_left is 160px, this means we can only translate from 160px to -160px to keep the image visible
// so we check if the image moves beyond abs(160), if it does, set it to 160 or -160 depending on direction, else, let it continue
translateX = (Math.abs(evt.pageX - startX) >= max_left ? (max_left * Math.sign(evt.pageX - startX)) : (evt.pageX - startX));
translateY = (Math.abs(evt.pageY - startY) >= max_top ? (max_top * Math.sign(evt.pageY - startY)) : (evt.pageY - startY));
// check if scaled image width is greater than it's container. if it isn't set translateX to zero and so on
translateX = overflow_horizontal ? translateX : 0, translateY = overflow_vertical ? translateY : 0;
// translate parent div
parent.style['-webkit-transform'] = 'translate(' + translateX + 'px, ' + translateY + 'px)';
// return
return false;
}
window.onmouseup = function(evt) {
// set mousemove event to null
window.onmousemove = null;
}
return false;
};

Detect container, while moving an element over it

I making a simple drag'n'drop interface. I have a bunch of containers ("wrapper") and some dynamically added items ("dragElement") in one of them. So I need, when I move item over another container, JS detect it and move the item there when the drag is finished.
I tried to detect container with "onmouseover" and "mouseup" when dragging item, but had no success, because, actually, mouse always was over the dragged element.
So how can I detect container when drag item? In pure JS please...
document.onmousedown = function(e) {
var dragElement = e.target;
if (!dragElement.classList.contains('draggable')) return;
var coords, shiftX, shiftY, detectPage;
startDrag(e.clientX, e.clientY);
document.onmousemove = function(e) {
moveAt(e.clientX, e.clientY);
};
wrapper.onmouseover = function(e) {
detectPage = e.target;
console.log(detectPage);
};
dragElement.onmouseup = function() {
finishDrag();
};
function startDrag(clientX, clientY) {
shiftX = clientX - dragElement.getBoundingClientRect().left;
shiftY = clientY - dragElement.getBoundingClientRect().top;
dragElement.style.position = 'fixed';
document.body.appendChild(dragElement);
moveAt(clientX, clientY);
};
function finishDrag() {
dragElement.style.top = parseInt(dragElement.style.top) - wrapper.getBoundingClientRect().top + 'px';
dragElement.style.position = 'absolute';
wrapper.onmouseup = function(e) {
var selectPage = e.target;
}
wrapper.appendChild(dragElement);
document.onmousemove = null;
dragElement.onmouseup = null;
};
function moveAt(clientX, clientY) {
var newX = clientX - shiftX;
var newY = clientY - shiftY;
if (newX < 0) newX = 0;
if (newX > wrapper.offsetWidth - dragElement.offsetWidth) {
newX = wrapper.offsetWidth - dragElement.offsetWidth;
}
dragElement.style.left = newX + 'px';
dragElement.style.top = newY + 'px';
};
return false;
};
Well, no one help. So one free day gone to find the solution. All I can found is to delete function finishDrag() from dragElement.onmouseup and change it to the code below.
If in shorter, when onmouseup comes, dragElement must go to display:none and now we can get access to the object near the mouse cursor through elementFromPoint. When we done with it, we can easily detects container, bring an element back to display:block and put it to that container...
Hope, it helps to someone...
dragElement.onmouseup = function(e) {
dragElement.style.display = 'none';
var selectPage = document.elementFromPoint(e.clientX, e.clientY);
dragElement.style.display = 'block';
dragElement.style.top = parseInt(dragElement.style.top) - selectPage.getBoundingClientRect().top + 'px';
dragElement.style.position = 'absolute';
selectPage.appendChild(dragElement);
document.onmousemove = null;
dragElement.onmouseup = null;
};

Calculating window dragging and skewing in JavaScript

I am using JavaScript and trying to make a skew effect on a div.
First, take a look at this video: http://www.youtube.com/watch?v=ny5Uy81smpE (0:40-0:60 should be enough). The video shows some nice transformations (skew) when you move the window. What I want to do is the same thing: to skew a div when I move it.
Currently I just have a plain simple div:
<div id="a" style="background: #0f0; position: absolute; left: 0px; top: 0px;"></div>
I have done a simple skew transformation using the CSS3's transform property, but my implementation is buggy. Are there good tutorials or maths sites or resources that describe the logic behind this? I know JavaScript and CSS well enough to implement, if I just knew the logic and maths. I tried reading FreeWins source code, but I am not good in C.
I am accepting any resourceful answers or pseudo code. My dragging system is part of a bigger system, thus, now that I post some real code, it does not work without giving you the entire system (that I can not do at this point). So, you can't run this code as is. The code I use is this (slightly modified though) to demonstrate my idea:
/**
* The draggable object.
*/
Draggable = function(targetElement, options) {
this.targetElement = targetElement;
// Initialize drag data.
this.dragData = {
startX: null,
startY: null,
lastX: null,
lastY: null,
offsetX: null,
offsetY: null,
lastTime: null,
occuring: false
};
// Set the cursor style.
targetElement.style.cursor = 'move';
// The element to move.
this.applyTo = options.applyTo || targetElement;
// Event methods for "mouse down", "up" and "move".
// Mouse up and move are binded to window.
// We can attach and deattach "move" and "up" events as needed.
var me = this;
targetElement.addEventListener('mousedown', function(event) {
me.onMouseDown.call(me, event);
}, false);
this.mouseUp = function(event) {
me.onMouseUp.call(me, event);
};
this.mouseMove = function(event) {
me.onMouseMove.call(me, event);
};
};
/**
* The mouse down event.
* #param {Object} event
*/
Draggable.prototype.onMouseDown = function(event) {
// New drag event.
if (this.dragData.occuring === false) {
this.dragData.occuring = true;
this.dragData.startX = this.dragData.lastX = event.clientX;
this.dragData.startY = this.dragData.lastY = event.clientY;
this.dragData.offsetX = parseInt(this.applyTo.style.left, 10) - event.clientX;
this.dragData.offsetY = parseInt(this.applyTo.style.top, 10) - event.clientY;
this.dragData.lastTime = (new Date()).getTime();
// Mouse up and move events.
var me = this;
window.addEventListener('mousemove', this.mouseMove, false);
window.addEventListener('mouseup', this.mouseUp, false);
}
};
/**
* The mouse movement event.
* #param {Object} event
*/
Draggable.prototype.onMouseMove = function(event) {
if (this.dragData.occuring === true) {
// He is dragging me now, we move if there is need for that.
var moved = (this.dragData.lastX !== event.clientX || this.dragData.lastY !== event.clientY);
if (moved === true) {
var element = this.applyTo;
// The skew animation. :)
var skew = (this.dragData.lastX - event.clientX) * 1;
var limit = 25;
if (Math.abs(skew) > limit) {
skew = limit * (skew > 0 ? 1 : -1);
}
var transform = 'translateX(' + (event.clientX + this.dragData.offsetX - parseInt(element.style.left, 10)) + 'px)';
transform += 'translateY(' + (event.clientY + this.dragData.offsetY - parseInt(element.style.top, 10)) + 'px)';
transform += 'skew(' + skew + 'deg)';
element.style.MozTransform = transform;
element.style.webkitTransform = transform;
this.dragData.lastX = event.clientX;
this.dragData.lastY = event.clientY;
this.dragData.lastTime = (new Date()).getTime();
}
}
};
/**
* The mouse up event.
* #param {Object} event
*/
Draggable.prototype.onMouseUp = function(event) {
this.dragData.occuring = false;
var element = this.applyTo;
// Reset transformations.
element.style.MozTransform = '';
element.style.webkitTransform = '';
// Save the new position.
element.style.left = (this.dragData.lastX + this.dragData.offsetX) + 'px';
element.style.top = (this.dragData.lastY + this.dragData.offsetY) + 'px';
// Remove useless events.
window.removeEventListener('mousemove', this.mouseMove, false);
window.removeEventListener('mousemove', this.mouseUp, false);
};
Currently my dragging system is buggy and simple. I need more information on the logic that I should be applying.
Wow, the idea rocks. :) I've cleaned your code a bit, and solved the problems with initialization. Now it works fine for me on Firefox and Chrome (even though you said it shouldn't).
A few notes:
you need to grab the starting top and left positions during initialization (getBoundingClientRect)
store references like this.dragData and element.style for shortness and faster execution
dragData can be initialized as an empty object. It's fine in javascript. You can add properties later.
options should be conditionally initialized as an empty object, so that you can take zero options
moved and dragData.occuring were totally useless because of the event management
preventDefault is needed in order not to select text during dragging
you may want to keep track of z-indexes to be the active element always visible
Have fun!
Code [See it in action]
/**
* The draggable object.
*/
Draggable = function(targetElement, options) {
this.targetElement = targetElement;
// we can take zero options
options = options || {};
// Initialize drag data.
// #props: startX, startY, lastX, lastY,
// offsetX, offsetY, lastTime, occuring
this.dragData = {};
// Set the cursor style.
targetElement.style.cursor = 'move';
// The element to move.
var el = this.applyTo = options.applyTo || targetElement;
// Event methods for "mouse down", "up" and "move".
// Mouse up and move are binded to window.
// We can attach and deattach "move" and "up" events as needed.
var me = this;
targetElement.addEventListener('mousedown', function(event) {
me.onMouseDown.call(me, event);
}, false);
this.mouseUp = function(event) {
me.onMouseUp.call(me, event);
};
this.mouseMove = function(event) {
me.onMouseMove.call(me, event);
};
// initialize position, so it will
// be smooth even on the first drag
var position = el.getBoundingClientRect();
el.style.left = position.left + "px";
el.style.top = position.top + "px";
el.style.position = "absolute";
if (el.style.zIndex > Draggable.zindex)
Draggable.zindex = el.style.zIndex + 1;
};
Draggable.zindex = 0;
/**
* Sets the skew and saves the position
* #param {Number} skew
*/
Draggable.prototype.setSkew = function(skew) {
var data = this.dragData;
var style = this.applyTo.style;
// Set skew transformations.
data.skew = skew;
style.MozTransform = skew ? 'skew(' + skew + 'deg)' : '';
style.webkitTransform = skew ? 'skew(' + skew + 'deg)' : '';
// Save the new position.
style.left = (data.lastX + data.offsetX) + 'px';
style.top = (data.lastY + data.offsetY) + 'px';
}
/**
* The mouse down event.
* #param {Object} event
*/
Draggable.prototype.onMouseDown = function(event) {
var data = this.dragData;
// New drag event.
var style = this.applyTo.style;
data.startX = data.lastX = event.clientX;
data.startY = data.lastY = event.clientY;
data.offsetX = parseInt(style.left, 10) - event.clientX;
data.offsetY = parseInt(style.top, 10) - event.clientY;
style.zIndex = Draggable.zindex++;
data.lastTime = (new Date()).getTime();
// Mouse up and move events.
window.addEventListener('mousemove', this.mouseMove, false);
window.addEventListener('mouseup', this.mouseUp, false);
event.preventDefault(); // prevent text selection
};
/**
* The mouse movement event.
* #param {Object} event
*/
Draggable.prototype.onMouseMove = function(event) {
// He is dragging me now
var me = this;
var data = me.dragData;
var element = me.applyTo;
var clientX = event.clientX;
var clientY = event.clientY;
data.moving = true;
// The skew animation. :)
var skew = (data.lastX - clientX) * 1;
var limit = 25;
if (Math.abs(skew) > limit) {
skew = limit * (skew > 0 ? 1 : -1);
}
var style = element.style;
var left = parseInt(style.left, 10);
var top = parseInt(style.top, 10);
var transform =
'translateX(' + (clientX + data.offsetX - left) + 'px)' +
'translateY(' + (clientY + data.offsetY - top) + 'px)' +
'skew(' + skew + 'deg)';
style.MozTransform = transform;
style.webkitTransform = transform;
data.lastX = clientX;
data.lastY = clientY;
data.lastTime = (new Date()).getTime();
// here is the cooldown part in order
// not to stay in disorted state
var pre = skew > 0 ? 1 : -1;
clearInterval(data.timer);
data.timer = setInterval(function() {
var skew = data.skew - (pre * 10);
skew = pre * skew < 0 ? 0 : skew;
me.setSkew(skew);
if (data.moving || skew === 0)
clearInterval(data.timer);
}, 20);
data.moving = false;
};
/**
* The mouse up event.
* #param {Object} event
*/
Draggable.prototype.onMouseUp = function(event) {
this.setSkew('');
// Remove useless events.
window.removeEventListener('mousemove', this.mouseMove, false);
window.removeEventListener('mousemove', this.mouseUp, false);
};

Categories

Resources