📜 ⬆️ ⬇️

Writing a “draftsman” for iPhone on HTML5 Canvas

Hello, Habrazhiteli!

In this topic, I will show how to create a simple graphical editor for the iPhone. The article is written as clearly as possible, so even a beginner will not be difficult to understand. Moreover, I will tell:


')
More - under the cut



Introduction



I think that everyone was ever interested in developing for the iPhone, and HTML5 Canvas technology, but for various reasons I threw it. In this article I want to show that to make such an application is very easy. For code transparency, I did not use third-party libraries and frameworks.
So, let's begin.

CSS styles

At first I thought about starting with html code, but I realized that in order to fully understand what was happening, you first need to provide styles.

body {
margin: 0;
padding: 0;
background: #fff;
}
/* , */
.tb {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 40px;
overflow: hidden;
border-bottom: 1px solid #CCC;
background-color: orange;
}
canvas {
position: absolute;
top: 40px;
left: 0;
}
/* */
.bt {
overflow: hidden;
float: left;
font-weight: bold;
color: white;
font: 16px Arial;
width: 33%;
padding-top: 10px;
height: 30px;
text-align: center;
}
/* select */
select {
float: left;
width: 33%;
margin-top: 10px;
height: 20px;
}


Html file

To save an adequate size of the topic, I decided not to publish the entire file, but only interesting parts of it.

Meta viewport


 ... <head> <meta name="viewport" content="width=device-width,user-scalable=no" /> </head> ... 


A meta tag with the viewport attribute tells the mobile browser how wide the page will be (width), height (height), whether to allow the user to change the zoom on this page (user-scalable value), what initial zoom to load the page (initial-scale), what may be the minimum (minimum-scale) and maximum (maximum-scale) zoom. The width and height of the current device is stored in the values ​​device-width / device-height.

Make a choice of color / brush size


I thought a lot about how to make a choice of colors, and the simplest thing that occurred to me was to create a select element, with the desired color and size values ​​in the option tags and by pressing the “Color” or “Size” button , just send focus to the select element . It is known, the select tag with the focus on the iPhone works like this.

Focus (method. Focus () ), unfortunately, did not want to send. But I did not give up! And here's what I thought: what if I make a transparent div in which the select elements I need are located. And this div, in turn, impose on my buttons? And what do you think? Everything is working! For clarity, I show how:

I give the hack code:
 <!-- ,    --> <div id="tb" style="z-index: 2"> <div class="bt"></div> <div class="bt"></div> <a class="bt"></a> </div> <!-- ,    --> <div id="htb" style="opacity: 0; z-index: 3"> <select id="hcs"> <option>blue</option> <option>red</option> <option>green</option> <!--      --> </select> <select id="hss"> <option>10</option> <option>11</option> <option>12</option> <!--      --> </select> <a class="bt" id="savebutton" style="z-index: 20;"></a> </div> 

(this thing is called clickjacking, if a transparent frame from another site, for example)


The most interesting



Before downloading a reader with a hundred lines of code, I want to talk about how things should work and why.

Touch events


ontouchstart - triggered when the user has just started moving his finger across the screen (can be compared with onmousedown on a computer).

ontouchmove - triggered when the user moves the finger across the screen (can be compared with onmousemove ).

ontouchend - triggered when the user released his finger from the screen (can be compared with onmouseup ).

There are also ongesturechange , ongestureend from the Gesture API , but they will not be considered in this article.

Each event returns an array of touches, each element of which contains such properties as:
pageX , pageY , clientX , clientY . The number of elements in the touches array depends on the number of fingers that touch the screen.

Therefore, to track the touch screen and get the coordinates can be very simple:
 someElement.ontouchstart = function(e) { console.log("X: " + e.touches[0].pageX + ", Y:" + e.touches[0].pageY); } 


Algorithm

How will everything work? The points

Why is everything so difficult?
The point is that the Bezier curves are used in the drawing function, i.e. the curve (more often - its end) may change if the next control coordinate is in the other side. If you use the usual straight lines, then with fast circular movements, you will not be able to observe beautiful angles.

Why redraw everything on the second canvas

As I said, after each “stroke” the image from the main canvas is cloned onto the canvas assistant. This is done in order not to clog an array of coordinates for the main canvas. Since the more control points in the array, the slower our drawing will be redrawn. For these reasons, I limited the redraw buffer to 1 stroke (if you draw one big stroke, the speed can also decrease).

Code

Code for creating canvases and getting contexts

 var width = window.innerWidth; //  var height = window.innerHeight; //  var hcanv = document.createElement("canvas"); //    var mcanv = document.createElement("canvas"); //    //   mcanv.width = width; mcanv.height = height; hcanv.width = width; hcanv.height = height; //   DOM document.body.appendChild(mcanv); document.body.appendChild(hcanv); hcanv.style.zIndex = 10; //     //  var mctx = mcanv.getContext("2d"); var hctx = hcanv.getContext("2d"); 


Handle color and brush size processing

 var selects = document.getElementsByTagName("select"); //   select for(var i=0; i<selects.length; i++) { //  selects[i].onchange = handleSelects; //       } function handleSelects() { //    var val = this.options[this.selectedIndex].value; switch(this.id) { //  ,     case "hcs": brush.color = val; //   break; case "hss": brush.size = val; //   break; } } 


Drawing code


 var touches = {x:[], y:[]}; //   var brush = {color: "blue", size: 10}; //   var snapshot = ""; //    hcanv.ontouchstart = function(e) { //   //    touches.x.push(e.touches[0].pageX); touches.y.push(e.touches[0].pageY-40); //40  -    hctx.clearRect(0, 0, width, height); //  redraw(hctx); //  () return false; //    - } hcanv.ontouchmove = function(e) { //   //    touches.x.push(e.touches[0].pageX); touches.y.push(e.touches[0].pageY-40); hctx.clearRect(0, 0, width, height); //  redraw(hctx); // () return false; //    - ( etc) } hcanv.ontouchend = function(e) { //     snapshot = hcanv.toDataURL(); //  URL    png var img = new Image(); //    img.src = snapshot; //   url img.onload = function() { //   (     ) mctx.drawImage(img, 0, 0); //     hctx.clearRect(0, 0, width, height); //   } //  touches.x = []; touches.y = []; } //  function redraw(ctx) { ctx.lineCap = "round"; //   ctx.lineJoin = "round"; //  ctx.strokeStyle = brush.color; //  ctx.lineWidth = brush.size; //   ctx.beginPath(); if(touches.x.length < 2) { //,      ctx.moveTo(touches.x[0], touches.y[0]); ctx.lineTo(touches.x[0] + 0.51, touches.y[0]); ctx.stroke(); ctx.closePath(); return; } ctx.moveTo(touches.x[0], touches.y[0]); ctx.lineTo((touches.x[0] + touches.x[1]) * 0.5, (touches.y[0] + touches.y[1]) * 0.5); var i = 0; while(++i < (touches.x.length -1)) { var abs1 = Math.abs(touches.x[i-1] - touches.x[i]) + Math.abs(touches.y[i-1] - touches.y[i]) + Math.abs(touches.x[i] - touches.x[i+1]) + Math.abs(touches.y[i] - touches.y[i+1]); var abs2 = Math.abs(touches.x[i-1] - touches.x[i+1]) + Math.abs(touches.y[i-1] - touches.y[i+1]); if(abs1 > 10 && abs2 > abs1 * 0.8) { //,      ctx.quadraticCurveTo(touches.x[i], touches.y[i], (touches.x[i] + touches.x[i+1]) * 0.5, (touches.y[i] + touches.y[i+1]) * 0.5); continue; } ctx.lineTo(touches.x[i], touches.y[i]); ctx.lineTo((touches.x[i] + touches.x[i+1]) * 0.5, (touches.y[i] + touches.y[i+1]) * 0.5); } ctx.lineTo(touches.x[touches.x.length-1], touches.y[touches.y.length-1]); ctx.moveTo(touches.x[touches.x.length-1], touches.y[touches.y.length-1]); ctx.stroke(); ctx.closePath(); } 


Demo
Source

PS If you have any questions - ask! I will try to answer.

Source: https://habr.com/ru/post/126432/


All Articles