Using HTML5 canvas, Drag and Drop and File API to offer The Cure
By using a combination of the <canvas>
element and the File API we could put together a little service that offers “The Cure” for many people.
The cure for what, you might ask? Baldness, of course. π
I’m going to go through the basics here to show how you can drag an image from your desktop into the web browser, drop it into a <canvas>
element and then start drawing on it; to be more specific, draw something that could, for instance, be hair on the picture of a bald guy. When you are done, you also have the option to save what you have accomplished in the canvas
.
The end result
You can see/try out this example in The Cure demo page. I’ve also put together a little video showing this in action, offered below through the HTML5 <video>
element and the vid.ly video service:
Web browser support disclaimer
Before we begin, let me state that this will only work fully as intended in Firefox and Google Chrome (not Safari). All the canvas things should work fine in Safari, Opera and IE9, but the Drag and Drop combined with the File API is the things that won’t.
First steps
To begin with, we create a canvas element in a page and get some scripting references:
HTML
<canvas id="my-canvas" width="700" height="600">I am canvas</canvas>
JavaScript
var canvas = document.getElementById("my-canvas"), context = canvas.getContext("2d"), img = document.createElement("img"), mouseDown = false, brushColor = "rgb(0, 0, 0)", hasText = true, clearCanvas = function () { if (hasText) { context.clearRect(0, 0, canvas.width, canvas.height); hasText = false; } };
This is basically just about getting a reference to the canvas element, its 2d-context we will be drawing on and some extra variables for detecting what has happened on the canvas, brush color and such. The img
variable is a container image for future loading use.
Instructions and loading an image
// Adding instructions context.fillText("Drop an image onto the canvas", 240, 200); context.fillText("Click a spot to set as brush color", 240, 220); // Image for loading img.addEventListener("load", function () { clearCanvas(); context.drawImage(img, 0, 0); }, false);
What we do here is draw some text in the canvas element to show initial instructions, and also applying a loading event to a container image we will be using to load dropped images on the canvas and then draw it into the canvas once it has loaded.
Drag and drop
// To enable drag and drop canvas.addEventListener("dragover", function (evt) { evt.preventDefault(); }, false); // Handle dropped image file - only Firefox and Google Chrome canvas.addEventListener("drop", function (evt) { var files = evt.dataTransfer.files; if (files.length > 0) { var file = files[0]; if (typeof FileReader !== "undefined" && file.type.indexOf("image") != -1) { var reader = new FileReader(); // Note: addEventListener doesn't work in Google Chrome for this event reader.onload = function (evt) { img.src = evt.target.result; }; reader.readAsDataURL(file); } } evt.preventDefault(); }, false);
First, we need to apply the dragover
event to the canvas and cancel its default behavior; yes, folks, that’s how the Drag and Drop specification and implementation works, to make the drop
event to be triggered…
The second part is handling a dropped image: it gets a reference to the dropped file, and then utilizes the FileReader
object to directly read out the dropped image as code (i.e. as a data URL, a base64-encoded representation of the image). When it has finished loading the dropped image into the FileReader
it loads that image as a src for the image container we created before.
Drawing on the canvas
So, now when we have the basic things set up, Drag and Drop in place and drawing an image into the canvas, it’s time to add drawing features.
// Detect mousedown canvas.addEventListener("mousedown", function (evt) { clearCanvas(); mouseDown = true; context.beginPath(); }, false); // Detect mouseup canvas.addEventListener("mouseup", function (evt) { mouseDown = false; var colors = context.getImageData(evt.layerX, evt.layerY, 1, 1).data; brushColor = "rgb(" + colors[0] + ", " + colors[1] + ", " + colors[2] + ")"; }, false); // Draw, if mouse button is pressed canvas.addEventListener("mousemove", function (evt) { if (mouseDown) { context.strokeStyle = brushColor; context.lineWidth = 20; context.lineJoin = "round"; context.lineTo(evt.layerX+1, evt.layerY+1); context.stroke(); } }, false);
First we detect if the mouse is being pressed and make sure that the drawing will begin a new path, i.e. not closing the drawing for a potential previous path. We have a mouseup
event, and the interesting thing to mention there is a little extra side-feature: if you click somewhere, we will use the getImageData
method on the canvas context to detect the color of the canvas coordinates where the mouse button was released. Here we just read out the RGB color and then set that as the new brush color for our drawing.
With the mousemove
event, if the mouse button is being held down, we draw a 20 pixels wide rounded line with the chosen brush color where the mouse currently is.
Saving the canvas content
When you are happy with the creation you have made in the canvas, we have a dynamically generated button that will open the canvas content in a new window/tab (depending on your web browser’s default behavior), just as a data URL in the PNG format.
The entire code
This is what the entire code looks like:
(function () { var canvas = document.getElementById("my-canvas"), context = canvas.getContext("2d"), img = document.createElement("img"), mouseDown = false, brushColor = "rgb(0, 0, 0)", hasText = true, clearCanvas = function () { if (hasText) { context.clearRect(0, 0, canvas.width, canvas.height); hasText = false; } }; // Adding instructions context.fillText("Drop an image onto the canvas", 240, 200); context.fillText("Click a spot to set as brush color", 240, 220); // Image for loading img.addEventListener("load", function () { clearCanvas(); context.drawImage(img, 0, 0); }, false); // Detect mousedown canvas.addEventListener("mousedown", function (evt) { clearCanvas(); mouseDown = true; context.beginPath(); }, false); // Detect mouseup canvas.addEventListener("mouseup", function (evt) { mouseDown = false; var colors = context.getImageData(evt.layerX, evt.layerY, 1, 1).data; brushColor = "rgb(" + colors[0] + ", " + colors[1] + ", " + colors[2] + ")"; }, false); // Draw, if mouse button is pressed canvas.addEventListener("mousemove", function (evt) { if (mouseDown) { context.strokeStyle = brushColor; context.lineWidth = 20; context.lineJoin = "round"; context.lineTo(evt.layerX+1, evt.layerY+1); context.stroke(); } }, false); // To enable drag and drop canvas.addEventListener("dragover", function (evt) { evt.preventDefault(); }, false); // Handle dropped image file - only Firefox and Google Chrome canvas.addEventListener("drop", function (evt) { var files = evt.dataTransfer.files; if (files.length > 0) { var file = files[0]; if (typeof FileReader !== "undefined" && file.type.indexOf("image") != -1) { var reader = new FileReader(); // Note: addEventListener doesn't work in Google Chrome for this event reader.onload = function (evt) { img.src = evt.target.result; }; reader.readAsDataURL(file); } } evt.preventDefault(); }, false); // Save image var saveImage = document.createElement("button"); saveImage.innerHTML = "Save canvas"; saveImage.addEventListener("click", function (evt) { window.open(canvas.toDataURL("image/png")); evt.preventDefault(); }, false); document.getElementById("main-content").appendChild(saveImage); })();
Combining APIs
This is just an example of how you can combine a number of HTML5 APIs to interact with the user’s desktop/file system, read out file information and offering drawing and saving in the web browser. Now go code something nice. π
Robert,
Thanks for sharing this. I have done some work along the same lines – Instant Sprite, an in browser CSS sprite generator (code). I have also wrapped up the File Reading into a small library that allows you to access files via the standard <input type=”file” /> change event or from a drop event, and lets you process files as groups, filter by content types, etc: filereader.js
Brian,
Thanks for the tip.
nice work…i am no coder but i want to be able to move the images i drag to the canvas,can it be done with few lines of code?
crossaxx,
Well, no, it’ll take a little more than a few lines of code.
hello
this code is what i was looking for..thank you π
but I’ve problem running it offline…your online demo works fine but when i create my own index the image won’t load but i can draw…i also tried to download the whole demo and i got the same problem…it detects that i am moving the image on the canvas and it writes “+Move” but the image won’t show after dragging it to the canvas but still i can draw
any suggestions? is it because i am running it offline?
note that i tried it on Firefox 4 and chrome 10
your help is really appreciated
here is an image of whats going on http://img64.imageshack.us/i/mariammm.png/
thanks π
Maria,
No, it works offline for me too. Do you get any JavaScript errors or something?
no javascript errors..its freaking me out!
could you please send the working index to wiswis911@live.com?
i am sure i did every thing right but I’ve got nothing else to try except your index that worked offline
Thanks again π
Maria,
Please just take the source from http://robertnyman.com/html5/canvas/the-cure.html or save that page in the web browser (File > Save Page As). There are no other dependencies, it’s all there.
I just saved it and ran it locally, and it worked the same way.
Good luck!
already tried that…there must be something wrong with my system, thanks anyway
hello
how about if i wan’t to drag and image from the same page to the canvas or from another browser window?
thx
Maria,
Sorry about that.
ray,
That should in general work fine, too. It depends om what data is exposed and the operating system you use.
Hello.
Are questions to be asked.
“addEventListener doesn’t work in Google Chrome for this event”
But to work in Chrome do you know what to do?
Please let me know ..
CHUNG,
The demo works fine in Google Chrome for me. Which version do you have and which event doesn’t work?
hi..
Drag and drop a file on the canvas does not…
help me…
Drag and drop the file does not look at the canvas…
sorry, i can’t speak english well..
CHUNG,
I’m sorry, I really can’t help. For me it works in Google Chrome to drag and drop – you need to give me more information then.
I haven’t coded in javascript for a while and I needed something to revisit and brush up…
Thanks for sharing!
sm1112,
Glad you liked it!
Maria, Chrome cannot read the image data natively on your computer. You must either run your code from localhost, or upload your code to your webspace and test it that way. Not sure why the author couldn’t explain this to you
Chris,
Maybe because it wasn’t clear that she was running it as a local file compared to localhost? She also mentioned that it didn’t work in Firefox, but running local files work fine in Firefox, not just in Google Chrome.
Hi….
I’m just learning….
I’m wondering if it is possible to create a maze over an existing image using canvas? Any info would be greatly appreciated. Thanks for all that you share here—-awesome!!!
Monica,
It is definitely possible, but it would take some code to put it together.
Here is the example running at jsfiddle.
http://jsfiddle.net/dansalmo/eyyMJ/
Dan,
Thanks, but the link doesn’t work.
Just chiming in for people who couldn’t run this locally. I’ve run into the same problem before, where drag and drop doesn’t work by opening the html file in the browser. As another commenter said, you need to run a local server. On a Mac, this is how I did it.
Open up Terminal
Navigate to the folder (do this by typing “cd ” then dragging the folder onto Terminal for the path), hit Enter
then type: “python -m SimpleHTTPServer” hit Enter
Then it should say something like: Serving HTTP on 0.0.0.0 port 8000 …
Now you have a server running, here it’s on port 8000, but it can be different for you.
In your browser, you can then use the site by navigating to: http://localhost:8000/myfile.html
where “8000” is the port number
I hope that helps.
Great tutorial, btw. I’m about to use it in a project!
Michelle,
Thanks, and also thanks for the tip for people!
is it the same with this http://www.customink.com/lab
wherein from list you can click a image then it will show to the canvas and you can move it freely within the set bounds? and you can save it?
Kevin,
Well, it’s similar. More of a coding exercise, really.
Hey Robert,
Thanks for the concise example. When I run it in chrome the painbrush is offset incorrectly.
Should line 45 be:
context.lineTo(evt.offsetX+1, evt.offsetY+1);
Rather than:
context.lineTo(evt.layerX+1, evt.layerY+1);
So, the post is three and a half years old, so can’t really say what code will still be working across the board. But
layerX
works in the other web browsers, but WebKit/Blink rendering engines. So for that, you need to do a workaround and some code branching (googling layerX will show you people’s problems with it and alternatives).hi, thanks for this example, hopefully you are still around π
if you can remember this old code… I found it highly usable, but I don’t know how could I load image from url (drop it from another site), code works only for files dropped from my computer. If you have any ideas how to do this, please drop me email…Thanks in advance for any hint!