The javascript below allows me to drag a DIV onto a canvas. I am loading a multi-page PDF (actually image files) in a popup window and placing canvas layers over them and loading shapes in each canvas at certain coordinates.
The code below is mostly working except for when the div is "dropped" onto the canvas, it does not drop where the cursor is. The div drops up and to the right of wherever my cursor is on the canvas.
The page width (918) and height (1188) are static and never change.
I'm assuming this has something to do with the div's offset as it's relative to the page's offset but I cannot seem to get it right.
Any suggestions are appreciated.
$('.canvas-container').each(function (index, item) {
var canvasContainer = $(this)[index];
var canvasObject = $("canvas", this)[index];
var divOffsetX, divOffsetY;
var sigDivs = $(".sigFields").last();
divOffsetX = sigDivs.offset().left;
divOffsetY = sigDivs.offset().top;
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault();
}
e.dataTransfer.dropEffect = 'copy';
return false;
}
function handleDragEnter(e) {
this.classList.add('over');
}
function handleDragLeave(e) {
this.classList.remove('over');
}
function handleDrop(e) {
e = e || window.event;
if (e.preventDefault) {
e.preventDefault();
}
if (e.stopPropagation) {
e.stopPropagation();
}
var div = document.querySelector('.sigFields div.div_dragging');
console.log('event: ', e);
var offset = $(this).offset();
var y = e.clientY - (offset.top + divOffsetY);
var x = e.clientX - (offset.left + divOffsetX);
var newSigDiv = new fabric.Rect({
width: 234,
height: 24,
left: x,
top: y,
lockRotation: true,
hasRotatingPoint: false
});
fabricCanvas.add(newSigDiv);
return false;
}
function handleDragEnd(e) {
[].forEach.call(divs, function (div) {
div.classList.remove('div_dragging');
});
}
var divs = document.querySelectorAll('.sigFields div');
[].forEach.call(divs, function (div) {
div.addEventListener('dragstart', handleDragStart, false);
div.addEventListener('dragend', handleDragEnd, false);
});
canvasContainer.addEventListener('dragenter', handleDragEnter, false);
canvasContainer.addEventListener('dragover', handleDragOver, false);
canvasContainer.addEventListener('dragleave', handleDragLeave, false);
canvasContainer.addEventListener('drop', handleDrop, false);
});
The fix was simple.
Rather than attempt some weird math with the offsets, I changed
left: x,
left: y
to
left: e.layerX,
top: e.layerY
Related
I need to just drag/move around some images. Each image is linked to some other page. When I use drag functions I can drag the image but when I release it the link of the image fires up. I would need to drag around, release, and then click the image if I want to open the link. What can I do?
The JSFiddle: http://jsfiddle.net/grundum/wp0zhjbn/21/
I found How to HTML5-drag an element that has a link in it but is not clear and doesn't have a concrete answer. Any ideas?
Thanks in advance.
HTML
<div class="dragme" draggable="true">
<a draggable="false" href="https://placeholder.com/"><img class="dragme" draggable="false" src="https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/microformat.svg"></a>
</div>
CSS
.dragme {
position: relative;
width: 270px;
height: 203px;
cursor: move;
}
#draggable {
background-color: #ccc;
border: 1px solid #000;
}
JS
function startDrag(e) {
// determine event object
if (!e) {
var e = window.event;
}
// IE uses srcElement, others use target
var targ = e.target ? e.target : e.srcElement;
if (targ.className != 'dragme') {
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 = '0px'
};
if (!targ.style.top) {
targ.style.top = '0px'
};
// calculate integer values for top and left
// properties
coordX = parseInt(targ.style.left);
coordY = parseInt(targ.style.top);
drag = true;
// move div element
document.onmousemove = dragDiv;
}
function dragDiv(e) {
if (!drag) {
return
};
if (!e) {
var e = window.event
};
var targ = e.target ? e.target : e.srcElement;
// move div element
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;
}
When I use drag functions I can drag the image but when I release it
the link of the image fires up. I would need to drag around, release,
and then click the image if I want to open the link. What can I do?
Then how can you recognize if element is being dragged or clicked? Time interval between onmousedown, onmouseup calls? Mouse movement?
One thing for sure you have to get rid of anchor tag or call in its click event handler preventDefault() as its getting in the way.
Ugly example of how that might work.
let state = {
startDrag: false,
drag: false,
clicked: false,
offsetX: -1,
offsetY: -1,
target: null,
anchor: null
}
let href = "https://placeholder.com/";
window.addEventListener("load", (event) => {
state.anchor = document.querySelector("#anchor");
state.anchor.addEventListener('click', (event) => {
if(!state.clicked) {
event.preventDefault()
}
});
document.addEventListener('mousedown', startDrag);
document.addEventListener('mouseup', stopDrag);
})
function startDrag(event) {
const target = event.target ? event.target : event.srcElement;
state.target = target;
const { clientX, clientY } = event;
state.offsetX = clientX;
state.offsetY = clientY;
state.startDrag = true;
if (!target.style.left) {
target.style.left = '0px'
};
if (!target.style.top) {
target.style.top = '0px'
};
state.coordX = parseInt(target.style.left);
state.coordY = parseInt(target.style.top);
document.onmousemove = dragDiv;
}
function dragDiv({clientX, clientY}) {
if(!state.startDrag) {
return;
}
const { target, coordX, coordY, offsetX, offsetY } = state;
state.drag = state.startDrag;
target.style.left = coordX + clientX - offsetX + 'px';
target.style.top = coordY + clientY - offsetY + 'px';
return false;
}
function stopDrag() {
document.onmousemove = null;
if(state.startDrag && state.drag) {
// handle stop dragging;
}
else {
// handle click;
state.clicked = true;
state.anchor.click();
// or location.href = href;
}
state.clicked = false;
state.startDrag = false;
state.drag = false;
}
.dragme {
position: relative;
width: 270px;
height: 203px;
cursor: move;
}
#draggable {
background-color: #ccc;
border: 1px solid #000;
}
<div class="dragme" draggable="true">
<a id="anchor" draggable="false" href="https://placeholder.com/"><img class="dragme" draggable="false" src="https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/microformat.svg"></a>
</div>
When the content goes outside the div, we use scrollbars to see it. How can I scroll the div content by grabbing and dragging its background? I've searched the solution but did not find what I need. Here is my fiddle:
https://jsfiddle.net/vaxobasilidze/xhn49e1j/
Drag any item to the right div and move it outside the container to the right or bottom. scrollbars appear to help you to scroll. Here is an example of what I want to achieve. See the first diagram on the link and drag it:
https://jsplumbtoolkit.com/
Any tips on how to do this?
You should just need to detect when the mouse is down and then when the mouse is moving afterwards you can store the previous mouse coordinates and reference the current coordinates. Finally you can scroll the div in question by an amount based on the difference in drag since the last mousemove call.
var mouseDown = false;
var prevCoords = { x: 0, y: 0 };
$("#mainDiv").mousedown(function() {
mouseDown = true;
}).mousemove(function(e) {
var currentScrollX = $('#mainDiv').scrollLeft();
var currentScrollY = $('#mainDiv').scrollTop();
if(mouseDown) {
$('#mainDiv').scrollLeft(currentScrollX + prevCoords.x - (e.clientX + currentScrollX))
$('#mainDiv').scrollTop(currentScrollY + prevCoords.y - e.clientY)
};
prevCoords.x = e.clientX + currentScrollX;
prevCoords.y = e.clientY;
}).mouseup(function() {
mouseDown = false;
});
https://jsfiddle.net/6rx30muh/
EDIT: Fixed bug with wiggling tables when dragging:
var mouseDown = false;
var prevCoords = { x: 0, y: 0 };
$("#mainDiv").mousedown(function() {
mouseDown = true;
}).mousemove(function(e) {
var currentScrollX = $('#mainDiv').scrollLeft();
var currentScrollY = $('#mainDiv').scrollTop();
if(mouseDown) {
$('#mainDiv').scrollLeft(currentScrollX + prevCoords.x - e.clientX)
$('#mainDiv').scrollTop(currentScrollY + prevCoords.y - e.clientY)
};
prevCoords.x = e.clientX;
prevCoords.y = e.clientY;
}).mouseup(function() {
mouseDown = false;
});
Check for mousemove between mousedown and mouseup on the body element is a good place to start.
element = $('body');
element.addEventListener("mousedown", function(){
flag = 0;
}, false);
element.addEventListener("mousemove", function(){
flag = 1;
}, false);
element.addEventListener("mouseup", function(){
if(flag === 0){
console.log("click");
}
else if(flag === 1){
console.log("drag");
}
}, false);
I'm trying to move a div with the movement of the mouse cursor, but can't understand how to get the newly updated mouse position within my timeout. Maybe there is a simpler way.
var t;
$(document).ready(function(){
$("body").on("mousedown", ".heading", function (e) {
$("body").data("header_click", true);
if ($("body").data("header_click")) {
var container = $("#dialog");
container.css("position", "absolute");
t = setInterval(function(){
//some way to get mouse position
var pos = container.position();
container.css({
top: "",//set based on mouse position
left: "",//set based on mouse position
});
}, 100);
}else{
document.clearInterval(t);
}
});
});
$("body").on("mousedown", ".heading", function (e) {
$("body").data("header_click", false);
});
The solution found here did not work for me.
You will need to bind to the mouse move event and update a document variable.
var currentMousePos = { x: -1, y: -1 };
$(document).on('mousemove', function(event) {
currentMousePos.x = event.pageX;
currentMousePos.y = event.pageY;
});
Then use those positions relative to the absolute positions of the element you are wanting to drag to calculate and update the elements new position.
$(document).ready(function(){
$("body").on("mousedown", ".heading", function (e) {
$("body").data("header_click", true);
if ($("body").data("header_click")) {
var container = $("#dialog");
container.css("position", "absolute");
var containerPos = container.pos();
var mouseTopOffset = containerPos.top - currentMousePos.y;
var mouseLeftOffset = containerPos.left - currentMousePos.x;
container.css("left", mouseTopOffset +"px");
container.css("top", mouseLeftOffset +"px");
}
}
}
I havent really tested this but in theory should do what you need.
http://jsfiddle.net/rodrigopandini/gj7HT/
I'm using this example to drag & drop images from my computer to canvas using Fabric.js.
//Drag and Drop Image
var canvas = new fabric.Canvas('c');
function handleDragStart(e) {
[].forEach.call(images, function (img) {
img.classList.remove('img_dragging');
});
this.classList.add('img_dragging');
}
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault(); // Necessary. Allows us to drop.
}
e.dataTransfer.dropEffect = 'copy'; // See the section on the DataTransfer object.
// NOTE: comment above refers to the article (see top) -natchiketa
return false;
}
function handleDragEnter(e) {
// this / e.target is the current hover target.
this.classList.add('over');
}
function handleDragLeave(e) {
this.classList.remove('over'); // this / e.target is previous target element.
}
function handleDrop(e) {
// this / e.target is current target element.
/*
if (e.stopPropagation) {
e.stopPropagation(); // stops the browser from redirecting.
}
*/
e.stopPropagation(); // Stops some browsers from redirecting.
e.preventDefault(); // Stops some browsers from redirecting.
// handle desktop images
if (e.dataTransfer.files.length > 0) {
var files = e.dataTransfer.files;
for (var i = 0, f; f = files[i]; i++) {
// Only process image files.
if (f.type.match('image.*')) {
// Read the File objects in this FileList.
var reader = new FileReader();
// listener for the onload event
reader.onload = function (evt) {
// create img element
var img = document.createElement('img');
img.src = evt.target.result;
// put image on canvas
var newImage = new fabric.Image(img, {
width: img.width,
height: img.height,
// Set the center of the new object based on the event coordinates relative to the canvas container.
left: e.layerX,
top: e.layerY
});
canvas.add(newImage);
};
// Read in the image file as a data URL.
reader.readAsDataURL(f);
}
}
}
// handle browser images
else {
var img = document.querySelector('#images img.img_dragging');
var newImage = new fabric.Image(img, {
width: img.width,
height: img.height,
// Set the center of the new object based on the event coordinates relative to the canvas container.
left: e.layerX,
top: e.layerY
});
canvas.add(newImage);
}
return false;
}
function handleDragEnd(e) {
// this/e.target is the source node.
[].forEach.call(images, function (img) {
img.classList.remove('img_dragging');
});
}
if (Modernizr.draganddrop) {
// Browser supports HTML5 DnD.
// Bind the event listeners for the image elements
var images = document.querySelectorAll('#images img');
[].forEach.call(images, function (img) {
img.addEventListener('dragstart', handleDragStart, false);
img.addEventListener('dragend', handleDragEnd, false);
});
// Bind the event listeners for the canvas
var canvasContainer = document.getElementById('canvas-container');
canvasContainer.addEventListener('dragenter', handleDragEnter, false);
canvasContainer.addEventListener('dragover', handleDragOver, false);
canvasContainer.addEventListener('dragleave', handleDragLeave, false);
canvasContainer.addEventListener('drop', handleDrop, false);
} else {
// Replace with a fallback to a library solution.
alert("This browser doesn't support the HTML5 Drag and Drop API.");
}
//End Drag and Drop
For adding multiple images by drag and drop I used the above code (jsfiddle example).
Here's my code which is working fine when I add images one by one on Canvas.
var $ = function (id) { return document.getElementById(id) };
//Upload Click
var fileSelect = document.getElementById("fileSelect"),
upload = document.getElementById("upload");
fileSelect.addEventListener("click", function (e) {
if (upload) {
upload.click();
}
e.preventDefault(); // prevent navigation to "#"
}, false);
//End
function applyFilter(index, filter) {
var obj = canvas.getActiveObject();
obj.filters[index] = filter;
obj.applyFilters(canvas.renderAll.bind(canvas));
}
function applyFilterValue(index, prop, value) {
var obj = canvas.getActiveObject();
if (obj.filters[index]) {
obj.filters[index][prop] = value;
obj.applyFilters(canvas.renderAll.bind(canvas));
}
}
var canvas = new fabric.Canvas('c'),
f = fabric.Image.filters;
//Canvas Events
canvas.on({
'object:selected': function () {
fabric.util.toArray(document.getElementsByTagName('input'))
.forEach(function (el) {
el.disabled = false;
})
//Check for already applied filter on Image after selecting an image
var filters = ['grayscale', 'invert', 'remove-white', 'sepia', 'sepia2',
'brightness', 'noise', 'gradient-transparency', 'pixelate',
'blur', 'sharpen'];
for (var i = 0; i < filters.length; i++) {
$(filters[i]).checked = !!canvas.getActiveObject().filters[i];
}
canvas._activeObject.bringToFront();
},
//'selection:cleared': function() {
// fabric.util.toArray(document.getElementsByTagName('input'))
// .forEach(function(el) { el.disabled = true; })
//}
});
var imageLoader = document.getElementById('upload');
imageLoader.addEventListener('change', handleImage, false);
var c = document.getElementById('c');
var context = canvas.getContext('2d');
//Canvas Handler
function handleImage(e) {
var reader = new FileReader();
reader.onload = function (event) {
var img = new Image();
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
context.drawImage(img);
}
img.src = event.target.result;
}
reader.readAsDataURL(e.target.files[0]);
}
//Fabric.JS single image handler
document.getElementById('upload').onchange = function handleImage(e) {
var reader = new FileReader();
reader.onload = function(event) {
var imgObj = new Image();
imgObj.src = event.target.result;
imgObj.onload = function() {
var image = new fabric.Image(imgObj);
image.set({
left: 250,
top: 250,
angle: 20,
padding: 10,
cornersize: 10,
}).scale(0.4);
canvas.add(image);
}
}
reader.readAsDataURL(e.target.files[0]);
}
//End
//Static Image in background
function setBackgrnd() {
fabric.Image.fromURL('/2-road.jpg', function (img) {
var oImg = img.set({ left: 400, top: 300, opactiy:0.5, selectable:false, lockHorizontally: false, lockVertically: false, lockScaling: false, lockRotation: false }).scale(1);
//oImg.filters.push(new fabric.Image.filters.blur());
//oImg.applyFilters(canvas.renderAll.bind(canvas));
//canvas.setBackgroundImage('/2-road.jpg', canvas.renderAll.bind(canvas));
canvas.add(oImg).renderAll();
canvas.setActiveObject(oImg);
canvas.sendToBack(oImg);
$('#delBackgrnd').click(function() {
canvas.getObjects();
remove(oImg);
canvas.renderAll();
});
//function setBackgrnd() {
// canvas.add(oImg).renderAll();
// canvas.setActiveObject(oImg);
//}
//function delBackgrnd() {
// fxRemove(oImg, canvas.renderAll());
//}
});
}
//Clears Everything on Canvas
function clearCanvas() {
canvas.clear();
}
//Delete Background Image
function delBackgrnd() {
//SelectedObject.onclick = function () {
// if (canvas.getActiveGroup()) {
// canvas.getActiveGroup().forEachObject(function (o) { canvas.remove(o) });
// canvas.discardActiveGroup().renderAll();
// } else {
// canvas.remove(canvas.getActiveObject());
// }
//};
}
//Generate Collage
function convertCanvasToImage() {
//var canvas = document.getElementById('c');
canvas.deactivateAll().renderAll();
var image_src = canvas.toDataURL("image/jpeg");
//document.write('<img src="' + image_src + '"/>');
window.open(image_src);
}
//Drag and Drop Image
var canvas = new fabric.Canvas('c');
function handleDragStart(e) {
[].forEach.call(images, function (img) {
img.classList.remove('img_dragging');
});
this.classList.add('img_dragging');
}
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault(); // Necessary. Allows us to drop.
}
e.dataTransfer.dropEffect = 'copy'; // See the section on the DataTransfer object.
// NOTE: comment above refers to the article (see top) -natchiketa
return false;
}
function handleDragEnter(e) {
// this / e.target is the current hover target.
this.classList.add('over');
}
function handleDragLeave(e) {
this.classList.remove('over'); // this / e.target is previous target element.
}
function handleDrop(e) {
// this / e.target is current target element.
/*
if (e.stopPropagation) {
e.stopPropagation(); // stops the browser from redirecting.
}
*/
e.stopPropagation(); // Stops some browsers from redirecting.
e.preventDefault(); // Stops some browsers from redirecting.
// handle desktop images
if (e.dataTransfer.files.length > 0) {
var files = e.dataTransfer.files;
for (var i = 0, f; f = files[i]; i++) {
// Only process image files.
if (f.type.match('image.*')) {
// Read the File objects in this FileList.
var reader = new FileReader();
// listener for the onload event
reader.onload = function (evt) {
// create img element
var img = document.createElement('img');
img.src = evt.target.result;
// put image on canvas
var newImage = new fabric.Image(img, {
width: img.width,
height: img.height,
// Set the center of the new object based on the event coordinates relative to the canvas container.
left: e.layerX,
top: e.layerY
});
canvas.add(newImage);
};
// Read in the image file as a data URL.
reader.readAsDataURL(f);
}
}
}
// handle browser images
else {
var img = document.querySelector('#images img.img_dragging');
var newImage = new fabric.Image(img, {
width: img.width,
height: img.height,
// Set the center of the new object based on the event coordinates relative to the canvas container.
left: e.layerX,
top: e.layerY
});
canvas.add(newImage);
}
return false;
}
function handleDragEnd(e) {
// this/e.target is the source node.
[].forEach.call(images, function (img) {
img.classList.remove('img_dragging');
});
}
if (Modernizr.draganddrop) {
// Browser supports HTML5 DnD.
// Bind the event listeners for the image elements
var images = document.querySelectorAll('#images img');
[].forEach.call(images, function (img) {
img.addEventListener('dragstart', handleDragStart, false);
img.addEventListener('dragend', handleDragEnd, false);
});
// Bind the event listeners for the canvas
var canvasContainer = document.getElementById('canvas-container');
canvasContainer.addEventListener('dragenter', handleDragEnter, false);
canvasContainer.addEventListener('dragover', handleDragOver, false);
canvasContainer.addEventListener('dragleave', handleDragLeave, false);
canvasContainer.addEventListener('drop', handleDrop, false);
} else {
// Replace with a fallback to a library solution.
alert("This browser doesn't support the HTML5 Drag and Drop API.");
}
//End Drag and Drop
//End of Code
My original HTML canvas tag is
<div id="canvas-container" class="canvas-container" >
<canvas id="c" width="800" height="600" style="left: -300px; border: 1px dotted;"></canvas>
</div>
But, when I integrated this code the images are adding to the canvas but as I click on Canvas they disappears. I inspected element in Chrome and found that there are 2 Canvas tags.
<div id="canvas-container" class="canvas-container">
<div class="canvas-container" style="width: 800px; height: 600px; position: relative; -webkit-user-select: none;">
<div class="canvas-container" style="width: 800px; height: 600px; position: relative; -webkit-user-select: none;">
<canvas id="c" width="800" height="600" style="left: 0px; border: 1px dotted; position: absolute; width: 800px; height: 600px; top: 0px; -webkit-user-select: none;" class="lower-canvas"></canvas>
<canvas class="upper-canvas" width="800" height="600" style="position: absolute; width: 800px; height: 600px; left: 0px; top: 0px; -webkit-user-select: none;"></canvas>
</div>
<canvas class="upper-canvas" width="800" height="600" style="position: absolute; width: 800px; height: 600px; left: 0px; top: 0px; -webkit-user-select: none;"></canvas>
</div>
</div>
When I changed the position of all elements in above code to position: relative;, it works.
The 3 extra Canvas tags are generated automatically. How can I change the position? Also, when I generate the png or jpg image of canvas using DataURL, firefox stops responding.
You need to override the css of that library, by using the following code.
/deep/ .canvas-container{
.upper-canvas {
position: relative !important;
}
}
Using a plain JS script from David Flanagan to make my bookmarklet draggable.
I have noticed that I can move the pointer off the drag bar during key-down and the pop-up may or may not follow the pointer around or suddenly snap to the pointer.
The total experience in Firefox 8 and 9 is not impressive. IE8 on XP works as designed
It is intended for a bookmarklet, so I canot use a framework like jQuery or YUI.
QUESTION: How do I improve the stickyness of the mousedown / drag so the window stays attached to the mouse onmousedown and onmousemove using plain JS?
Also please help me make the getLeft and getTop work in IE/Chrome and Fx so the popup is restricted to the viewport.
OLD DEMO HERE
NEW AND FIXED DEMO HERE (thanks techfoobar)
function getTop(top) {
// if (console) console.log('y:'+top+':'+document.body.clientHeight);
if (top<0) return 0;
if (top>=(document.body.clientHeight-40)) return document.body.clientHeight-40;
return top;
}
function getLeft(left) {
// if (console) console.log('x:'+left+':'+document.body.clientWidth);
if (left<0) return 0;
if (left>=(document.body.clientWidth-500)) return document.body.clientWidth-500;
return left;
}
// This code is from the book JavaScript: The Definitive Guide, 6th Edition (ISBN #978-0596805524). Copyright 2011 by David Flanagan.
function getScrollOffsets(w) {
w = w || window;
if (w.pageXOffset != null) return {x: w.pageXOffset, y:w.pageYOffset};
var d = w.document;
if (document.compatMode == "CSS1Compat")
return {x:d.documentElement.scrollLeft, y:d.documentElement.scrollTop};
return { x: d.body.scrollLeft, y: d.body.scrollTop };
}
function zDrag(elementToDrag, event) { var scroll = getScrollOffsets();
var startX = event.clientX + scroll.x;
var startY = event.clientY + scroll.y;
var origX = elementToDrag.offsetLeft;
var origY = elementToDrag.offsetTop;
var deltaX = startX - origX;
var deltaY = startY - origY;
if (document.addEventListener) {
document.addEventListener("mousemove", moveHandler, true);
document.addEventListener("mouseup", upHandler, true);
}
else if (document.attachEvent) {
elementToDrag.setCapture();
elementToDrag.attachEvent("onmousemove", moveHandler);
elementToDrag.attachEvent("onmouseup", upHandler);
elementToDrag.attachEvent("onlosecapture", upHandler);
}
if (event.stopPropagation) event.stopPropagation();
else event.cancelBubble = true;
if (event.preventDefault) event.preventDefault();
else event.returnValue = false;
function moveHandler(e) {
if (!e) e = window.event;
var scroll = getScrollOffsets();
elementToDrag.style.left = getLeft(e.clientX + scroll.x - deltaX,true) + "px";
elementToDrag.style.top = getTop(e.clientY + scroll.y - deltaY,true) + "px";
if (e.stopPropagation) e.stopPropagation();
else e.cancelBubble = true;
};
function upHandler(e) {
if (!e) e = window.event;
if (document.removeEventListener) {
document.removeEventListener("mouseup", upHandler, true);
document.removeEventListener("mousemove", moveHandler, true);
}
else if (document.detachEvent) {
elementToDrag.detachEvent("onlosecapture", upHandler);
elementToDrag.detachEvent("onmouseup", upHandler);
elementToDrag.detachEvent("onmousemove", moveHandler);
elementToDrag.releaseCapture();
}
if (e.stopPropagation) e.stopPropagation();
else e.cancelBubble = true;
};
}
// end drag code
This should take care of the stickiness issue.
I could see from your demo site that when the mouse is over the inner iframe, we have an issue with stickiness. This is because the inner iframe does not bubble the event up to the root document element which handles zDrag's mousemove event.
I solved that issue by appending an overlay div (invisible but there) that takes the entire area of the root document, thereby effectively preventing the inner iframe from getting the mousemove. And as this overlay div is a direct descendant of our root document element, it bubbles up the mousemove event correctly.
Changes include an additional method for getting the document height (from http://james.padolsey.com/javascript/get-document-height-cross-browser/), and changes to the zDrag method for adding/showing/hiding the overlay div.
function getDocHeight() {
var D = document;
return Math.max(
Math.max(D.body.scrollHeight, D.documentElement.scrollHeight),
Math.max(D.body.offsetHeight, D.documentElement.offsetHeight),
Math.max(D.body.clientHeight, D.documentElement.clientHeight)
);
}
function zDrag(elementToDrag, event) {
var scroll = getScrollOffsets();
// create/show overlay - over the inner iframe and everything else
var div = document.getElementById('overlay');
if(div==null) {
div = document.createElement('div');
div.id = 'overlay';
div.style.position = 'absolute';
div.style.left = '0px';
div.style.top = '0px';
div.style.width = '100%';
div.style.height = getDocHeight()+'px';
div.style.zIndex = 99999;
div.style.cursor = 'move';
var bodyTag = document.getElementsByTagName("body")[0];
bodyTag.appendChild(div);
}
else {
div.style.display = 'block';
}
var startX = event.clientX + scroll.x;
var startY = event.clientY + scroll.y;
var origX = elementToDrag.offsetLeft;
var origY = elementToDrag.offsetTop;
var deltaX = startX - origX;
var deltaY = startY - origY;
if (document.addEventListener) {
document.addEventListener("mousemove", moveHandler, true);
document.addEventListener("mouseup", upHandler, true);
}
else if (document.attachEvent) {
/*elementToDrag.setCapture();
elementToDrag.attachEvent("onmousemove", moveHandler);
elementToDrag.attachEvent("onmouseup", upHandler);
elementToDrag.attachEvent("onlosecapture", upHandler);*/
// attach the events to the document element, to ensure we dont 'miss' any move events.
document.setCapture();
document.attachEvent("onmousemove", moveHandler);
document.attachEvent("onmouseup", upHandler);
document.attachEvent("onlosecapture", upHandler);
}
if (event.stopPropagation) event.stopPropagation();
else event.cancelBubble = true;
if (event.preventDefault) event.preventDefault();
else event.returnValue = false;
function moveHandler(e) {
if (!e) e = window.event;
var scroll = getScrollOffsets();
elementToDrag.style.left = getLeft(e.clientX + scroll.x - deltaX,true) + "px";
elementToDrag.style.top = getTop(e.clientY + scroll.y - deltaY,true) + "px";
if (e.stopPropagation) e.stopPropagation();
else e.cancelBubble = true;
};
function upHandler(e) {
// dragging is over. hide the overlay.
document.getElementById('overlay').style.display = 'none';
if (!e) e = window.event;
if (document.removeEventListener) {
document.removeEventListener("mouseup", upHandler, true);
document.removeEventListener("mousemove", moveHandler, true);
}
else if (document.detachEvent) {
/*elementToDrag.detachEvent("onlosecapture", upHandler);
elementToDrag.detachEvent("onmouseup", upHandler);
elementToDrag.detachEvent("onmousemove", moveHandler);
elementToDrag.releaseCapture();*/
document.detachEvent("onlosecapture", upHandler);
document.detachEvent("onmouseup", upHandler);
document.detachEvent("onmousemove", moveHandler);
document.releaseCapture();
}
if (e.stopPropagation) e.stopPropagation();
else e.cancelBubble = true;
};
}
EDIT - For limiting the drag inside the viewport
Now it will limit the dragging to inside the viewport (i.e browser window inner width and height). Changes include a) an additional cross-browser function to get the window inner width and height (from http://www.javascripter.net/faq/browserw.htm) and b) changes to the moveHandler() method (inside the zDrag method) to check and enforce limits.
function getWindowSize() {
var winW = 630, winH = 460;
if (document.body && document.body.offsetWidth) {
winW = document.body.offsetWidth;
winH = document.body.offsetHeight;
}
if (document.compatMode=='CSS1Compat' && document.documentElement && document.documentElement.offsetWidth ) {
winW = document.documentElement.offsetWidth;
winH = document.documentElement.offsetHeight;
}
if (window.innerWidth && window.innerHeight) {
winW = window.innerWidth;
winH = window.innerHeight;
}
return {width: winW, height: winH};
}
And inside zDrag(), replace the current moveHandler with:
function moveHandler(e) {
if (!e) e = window.event;
var scroll = getScrollOffsets();
var newLeft = getLeft(e.clientX + scroll.x - deltaX,true);
if(newLeft + elementToDrag.offsetWidth > winDim.width) {
newLeft = winDim.width - elementToDrag.offsetWidth;
}
elementToDrag.style.left = newLeft + "px";
var newTop = getTop(e.clientY + scroll.y - deltaY,true);
if(newTop + elementToDrag.offsetHeight > winDim.height) {
newTop = winDim.height - elementToDrag.offsetHeight;
}
elementToDrag.style.top = newTop + "px";
if (e.stopPropagation) e.stopPropagation();
else e.cancelBubble = true;
};
EDIT - the winDim variable
winDim is the variable that stores the viewport's dimensions. It is used in the move handler to check if our movable is within the viewport. I kept it outside so as to avoid re-computation of window dimensions on every move event which could degrade performance.
var winDim = null;
function zDrag(...) {
if(winDim == null) winDim = getWindowSize
// ... rest of the code in zDrag ...
}
The main problem is the iframe. When the mouse pointer enters that you say goodbye to all your registered events. Replace the iframe with a div and things should improve in Firefox. IE simply seems to update the display faster; the mouse pointer never gets the chance to enter the iframe in IE.