Implement dragging an image within a canvas - javascript

I want to implement dragging of an image within a canvas. I want simplest code for that. So far I have seen a lot of examples but they have used complex ways of implementation. I want an example that is easy to learn and implement.

It's pretty difficult. You'll first need to write a function that can detect when you click a particular element. However, before we can do that, we must define what we mean by "element". Is it the product of a single draw instruction (e.g. a rectangle or arc), or something complex? (Imagine I wanted to draw a cat and make the entire cat draggable as a unit.)
A canvas is nothing but a collection of pixels. If you want your program to have an idea of "shapes" or even "collections of shapes treated as a unit" you'll need to implement them yourself as data structures external to the canvas itself. Once you have that, you can write an onmousedown handler that takes the x/y point clicked and determine what shape (if any) the click falls inside of (and if it falls inside of multiple shapes, check which has the foremost z-index). Then add an onmousemove handler that erases and redraws the shape on the canvas based on the information in the shape data object.
This is a moderately difficult problem with very difficult prerequisite problems (creating data structures that can describe a wide range of shapes as well as collections of shapes). I highly recommend you use a canvas drawing library that has already solved these problems. I use cake.js but there are loads of options available.

If you don't have to use the HTML5 canvas, jQuery UI is a lot simpler:
HTML:
<img class="drag-me" src="http://www.google.com/images/srpr/logo3w.png">​
JavaScript:
$(function() {
$('.drag-me').draggable();
});
​
See it in action:
http://jsfiddle.net/flackend/TQzSe/
The jQuery UI API has a lot of options too to make it act how you want:
http://jqueryui.com/demos/draggable/
Plus, if it doesn't do what you need, it's easy to implement yourself. Post here if you need help with that.

jsfiddle.net/Zevan/QZejF/5 This may help you.
<html>
<head>
<title>Test Page</title>
<script type="text/javascript" src="jquery.js"></script>
</head>
<body>
<canvas id="c" width = "500" height = "500" ></canvas>
<script type="text/javascript">
var canvas = $("#c");
var c = canvas[0].getContext("2d");
//var path = "http://wonderfl.net/images/icon/e/ec/ec3c/ec3c37ba9594a7b47f1126b2561efd35df2251bfm";
var path = "blue.jpg";
var path2 = "purple.jpg";
var image1 = new DragImage(path, 200, 100);
var image2 = new DragImage(path2, 300, 100);
var loop = setInterval(function() {
c.fillStyle = "gray";
c.fillRect(0, 0, 500, 500);
image1.update();
image2.update();
}, 30);
var mouseX = 0,
mouseY = 0;
var mousePressed = false;
var dragging = false;
canvas.mousemove(function(e) {
mouseX = e.offsetX;
mouseY = e.offsetY;
})
$(document).mousedown(function() {
mousePressed = true;
}).mouseup(function() {
mousePressed = false;
dragging = false;
});
function DragImage(src, x, y) {
var that = this;
var startX = 0,
startY = 0;
var drag = false;
this.x = x;
this.y = y;
var img = new Image();
img.src = src;
this.update = function() {
if (mousePressed ) {
var left = that.x;
var right = that.x + img.width;
var top = that.y;
var bottom = that.y + img.height;
if (!drag) {
startX = mouseX - that.x;
startY = mouseY - that.y;
}
if (mouseX < right && mouseX > left && mouseY < bottom && mouseY > top) {
if (!dragging){
dragging = true;
drag = true;
}
}
} else {
drag = false;
}
if (drag) {
that.x = mouseX - startX;
that.y = mouseY - startY;
}
c.drawImage(img, that.x, that.y);
}
}
</script>
</body>
</html>

Related

Image in canvas leaves a tiled trail when panned

I am trying to create a pannable image viewer which also allows magnification. If the zoom factor or the image size is such that the image no longer paints over the entire canvas then I wish to have the area of the canvas which does not contain the image painted with a specified background color.
My current implementation allows for zooming and panning but with the unwanted effect that the image leaves a tiled trail after it during a pan operation (much like the cards in windows Solitaire when you win a game). How do I clean up my canvas such that the image does not leave a trail and my background rectangle properly renders in my canvas?
To recreate the unwanted effect set magnification to some level at which you see the dark gray background show and then pan the image with the mouse (mouse down and drag).
Code snippet added below and Plnkr link for those who wish to muck about there.
http://plnkr.co/edit/Cl4T4d13AgPpaDFzhsq1
<!DOCTYPE html>
<html>
<head>
<style>
canvas{
border:solid 5px #333;
}
</style>
</head>
<body>
<button onclick="changeScale(0.10)">+</button>
<button onclick="changeScale(-0.10)">-</button>
<div id="container">
<canvas width="700" height="500" id ="canvas1"></canvas>
</div>
<script>
var canvas = document.getElementById('canvas1');
var context = canvas.getContext("2d");
var imageDimensions ={width:0,height:0};
var photo = new Image();
var isDown = false;
var startCoords = [];
var last = [0, 0];
var windowWidth = canvas.width;
var windowHeight = canvas.height;
var scale=1;
photo.addEventListener('load', eventPhotoLoaded , false);
photo.src = "http://www.html5rocks.com/static/images/cors_server_flowchart.png";
function eventPhotoLoaded(e) {
imageDimensions.width = photo.width;
imageDimensions.height = photo.height;
drawScreen();
}
function changeScale(delta){
scale += delta;
drawScreen();
}
function drawScreen(){
context.fillRect(0,0, windowWidth, windowHeight);
context.fillStyle="#333333";
context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
}
canvas.onmousedown = function(e) {
isDown = true;
startCoords = [
e.offsetX - last[0],
e.offsetY - last[1]
];
};
canvas.onmouseup = function(e) {
isDown = false;
last = [
e.offsetX - startCoords[0], // set last coordinates
e.offsetY - startCoords[1]
];
};
canvas.onmousemove = function(e)
{
if(!isDown) return;
var x = e.offsetX;
var y = e.offsetY;
context.setTransform(1, 0, 0, 1,
x - startCoords[0], y - startCoords[1]);
drawScreen();
}
</script>
</body>
</html>
You need to reset the transform.
Add context.setTransform(1,0,0,1,0,0); just before you clear the canvas and that will fix your problem. It sets the current transform to the default value. Then befor the image is draw set the transform for the image.
UPDATE:
When interacting with user input such as mouse or touch events it should be handled independently of rendering. The rendering will fire only once per frame and make visual changes for any mouse changes that happened during the previous refresh interval. No rendering is done if not needed.
Dont use save and restore if you don't need to.
var canvas = document.getElementById('canvas1');
var ctx = canvas.getContext("2d");
var photo = new Image();
var mouse = {}
mouse.lastY = mouse.lastX = mouse.y = mouse.x = 0;
mouse.down = false;
var changed = true;
var scale = 1;
var imageX = 0;
var imageY = 0;
photo.src = "http://www.html5rocks.com/static/images/cors_server_flowchart.png";
function changeScale(delta){
scale += delta;
changed = true;
}
// Turns mouse button of when moving out to prevent mouse button locking if you have other mouse event handlers.
function mouseEvents(event){ // do it all in one function
if(event.type === "mouseup" || event.type === "mouseout"){
mouse.down = false;
changed = true;
}else
if(event.type === "mousedown"){
mouse.down = true;
}
mouse.x = event.offsetX;
mouse.y = event.offsetY;
if(mouse.down) {
changed = true;
}
}
canvas.addEventListener("mousemove",mouseEvents);
canvas.addEventListener("mouseup",mouseEvents);
canvas.addEventListener("mouseout",mouseEvents);
canvas.addEventListener("mousedown",mouseEvents);
function update(){
requestAnimationFrame(update);
if(photo.complete && changed){
ctx.setTransform(1,0,0,1,0,0);
ctx.fillStyle="#333";
ctx.fillRect(0,0, canvas.width, canvas.height);
if(mouse.down){
imageX += mouse.x - mouse.lastX;
imageY += mouse.y - mouse.lastY;
}
ctx.setTransform(scale, 0, 0, scale, imageX,imageY);
ctx.drawImage(photo,0,0);
changed = false;
}
mouse.lastX = mouse.x
mouse.lastY = mouse.y
}
requestAnimationFrame(update);
canvas{
border:solid 5px #333;
}
<button onclick="changeScale(0.10)">+</button><button onclick="changeScale(-0.10)">-</button>
<canvas width="700" height="500" id ="canvas1"></canvas>
Nice Code ;)
You are seeing the 'tiled' effect in your demonstration because you are painting the scaled image to the canvas on top of itself each time the drawScreen() function is called while dragging. You can rectify this in two simple steps.
First, you need to clear the canvas between calls to drawScreen() and second, you need to use the canvas context.save() and context.restore() methods to cleanly reset the canvas transform matrix between calls to drawScreen().
Given your code as is stands:
Create a function to clear the canvas. e.g.
function clearCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
In the canavs.onmousemove() function, call clearCanvas() and invoke context.save() before redefining the transform matrix...
canvas.onmousemove = function(e) {
if(!isDown) return;
var x = e.offsetX;
var y = e.offsetY;
/* !!! */
clearCanvas();
context.save();
context.setTransform(
1, 0, 0, 1,
x - startCoords[0], y - startCoords[1]
);
drawScreen();
}
... then conditionally invoke context.restore() at the end of drawScreen() ...
function drawScreen() {
context.fillRect(0,0, windowWidth, windowHeight);
context.fillStyle="#333333";
context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
/* !!! */
if (isDown) context.restore();
}
Additionally, you may want to call clearCanvas() before rescaling the image, and the canvas background could be styled with CSS rather than .fillRect() (in drawScreen()) - which could give a performance gain on low spec devices.
Edited in light of comments from Blindman67 below
See Also
Canvas.context.save : https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/save
Canvas.context.restore : https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/restore
requestAnimationFrame : https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame
Paul Irish, requestAnimationFrame polyfill : http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
Call context.save to save the transformation matrix before you call context.fillRect.
Then whenever you need to draw your image, call context.restore to restore the matrix.
For example:
function drawScreen(){
context.save();
context.fillStyle="#333333";
context.fillRect(0,0, windowWidth, windowHeight);
context.restore();
context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
}
Also, to further optimize, you only need to set fillStyle once until you change the size of canvas.

How can I make different shapes of a canvas draggable and particular area of it droppable in the same canvas

I want to create a Canvas in which there will be two areas (Left and right), Left panel will contain some shapes which will be draggable(static as well) and on the right side I would be able to drop them, but I am facing following problem,
I am not able to make the shapes which i draw on the left side, draggable, because there is no id associated with them.
I do not know how to make some particular area droppable.
Here is code to visualize what I am trying to achieve-
<body>
<canvas id="myCanvas" width="800" height="600" style="border:1px solid #000000;">
</canvas>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.moveTo(250,0);
ctx.lineTo(250,600);
ctx.stroke();
ctx.fillStyle = "#FF0000";
ctx.fillRect(50,50,160,25);
ctx.fillStyle = "#0000FF";
ctx.font = "15px";
ctx.strokeText("Draggable Elements here",57,67);
ctx.fillStyle = "#FF0000";
ctx.fillRect(500,50,130,25);
ctx.font = "15px";
ctx.strokeText("Droppable area Here",510,67);
</script>
</body>
Here is the JS fiddle for the same -
http://jsfiddle.net/akki166786/4tfyy4o5/
so if anybody can shed some light on how can I achieve this, it will be a great help.
Thanks in Advance
Drag and drop in specifik area
UPDATE: Copy of box remains at original position while it's being moved.
First you need to be able to detect your rectangles. You do this by making then into objects in your code:
function box(x,y,w,h,rgb) {
this.x = x,
this.y = y;
this.xS = x; //saving x
this.yS = y; //saving y
this.w = w;
this.h = h;
this.rgb = rgb;
//to determine if the box is being draged
this.draging = false;
}
No you need to add an event listener to determine if someone is clicking, you also need to determine if the person clicked in one of your boxes.
c.addEventListener("mousedown",down);
c.addEventListener("mousemove",move);
c.addEventListener("mouseup",up);
So events have been made to detect when the mouse button is pressed down, released back up and if the mouse moves within the canvas. To these events we have functions, down(), move() and up(), ready to be executed.
All functions will be visible in the example below.
When we're happily draging our boxes and releasing our mouse button, we need to check if the box was dropped in the dropable area. We do this in the up()-function. If the drop was OK, the box can stay, otherwise we send it back to where it came from.
Working example
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
c.width = 600;
c.height = 300;
//My mouse coordinates
var x,y;
c.addEventListener("mousedown",down);
c.addEventListener("mousemove",move);
c.addEventListener("mouseup",up);
//I'll save my boxes in this array
var myBoxes = new Array();
//This function describes what a box is.
//Each created box gets its own values
function box(x,y,w,h,rgb) {
this.x = x,
this.y = y;
this.xS = x; //saving x
this.yS = y; //saving y
this.w = w;
this.h = h;
this.rgb = rgb;
//to determine if the box is being draged
this.draging = false;
}
//Let's make some boxes!!
myBoxes[0] = new box(10,10,50,100,"green");
myBoxes[1] = new box(80,50,100,75,"blue");
myBoxes[2] = new box(40,150,20,70,"yellow");
//here we draw everything
function draw() {
ctx.clearRect(0,0,c.width,c.height);
//Dropable area
ctx.fillStyle="red";
ctx.fillRect(c.width/2,0,c.width,c.height);
//Boxes!
for (var i = 0; i<myBoxes.length; i++) {
var b = myBoxes[i];
//NEW CODE FOR UPDATE
if (b.draging) { //box on the move
//Also draw it on the original spot
ctx.fillStyle="grey"; //I chose a different color to make it appear more as a shadow of the box that's being moved.
ctx.fillRect(b.xS,b.yS,b.w,b.h);
ctx.strokeRect(b.xS,b.yS,b.w,b.h);
}
//End of new code for update
ctx.fillStyle=b.rgb;
ctx.fillRect(b.x,b.y,b.w,b.h);
ctx.strokeRect(b.x,b.y,b.w,b.h);
}
//Let's keep re-drawing this
requestAnimationFrame(draw);
}
function down(event) {
event = event || window.event;
x = event.pageX - c.offsetLeft,
y = event.pageY - c.offsetTop;
for (var i = 0; i<myBoxes.length; i++) {
var b = myBoxes[i];
if (x>b.x && x<b.x+b.w && y>b.y && y<b.y+b.h) {
b.draging = true;
}
}
}
function move(event) {
event = event || window.event;
x = event.pageX - c.offsetLeft,
y = event.pageY - c.offsetTop;
for (var i = 0; i<myBoxes.length; i++) {
var b = myBoxes[i];
if (b.draging) {
b.x = x;
b.y = y;
}
}
}
function up(event) {
event = event || window.event;
x = event.pageX - c.offsetLeft,
y = event.pageY - c.offsetTop;
for (var i = 0; i<myBoxes.length; i++) {
var b = myBoxes[i];
if (b.draging) {
//Let's see if the rectangle is inside the dropable area
if (b.x>c.width/2) {
//Yes is it!
b.x = x;
b.y = y;
b.draging = false;
}
else {
//No it's not, sending it back to its ordiginal spot
b.x = b.xS;
b.y = b.yS;
b.draging = false;
}
}
}
}
draw();
canvas {
border: 1px solid black;
}
<canvas id="canvas"></canvas>
You're using just one canvas, maybe it would be better if you use two separate canvas, one for each element you want to handle on page. so you'll have one element ID for each one.
plus. if your drawing is simple, consider using a div for it instead a canvas
Once drawn to the canvas, shapes(or lines, images, everything) are no longer accessible.
What you will need to do is store each shape in an object in your code. For example:
var rectangle = {
width: 100,
height: 100,
x: 50,
y: 50
}
Then when you drag rectangle, you will need to update it's x and y properties on mouseup (or while it's being dragged if you want a drag preview).

HTML5/JavaScript based game - Controlling x/y coordinates with touch

I am trying to make a HTML5 Canvas/JavaScript game. I watched some tutorials, and I understand what everying is meant for, but I don't get the right controls for touch devices.
I want to get my 'airship', where my finger touches the screen. It is not only working with where my mouse is positioned.
<!DOCTYPE html>
<html>
<head>
<title>Title</title>
<script type="text/javascript">
window.onload = function() {
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var canvasWidth = canvas.offsetWidth;
var canvasHeight = canvas.offsetHeight;
var bg1 = new Image();
var bg2 = new Image();
bg1.src = "img/spritesheet.png";
bg2.src = "img/spritesheet.png";
var increment = -5;
var sYbg1 = 0;
var sYbg2 = 960;
var ship = new Image();
ship.src = "img/spritesheet.png";
var mouseX;
var mouseY;
canvas.onmousemove = function() {
mouseX = window.event.clientX - canvas.offsetLeft; // mousePositionX
inside the <canvas> element
mouseY = window.event.clientY - canvas.offsetTop; // mousePositionY
inside the <canvas> element
};
var animate = function() {
context.clearRect(0,0,canvasWidth,canvasHeight);
context.drawImage(bg1,0,sYbg1,canvasWidth,canvasHeight,0,0,canvasWidth,canvasHeight);
context.drawImage(bg2,0,sYbg2,canvasWidth,canvasHeight,0,0,canvasWidth,canvasHeight);
sYbg1 += increment;
sYbg2 += increment;
if(sYbg2 <= 0) {
sYbg1 = 0;
sYbg2 = 960;
}
context.drawImage(ship,320,0,65,105,(mouseX-32),(mouseY-52),65,105);
setTimeout(animate,25);
};
animate();
}
</script>
</head>
<body>
<canvas id="canvas" width="320" height="480"></canvas>
</body>
</html>
(!MY CODE IS ONLY WORKING IN CHROME!) --> But thats not important not, I know about the window.event and that it's not working in FF.
I hope someone can explain to me what I need to do. I tried to fix my problem to use onmousemove and onmousedown together, but that wasn't working for me too. Thanks a lot for the help!
I will provide a hint, in case you haven't gotten there yet, and for future researchers. You have a mouse event handler (mousemove). I don't see a touch event handler. There are some options depending on your browser, which is unclear to me which you want to use...
However you can start looking at https://developers.google.com/ or MDN https://developer.mozilla.org/en-US/ as good starting points.

replacing cursor with image to be used as target in javascript

I've tried for a couple of weeks to trouble shoot this problem and I've come to a dead end.
as a word of warning I'm very new to coding and I may not be able to understand much.
I'm trying to draw an image at the location of the mouse.
this is my script so far
$(function()
{
var canvas = $('#canvas')[0];
var context = canvas.getContext("2d");
var img = new Image();
img.src = 'cross.png';
function getTopLeft(elm)
{
var x, y = 0;
x = elm.offsetLeft;
y = elm.offsetTop;
elm = elm.offsetParent;
while(elm != null)
{
x = parseInt(x) + parseInt(elm.offsetLeft);
y = parseInt(y) + parseInt(elm.offsetTop);
elm = elm.offsetParent;
}
return {Top:y, Left: x};
}
canvas.style.cursor = "none";
canvas.addEventListener("mousemove", function (ev)
{
var mouseX = ev.pageX - getTopLeft(canvas).Left;
var mouseY = ev.pageX - getTopLeft(canvas).Top;
});
function animate()
{
context.onmousemove = function(evt)
{
context.clearRect(0,0,canvas.width,canvas.height);
context.drawImage(img, Left, Top);
}
}
});
I think the problem lies in defining the x y value of the picture ive tried every variable used in the code but to no avail the script loads without error but doesnt draw the image at the mouse location.
I have just realised also that i need the image to be centred over the mouse position.

Is it possible to make every line that I draw draggable in Canvas?

This is my Javascript
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
canvas.addEventListener('click', drawLine, false);
var clicks = 0;
var lastClick = [0, 0];
$(function() {
$.each(['#f00', '#ff0', '#0f0', '#0ff', '#00f', '#f0f', '#000', '#fff'], function() {
$('#tools').append("<a href='#' onclick=\"context.strokeStyle = '" + this + "';return false;\" style='width: 10px; background: " + this + ";'></a> ");
});
});
function getCursorPosition(e) {
var x;
var y;
if (e.pageX != undefined && e.pageY != undefined) {
x = e.pageX;
y = e.pageY;
} else {
x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
return [x, y];
}
function drawLine(e) {
//context = this.getContext('2d');
x = getCursorPosition(e)[0] - this.offsetLeft;
y = getCursorPosition(e)[1] - this.offsetTop;
if (clicks != 1) {
clicks++;
} else {
context.beginPath();
context.moveTo(lastClick[0], lastClick[1]);
context.lineTo(x, y, 6);
// context.strokeStyle = '#000000';
context.stroke();
clicks = 0;
}
lastClick = [x, y];
};
This HTML
<div id='tools'>
</div>
<canvas id="canvas" width="500" height="500"></canvas>
I want to generate a new DIV when I finish drawing the line, and make it draggable, how can I do it? I not sure how can contain the line inside the DIV.
The Div should be create when I stop drawing one line.
This is the Jfiddle for a clearer picture
http://jsfiddle.net/pVZzY/1/
I suggest kinetic.js for this task.
Take a look at it http://kineticjs.com/
This will surely make your intended job much easier
Canvas doesn't allow you to alter/move/scale the objects that you've just draw.
Think of it as paper sheet that you color up with pencils, you can't move the pencil line around the only way is to erase the old line and draw a new one - same goes for canvas.
But! There are a bunch of libraries that make working with canvas easier, one of which is http://kineticjs.com/, another one is http://paperjs.org/. I can't claim for kineticjs, but seems it's similar to paper.js in the way that they both create an Object layer.
Briefly speaking - they provide you with API to create and change scene objects (images, lines, shapes) and deal with draw-clear-redraw Canvas concept at the backstage.
fyi, regarding js libs mentioned in other posts, i just checked and:
kineticjs is no longer maintained (3-5 years) but last version is apparently very stable. 3775 stars on github.
paper is still going. > 10,000 stars on github.

Categories

Resources