Custom made Drag and Drop canvas in HTML5 - javascript

I made an little test to check out HTML 5.
In my test I have a <canvas> and want to drag this one.
But I have a little problem, my canvas is kinda shaking.
Could someone help me plz?
Fiddle:
http://jsfiddle.net/wnxFK/

Calculating layerX and layerY once at the start of the drag helps create a smoother effect:
http://jsfiddle.net/jB5YN/
So at the start you have:
var mousedown = false;
var layerX = 0;
var layerY = 0;
function onMouseDown(event)
{
var canvas = document.getElementById("myCanvas");
mousedown = true;
layerX = event.layerX;
layerY = event.layerY;
// Code
drag(event);
}
And when you come to drag you have:
function drag(event)
{
var clientX = ('clientX' in event) ? (event.clientX) : (event.offsetX);
var clientY = ('clientY' in event) ? (event.clientY) : (event.offsetY);
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext('2d');
var x = (event.clientX- layerX);
var y = (event.clientY- layerY);
var log = document.getElementById("log");
log.innerHTML = "x: " + x + " y: " + y;
canvas.style.left = x + 'px';
canvas.style.top = y + 'px';
}
Also, I've moved the events to hook up only once at the start, just feels a bit neater to me!
Update
Just for a complete answer, if you want to avoid layerX and layerY which seem to be in the process of being deprecated in webkit, you can calculate them yourself:
http://jsfiddle.net/B3TYe/
The change being:
var offsetX= 0;
var offsetY= 0;
function onMouseDown(event)
{
var canvas = document.getElementById("myCanvas");
mousedown = true;
var left = (canvas.style.left=="")? 0:parseInt(canvas.style.left);
var top = (canvas.style.top=="")? 0:parseInt(canvas.style.top);
offsetX = event.pageX - left;
offsetY = event.pageY - top;
// Code
drag(event);
}
Andy

I wouldn't use clientX and layerX properties. Here's an example:
http://jsfiddle.net/wnxFK/3/
function drag(event)
{
var canvas = document.getElementById("myCanvas");
canvas.style.left = event.pageX - 50 + 'px';
canvas.style.top = event.pageY - 50 + 'px';
}​

Related

How I can get the X and Y from some move event on canvas?

I am developing an app with Cordova + Onsen Ui 2 + javascript, but I am getting problems to get the coordinates X and Y from javascript move events. I tried mousemove (it didn't fire) and drag (but I got undefined when I tried to get pageX or clientX from event object). I didn't find any example about drawing with canvas yet. Thanks you all in advance!
Javascript:
var canvasListener = function(){
canvas = document.getElementById("canvas");
canvas.addEventListener('mousedown', function(event){
var coordinates = painting(event);
});
canvas.addEventListener('drag', function(event){
var coordinates = painting(event);
});
canvas.addEventListener('mouseup', function(event){
var coordinates = painting(event);
});
}
function painting(event){
var x = event.clientX;
var y = event.clientY;
var touchX = x - signatureCanvas.offsetLeft;
var touchY = y - signatureCanvas.offsetTop;
var localCoordinates;
if(event.type == 'mouseup'){
localCoordinates = {
x: 0,
y: 0
};
}else{
localCoordinates = {
x: touchX,
y: touchY
};
}
return localCoordinates;
}
Html:
<canvas id="canvas"></canvas>
Try this code. On moving mouse over the canvas the current mouse(x,y) co-ordinates will be printed in screen. Hope this logic only you are looking for.
JsFiddle
HTML
<canvas width="300" height="225"></canvas>
<span class="first">Move the mouse over the div.</span>
<span></span>
JS
var canvas = $('canvas');
canvas.mousemove(function(e){
var pageCrds = '('+ e.pageX +', '+ e.pageY +')',
clientCrds = '('+ e.clientX +', '+ e.clientY +')';
$('span:first').text('(e.pageX, e.pageY) - '+ pageCrds);
$('span:last').text('(e.clientX, e.clientY) - '+ clientCrds);
});
Generally you'll want to use event.clientX, event.clientY, and the results of canvas.getBoundingClientRect();. Here's an example, using the MouseMove event:
can.addEventListener('mousemove', function(e) {
var canwidth = can.width;
var canheight = can.height;
var bbox = can.getBoundingClientRect();
var mx = e.clientX - bbox.left * (canwidth / bbox.width);
var my = e.clientY - bbox.top * (canheight / bbox.height);
// output it to see results:
ctx.fillRect(mx, my, 1, 1);
})
This works with all mouse/touch events on all platforms, and accommodates a lot of difficult CSS situations.
Live example: http://codepen.io/simonsarris/pen/NNVQpj?editors=1010
Try this..
var damagesCanvas = document.getElementById('damagesCanvas');
damagesCanvas.addEventListener('click', function (evt) {
var pos = getMousePos(damagesCanvas, evt);
}, false);
function getMousePos(damagesCanvas, evt) {
var rect = damagesCanvas.getBoundingClientRect();
return {
X: evt.clientX - rect.left,
Y: evt.clientY - rect.top
};
}

select specific area of a div

I made a carousel using 2 divs named "left" and "right" putting mousemove events on them. I wanted to make it go up and down as well so I created a "top" and "bottom" and noticed that I couldn't make them combine to go the way the cursor goes.
I thus thought of targeting a specific area in the container (i.e top half of my container div) instead of creating divs inside triggering a specific direction, this way (I think) I can trigger all these event altogether. However after now hours of research I couldn't find a way to do so.
How should I proceed ? here is the code : http://jsfiddle.net/pool4/vL5g3/3/
var x=0,
y=0,
rateX=0,
rateY=0,
maxspeed=10;
var backdrop = $('.backdrop');
$('.directionx', backdrop).mousemove(function(e){
var $this = $(this);
var left = $this.is('.left');
var right = $this.is('.right');
if (left){
var w = $this.width();
rateX = (w - e.pageX - $this.offset().left + 1)/w;
}
else if (right){
var w = $this.width();
rateX = -(e.pageX - $this.offset().left + 1)/w;
}
});
$('.directiony', backdrop).mousemove(function(e){
var $this = $(this);
var top = $this.is('.top');
var bottom = $this.is('.bottom');
if (top){
var h = $this.height();
rateY = (h - e.pageY - $this.offset().top + 1)/h;
}
else if (bottom) {
var h = $this.height();
rateY = -(e.pageY - $this.offset().top + 1)/h;
}
});
backdrop.hover(
function(){
var scroller = setInterval( moveBackdrop, 30 );
$(this).data('scroller', scroller);
},
function(){
var scroller = $(this).data('scroller');
clearInterval( scroller );
}
);
function moveBackdrop(){
x += maxspeed * rateX;
y += maxspeed * rateY;
var newpos = x+'px '+y+'px';
backdrop.css('background-position',newpos);
}
Your problem is that the divs that control movement up and down are placed over the ones that control left and right, so the latter do not receive the mousemove event ever. Mouse events do not propagate through layers, even if they're transparent. I changed your code and CSS, so each div is in one of the corners. To make things easier, I've used data-* attributes so the direction controlled by each div is set in a declarative way, without the need to change the code. You'll see that the code is much simpler (and it could be simplified even more).
By the way, you could achieve this witout extra divs, just controlling where the cursor is (to the top, right, left or bottom of the center of the div).
backdrop.on('mousemove', '.dir', function(e){
var $this = $(this);
var direction = $(e.target).attr('data-direction');
var left = direction.indexOf('left') > - 1;
var right = direction.indexOf('right') > - 1;
var top = direction.indexOf('up') > - 1;
var bottom = direction.indexOf('down') > - 1;
if (left){
var w = $this.width();
rateX = (w - e.pageX - $this.offset().left + 1)/w;
}
else if (right){
var w = $this.width();
rateX = -(e.pageX - $this.offset().left + 1)/w;
}
if (top){
var h = $this.height();
rateY = (h - e.pageY - $this.offset().top + 1)/h;
}
else if (bottom) {
var h = $this.height();
rateY = -(e.pageY - $this.offset().top + 1)/h;
}
});
I've updated your fiddle.
EDIT In this new fiddle I do it without extra divs:
var w = backdrop.width() / 2;
var h = backdrop.height() / 2;
var center = {
x: backdrop.offset().left + backdrop.width() / 2,
y: backdrop.offset().top + backdrop.height() / 2
};
backdrop.on('mousemove', function(e){
var offsetX = e.pageX - center.x;
var offsetY = e.pageY - center.y;
rateX = -offsetX / w;
rateY = -offsetY / h;
});
backdrop.hover(
function(){
var scroller = $(this).data('scroller');
if (!scroller) {
scroller = setInterval( moveBackdrop, 30 );
$(this).data('scroller', scroller);
}
},
function(){
var scroller = $(this).data('scroller');
if (scroller) {
clearInterval( scroller );
$(this).data('scroller', null);
}
}
);
As you see, the mousmove handler is considerably simpler.
To avoid issue of children losing event could use just the one.
First HTML from 4 child divs to just one
<div class="backdrop">
<div class="direction"></div>
</div>
<div id="pos"></div>
Next Inside the mousemove find your relative position
//Get Relative Position
var relX = e.pageX - $this.offset().left;
var relY = e.pageY - $this.offset().top;
Get Relative Position as a percentage of width and put 50% of it in negative for direction
var w = $this.width();
rateX = ((relX / w) - 0.5) * -1;
var h = $this.height();
rateY = ((relY / h) - 0.5) * -1;
Fiddle

Pan Svg While dragging elements

I want to achieve 'Panning' in svg while 'dragging' an element in particular direction.
Let say i select an element and start 'dragging' it upward untill it reached top of screen, now my svg should pan upwards automatically, without causing any problem with dragging. how i can achieve this.?
i have made a small mockup of this, where user can select and drag elements. it also contain two button, which cause svg to pan upward and downward. I am achiveing 'Panning' by changing 'ViewBox' of svg. ( i have to use this logic, i cannot use any other solution);
here is the fiddle link : http://jsfiddle.net/9J25r/
Complete Code:-
addEventListener('mousedown', mousedown, false);
var mx, my;
var dx, dy;
var mainsvg = document.getElementById('svg');
var selectedElement;
var eleTx, eleTy;
function getSvgCordinates(event) {
var m = mainsvg.getScreenCTM();
var p = mainsvg.createSVGPoint();
var x, y;
x = event.pageX;
y = event.pageY;
p.x = x;
p.y = y;
p = p.matrixTransform(m.inverse());
x = p.x;
y = p.y;
x = parseFloat(x.toFixed(3));
y = parseFloat(y.toFixed(3));
return {x: x, y: y};
}
function mousedown(event) {
if (event.target.id === 'arrow_t') {
panning('up');
}
else if (event.target.id === 'arrow_b') {
panning('down');
}
else if (event.target.id.split('_')[0] === 'rect') {
selectedElement = event.target;
var translatexy = selectedElement.getAttribute('transform');
translatexy = translatexy.split('(');
translatexy = translatexy[1].split(',');
eleTx = translatexy[0];
translatexy = translatexy[1].split(')');
eleTy = translatexy[0];
eleTx = parseFloat(eleTx);
eleTy = parseFloat(eleTy);
var xy = getSvgCordinates(event);
mx = xy.x;
my = xy.y;
mx = parseFloat(mx);
my = parseFloat(my);
addEventListener('mousemove', drag, false);
addEventListener('mouseup', mouseup, false);
}
}
function drag(event) {
var xy = getSvgCordinates(event);
dx = xy.x - mx;
dy = xy.y - my;
selectedElement.setAttribute('transform', 'translate(' + (eleTx + dx) + ',' + (eleTy + dy) + ')');
}
function mouseup(event) {
removeEventListener('mousemove', drag, false);
removeEventListener('mouseup', mouseup, false);
}
function panning(direction) {
var viewBox = svg.getAttribute('viewBox');
viewBox = viewBox.split(' ');
var y = parseFloat(viewBox[1]);
if (direction === 'up')
{
y+=5;
}
else if (direction === 'down')
{
y-=5;
}
viewBox=viewBox[0]+' '+y+' '+viewBox[2]+' '+viewBox[3];
svg.setAttribute('viewBox',viewBox);
}
here is the fiddle link : http://jsfiddle.net/9J25r/
EDIT:- (UPDATE)
I use the solution of Ian , it works well on the sample, but when i applied it to my original application, it did not work. check the below gif. You can see the 'gap' between mouse pointer and element. how i can remove that? .
This is one way, I've just done it with the Y/vertical for the moment...
You may want to adjust it, so that if the cursor is off the screen it adjusts the viewBox automatically as well, depends how you want it to drag (otherwise you will need to keep wiggling it to kick the drag func in).
var viewBox = svg.getAttribute('viewBox');
viewBoxSplit = viewBox.split(' ');
if( ely < viewBoxSplit[1] ) {
panning('down');
} else if( ely + +event.target.getAttribute('height')> +viewBoxSplit[1] + 300 ) {
panning('up');
}
jsfiddle here

How do I get the mouse position in FireFox?

This code works in Google Chrome and IE but not in FireFox.
function drawChildren(names) {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var ctxcolor = "#000000";
if(listeners < 1){
c.addEventListener("click", getPosition, false);
listeners = 1;
} // At the click function "getPosition" is executed
}
function getPosition(event) {
if ( event.offsetX == null ) { // Firefox
clickedX = event.layerX;
clickedY = event.layerY;
} else { // Other browsers
clickedX = event.offsetX;
clickedY = event.offsetY;
}
checkCoordinates(clickedX, clickedY);
}
To answer your question posted in the above comment, your function getPosition() will be called whenever myCanvas is clicked. (Because your code says c.addEventListener("click", getPosition, false);)
Using your mouse position detection code, I'm not sure how the offset and layering works either, but if you use the following code, then I know you will always get x and y coordinates starting from the edge of the browser, rather than starting in any elements of your site:
if(evt.pageX){
var xPos = evt.pageX;
var yPos = evt.pageY;
}else if(evt.clientX){
var xPos = evt.clientX + ((document.documentElement.scrollLeft) ? document.documentElement.scrollLeft : document.body.scrollLeft);
var yPos = evt.clientY + ((document.documentElement.scrollTop) ? document.documentElement.scrollTop : document.body.scrollTop);
}
Your code may do the same thing as this, but I just wanted to provide what works for me also :)

draggable without jQuery ui

How to make a element draggable without using jQuery UI?
I have this code:
<script type="text/javascript">
function show_coords(event)
{
var x=event.clientX;
var y=event.clientY;
var drag=document.getElementById('drag');
drag.style.left=x;
drag.style.top=y
}
</script>
<body style="height:100%;width:100%" onmousemove="show_coords(event)">
<p id="drag" style="position:absolute">drag me</p>
</body>
The problem is that I want to drag while the user the pressing the mouse button. I tried onmousedown but results were negative.
It will be quite easy as you get the concept.
function enableDragging(ele) {
var dragging = dragging || false, //Setup a bunch of variables
x, y, Ox, Oy,
enableDragging.z = enableDragging.z || 1,
current;
ele.onmousedown = function(ev) { //When mouse is down
current = ev.target;
dragging = true; //It is dragging time
x = ev.clientX; //Get mouse X and Y and store it
y = ev.clientY; // for later use.
Ox = current.offsetLeft; //Get element's position
Oy = current.offsetTop;
current.style.zIndex = ++enableDragging.z; //z-index thing
window.onmousemove = function(ev) {
if (dragging == true) { //when it is dragging
var Sx = ev.clientX - x + Ox, //Add the difference between
Sy = ev.clientY - y + Oy; // 2 mouse position to the
current.style.top = Sy + "px"; // element.
current.style.left = Sx + "px";
return false; //Don't care about this.
}
};
window.onmouseup = function(ev) {
dragging && (dragging = false); //Mouse up, dragging done!
}
};
}
enableDragging(document.getElementById("drag")); //draggable now!
var ele = document.getElementsByTagName("div");
for(var i = 0; i < ele.length; i++){ //Every div's is draggable
enableDragging(ele[i]); // (only when its "position"
} // is set to "absolute" or
// "relative")
http://jsfiddle.net/DerekL/NWU9G/
The reason why your code is not working is because the <div> will always follow where your cursor goes, and you are not actually dragging it. The top left corner will always follow your cursor, and this is not we wanted.
UPDATE
Now if you only want a grabber or something similar, just change this part of the script:
ele.onmousedown = function(ev) {
current = ev.target;
to
var grabber = document.createElement("div");
grabber.setAttribute("class", "grabber");
ele.appendChild(grabber);
grabber.onmousedown = function(ev) {
current = ev.target.parentNode;
Now you can only click on the grabber to start the dragging process.
http://jsfiddle.net/DerekL/NWU9G/7/

Categories