HTML5 Challenge #1 – The result

Creative Commons: http://www.w3.org/html/logo/

Creative Commons: http://www.w3.org/html/logo/

Two weeks One month (I decided to give one month as requested by many people) have passed since I put the first challenge online, so it’s now the time to show you my solution. On one side, for my own pleasure, I wanted to give a more than complete solution with many options, or different ways to do it. On the other side, too often I saw examples about a specific element of a programming language that were too complex. If you were a new developer, you can get lost easily with all the code just to find what you were looking for, so I decided to take the simpler path.

Because I know how the Internet is working, let me also specify that this is not considered as the ultimate, and perfect solution: this is the solution I made with my knowledges, and I will be more than happy to discuss it in the comment section. The last thing to take into consideration before we start: I suck with design, and it’s OK as the goal is not to have something pretty, but something functional. To make things clear, people who just want to play with the code can make it yours by cloning, forking, or downloading a copy of it from the repository on GitHub. For others, I’ll explain each part, and if you have any questions, please let me know in the comment section.

For the rest of the post, to save some space, I assume you know the basics of HTML, so I will paste or write only about things directly related to the challenge (example: I won’t paste the import statement for my own JavaScript file), but the sources on my GitHub profile are complete. Note also that all my code has been verified with the HTML W3c validator, CSS W3C validator, and JSLint (note that for JSLint you’ll never have a perfect validation with my code as I ignore some of the errors it’s reporting). I also make the code, and tested in on the latest releases (no nightly builds) of Internet Explorer, Firefox, Chrome, Safari, and Opera. If you want to test my code, it’s also live.

Let’s start with the HTML code:

[html] <input id=”fileImage” type=”file” accept=”image/*” /> <button id=”imgSave” disabled=”disabled”><img id=”image” alt=”Drawing to save” src=”#” /> <canvas id=”canvas”>[/html]

The <input> of type file will give us the opportunity to open the image to draw on it. We won’t use a form here to load the file as we’ll use File API later. The <button> element will give us the opportunity to take the draw on the <canvas> element, and fire up some JavaScript code that will create the image we’ll be able to save. For now, it is disable as we didn’t load an image yet, so this button is useless. The <canvas> element will let the user draw on the image we’ll load. Last but not least, the <img> element where we will load the result of our saved image so users will be able to load it. Nothing very exciting there, but you have the first step to a successful challenge. Now, let’s take a look at where all the magic happens, the JavaScript file:

[javascript] //Add the events at the loading of the page window.onload = function() { //Using Modernizr to verify if the browser support canvas & FileApi if (Modernizr.canvas && !!window.FileReader) {//Since we are using HTML5, we don’t have to support < IE9 (no attachEvent) document.getElementById(“fileImage”).addEventListener(“change”, loadImage); document.getElementById(“canvas”).addEventListener(“mousedown”, startDrawOnCanvas); document.getElementById(“canvas”).addEventListener(“mouseup”, stopDrawOnCanvas); document.getElementById(“imgSave”).addEventListener(“click”, saveImage); } else { //We disable elements as they can’t use them document.getElementById(“imgSave”).disabled = true; document.getElementById(“fileImage”).disabled = true; updateStatus(“You browser doesn’t support the canvas element or the File API”, “red”); } }; [/javascript]

The first thing is to prepare our application to react to different events. Right before, we’ll check if the browser supports canvas by using a subset of Modernizr, and if the browser support also the File API. If it’s the case, let’s add a simple change event on the file input so we’ll be able to load the file when the user will select one. We’ll add mouseup, and mousedown events to the canvas so we know when the user will draw on it. Last but not least, we’ll add a click event to the generate button to fire up the function that will save the image from the canvas. If the browser use by the user don’t support canvas or File API will disable elements on the page, and let him know about the fact he can’t use our application. The second part of the code is the function that will load the file loaded by the user into the canvas element.

[javascript] //Load the image from the file input, and draw it to the canvas function loadImage() { var canvas = document.getElementById(“canvas”); canvas.style.visibility = “visible”;var htmlImage = document.getElementById(“image”); htmlImage.style.visibility = “hidden”;var reader = new FileReader();reader.readAsDataURL(document.getElementById(‘fileImage’).files[0]);reader.onload = (function(e) { var image = new Image(); image.src = e.target.result;image.onload = (function() { //Get the Canvas, and set the size of the images we got canvas.height = image.height; canvas.width = image.width;//Set the image element to the same size htmlImage.height = image.height; htmlImage.width = image.width;var context = canvas.getContext(“2d”); context.drawImage(image, 10, 10);document.getElementById(“imgSave”).disabled = false; updateStatus(“The image has been loaded, you can now draw on it”, “green”); }); }); } [/javascript]

The first thing to do is to make sure that the canvas element is visible, and the img is not. It’s not critical, but I added these lines of code to make sure the users can use the tool more than once without having to refresh the page. The magic happens at line 9, where I started to access the file with the FileReader, and I’ll start to read it with the readAsDataURL. After this, I’m setting an event handler once the operation will be successfully completed. In that case, I want to load the image that I accessed with the input field of type file. Once the image is loaded, I’ll resize the canvas element with the size of the image the user selected, so it will work with any image size as little or as large they will be. I’ll do the same with the hidden img element that I’ll use later to create the modified image. Last, but not least, because the canvas itself have no drawing abilities, we need to get one of his context, in our case, the 2d one, and use some script to do it. By using getContext on the canvas I’m able to draw on it, like drawing the image I built a couple lines before.

Now, I need to let the user draw on the canvas so I defined the startDrawOnCanvas function that I used in the first JavaScript code of this post.

[javascript] //Start the drawing on the canvas function startDrawOnCanvas() { var canvas = document.getElementById(“canvas”); canvas.style.cursor = “pointer”; canvas.addEventListener(“mousemove”, drawOnCanvas);</pre>

updateStatus(“”);

} [/javascript]

There is nothing complicated here: I’m setting a listener on mousemove on the canvas element, so if the user moves his mouse over the canvas when the mouse was down (remember the listener I added at the beginning), the function drawOnCanvas will be fired up. I did the same thing to stop the drawing by removing the same listener when the mouse is up.

[javascript] //Stop the drawing on the canvas

function stopDrawOnCanvas() {

var canvas = document.getElementById(“canvas”);

canvas.style.cursor = “default”;

canvas.removeEventListener(“mousemove”, drawOnCanvas);

} [/javascript]

Now, let’s check one of the biggest function, or I should say, one of the most important one for this challenge, the drawing function.

[javascript] <em id=”__mceDel”> //Draw on the canvas

function drawOnCanvas(e) {

var canvas = document.getElementById(“canvas”);

var rect = canvas.getBoundingClientRect();

War context = canvas. getContext(“2d”);</em>

var posX = e.clientX – rect.left;

var posY = e.clientY – rect.top;

//Black is the default color, but for learning purpose

context.fillStyle = ‘black’;

context.beginPath();

context.arc(posX, posY, 5, 0, Math.PI * 2);

context.closePath();

context.fill();

} [/javascript]

Actually, it seems worse than it is. Firstly, let’s get some data to draw on the canvas. We need to access the left, and top from the canvas, since the position of the mouse will be relative to the left top corner of the browser window. Once we have those positions, we can start to draw by, of course, getting the 2d context again. In my code, I used the fillStyle function to set the color to black, but it’s just for this challenge purpose, as the default color is already black.

The fun begins with the beginPath function. I would have been able to use something simpler like fillRect, but I figured out that if we are going to have only one tool to draw, a circle would be better. To create a circle, we need to create a path by starting it with the begin function, and closing it with the close one. Everything between those two functions will make our path, in that case, an arc. The function takes 5 parameters: the x coordinate where we will start the drawing, the y coordinate where we will start the drawing, the radius of the circle (5 is totally arbitrary, I thought the size was good enough for drawing), the starting angle (in radians – we’ll start at 0), and the end angle (also in radians – Math. PI *2 will make a full circle). In theory, you can also add a last optional parameter to tell the function to draw counter-clockwise, but it changed nothing in our case. At the end, we are calling the fill function to render the path we just created.

The last JavaScript function is the one I used to create the img the user will be able to save.

[javascript] //Saving the image

function saveImage() {

document.getElementById(“imgSave”).disabled = true;

var canvas = document.getElementById(“canvas”);

var htmlImage = document.getElementById(“image”);

htmlImage.src = canvas.toDataURL(“image/png”);

//canvas.parentNode.removeChild(canvas);

htmlImage.style.visibility = “visible”;

canvas.style.visibility = “hidden”;

updateStatus(“The image has been created, you can now right click, and save it”, “green”);

} [/javascript]

In that case, the only important lines are 6, and 7. We are setting the src of our hidden img by creating an image from the canvas element using the toDataUrl function. After this, the user will be able to right-click where he was drawing, and save the file. Another solution would have been to use a trick with window.location.href (see the code below), but it’s not implemented in all browsers yet.

[javascript]

image.replace(“image/png”, “image/octet-stream”);

window.location.href = image;

[/javascript]

For this not so beautiful HTML page, I used very little CSS.

[css] * { margin: 5px; }#image { visibility: hidden; position: absolute; left: 0px; }#imgSave { display: block; }#imgSave[disabled] { opacity: 0.5; }

label, div {

font-weight: bold; } [/css]

Let’s start with the #image one. I’m telling the browser to hide the element when it’s loaded, and set to an absolute position at 0 pixels from the left. It will give me the opportunity to have the image the user will save, and the canvas element one over the other, so when the user will ask to save the image, he won’t have to deal with an element elsewhere in the page. Not critical, but I thought it would give a better experience. As for the rest, nothing complicated here. Oh, maybe you didn’t see a lot of attribute selector, like the [disabled] I used with #imgSave. It just means that if this element have this attribute, this CSS will be rendered.

That’s it, you now have a simple HTML page that lets the users load an image, draw on it, and save the new masterpiece to his computer. So did you do the challenge? If you did, please share your result with us in the comment section. Did you had any problem? Do you find anything that I can improve? Do you have any challenge you would like to see? Did you find this one useful? Share your thoughts, and keep looking at my site for the next one.

P.-S.: It’s a pain in the ass to find a good code plugin for WordPress, so hope you’ll like the one I choose. I replaced the one I had, as this one is better, and it’s the less worse I found!

Share the love

Twitter Facebook LinkedIn Google Plus

Leave a comment

Your email address will not be published. Required fields are marked *