Pane for drawing in pseudo 3D
Today we are going to go back to the practical lessons for html5. I think we have already done a good break in our lessons. In this tutorial I will show you how to create a pane for drawing which is spinning on its axis (on html5 canvas object). Each of your drawn shape will be spinning in pseudo 3D mode. Two objects (prototypes) are defined here: Point and Shape (each shape consists of multiple points). The main idea – to project and be rotated points of figures, as well as to draw a curved line between these points.
Live Demo
[sociallocker]
download in package
[/sociallocker]
Now we can download the source package, unpack it, and start looking at the code
Step 1. HTML
In the first step we have to prepare some basic html code:
index.html
<!-- add scripts --> <script src="js/pointer.js"></script> <script src="js/main.js"></script> .... <canvas id="scene" height="600" width="800" tabindex="1"></canvas>
As usual, we include scripts in the header section of the document, but, in our case, it doesn’t actually matter where it is exactly. Our JS code will be executed only when the document will be downloaded
Step 2. JS (HTML5)
As usual, in the beginning of every JS code, we can define several global variables:
js/main.js
// Variables var canvas, ctx; // canvas and context objects var vPointer; // draw pointer pbject var aShapes = []; // shapes array var iLMx = iLMy = 0; // last pointer positions var vActShape; // active shape object
Now, we can introduce our first low level object – Point:
// Point object var Point = function (x, y, z) { this.x = x; this.y = y; this.z = z; this.x0 = x; this.z0 = z; this.xp = 0; this.yp = 0; this.zp = 0; this.fov = 2000; } Point.prototype.project = function () { this.zp = this.fov / (this.fov + this.z); this.xp = this.x * this.zp; this.yp = this.y * this.zp; } Point.prototype.rotate = function (ax, ay) { this.x = parseInt(Math.round(this.x0 * ax + this.z0 * ay)); this.z = parseInt(Math.round(this.x0 * -ay + this.z0 * ax)); }
For an object ‘Point’ we have a whole set of internal properties and only two functions: project and rotate. Another object ‘Shape’ is a more complex object:
// Shape object var Shape = function () { this.angle = 0; this.color = '#000'; this.halfheight = canvas.height / 2; this.halfwidth = canvas.width / 2; this.len = 0; this.points = []; return this; } // Add point to shape Shape.prototype.addPoint = function (x, y, z) { this.points.push( new Point(Math.round(x), Math.round(y), Math.round(z)) ); this.len++; } // Rotate shape Shape.prototype.rotate = function () { this.angle += Math.PI / 180; // offset by 1 degree (radians in one degrees) var ax = Math.cos(this.angle); var ay = Math.sin(this.angle); // Rotate all the points of the shape object for (var i = 0; i < this.len; i++) { this.points[i].rotate(ax, ay); } } Shape.prototype.draw = function () { // points projection for (var i = 0; i < this.len; i++) { this.points[i].project(); } // draw a curved line between points var p0 = this.points[0]; ctx.beginPath(); ctx.moveTo(p0.xp + this.halfwidth, p0.yp + this.halfheight); for (var i = 1, pl = this.points.length; i < pl; i++) { var apnt = this.points[i]; var xc = (p0.xp + apnt.xp) / 2; var yc = (p0.yp + apnt.yp) / 2; ctx.quadraticCurveTo(p0.xp + this.halfwidth, p0.yp + this.halfheight, xc + this.halfwidth, yc + this.halfheight); p0 = apnt; } // stroke properties ctx.lineWidth = 8; ctx.strokeStyle = this.color; ctx.lineCap = 'round'; // rounded end caps ctx.stroke(); }
It has less params, but more functions (addPoint, rotate and draw). Now, we can start adding few main scene functions: main scene (canvas) initialization (sceneInit), rendering function (drawScene) and random color generator (getRandomColor):
// Get random color function getRandomColor() { var letters = '0123456789ABCDEF'.split(''); var color = '#'; for (var i = 0; i < 6; i++ ) { color += letters[Math.round(Math.random() * 15)]; } return color; } // Draw main scene function function drawScene() { ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // Clear canvas if (vPointer.bDown) { // on mouse down var iDx = iLMx - vPointer.X; var iDy = iLMy - vPointer.Y; var dif = Math.sqrt(iDx * iDx + iDy * iDy); // difference between two last points if (dif > 5) { if (! vActShape) { aShapes.push( // prepare a new shape object vActShape = new Shape() ); vActShape.color = getRandomColor(); } iLMx = vPointer.X; iLMy = vPointer.Y; vActShape.addPoint(vPointer.X - canvas.width * 0.5, vPointer.Y - canvas.height * 0.5, 0); } } else { // Once mouse is released - cleanup if (vActShape) { vActShape = ''; iLMx = iLMy = 0; } // Rotate the shapes aShapes.forEach(function(sh) { sh.rotate(); }); } // Draw all shapes aShapes.forEach(function(sh) { sh.draw(); }); } // Initialization function sceneInit() { // Prepare canvas and context objects canvas = document.getElementById('scene'); // Canvas resize canvas.width = canvas.clientWidth; canvas.height = window.innerHeight; ctx = canvas.getContext('2d'); // Add two custom shapes var oShape = new Shape(); oShape.addPoint(-200,-200,50); oShape.addPoint(0,0,0); oShape.addPoint(-400,200,0); oShape.addPoint(200,-400,-50); aShapes.push(oShape); var oShape2 = new Shape(); oShape2.addPoint(200,200,-50); oShape2.addPoint(0,0,0); oShape2.addPoint(400,-200,0); oShape2.addPoint(-200,400,50); aShapes.push(oShape2); // Mouse pointer event handler vPointer = new CPointer(canvas); // Main scene loop setInterval(drawScene, 20); } // Window onload init if (window.attachEvent) { window.attachEvent('onload', sceneInit); } else { if (window.onload) { var curronload = window.onload; var newonload = function() { curronload(); sceneInit(); }; window.onload = newonload; } else { window.onload = sceneInit; } }
In sceneInit we added two shapes. As you have already noticed, in order to handle with mouse events we use an instance of CPointer class. Here is it:
js/pointer.js
CPointer = function (canvas) { var self = this; this.body = document.body; this.html = document.documentElement; this.elem = canvas; this.X = 0; this.Y = 0; this.Xi = 0; this.Yi = 0; this.Xr = 0; this.Yr = 0; this.startX = 0; this.startY = 0; this.bDrag = false; this.bMoved = false; this.bDown = false; this.bXi = 0; this.bYi = 0; this.sX = 0; this.sY = 0; this.left = canvas.offsetLeft; this.top = canvas.offsetTop; self.elem.onmousedown = function (e) { self.bDrag = false; self.bMoved = false; self.bDown = true; self.Xr = e.clientX; self.Yr = e.clientY; self.X = self.sX = self.Xr - self.left; self.Y = self.sY = self.Yr - self.top + ((self.html && self.html.scrollTop) || self.body.scrollTop); } self.elem.onmousemove = function(e) { self.Xr = (e.clientX !== undefined ? e.clientX : e.touches[0].clientX); self.Yr = (e.clientY !== undefined ? e.clientY : e.touches[0].clientY); self.X = self.Xr - self.left; self.Y = self.Yr - self.top + ((self.html && self.html.scrollTop) || self.body.scrollTop); if (self.bDown) { self.Xi = self.bXi + (self.X - self.sX); self.Yi = self.bYi - (self.Y - self.sY); } if (Math.abs(self.X - self.sX) > 2 || Math.abs(self.Y - self.sY) > 2) { self.bMoved = true; if (self.bDown) { if (! self.bDrag) { self.startX = self.sX; self.startY = self.sY; self.bDrag = true; } } else { self.sX = self.X; self.sY = self.Y; } } } self.elem.onmouseup = function() { self.bXi = self.Xi; self.bYi = self.Yi; if (! self.bMoved) { self.X = self.sX; self.Y = self.sY; } self.bDrag = false; self.bDown = false; self.bMoved = false; } }
Live Demo
Conclusion
Today, we have built the rotating panel where we can draw (with the mouse) using HTML5. I hope you enjoyed our lesson. Good luck and welcome back.
Hi Andrew,
Wow, another great example of using the HTML5 canvas element. I can’t wait to have a play with this one. Thanks for creating these awesome tutorials, I am thoroughly enjoying every one of them.
Regards,
Damian
I would like to know ho to take the initiate lines out ?? (sorry for my bad english I’m Brazilian)
Hello Hendrik,
The both initial objects are added in the sceneInit, both of them are shapes, as you see, once it is ready (prepared), we add it into the global array: aShapes
You can comment all these lines (from line 62 to line 75)
how I can clear the canvas content, I’ve aredy tryed “ClearRect()” but noting happend ???
The ‘clearRect’ function is to clean the main scene (periodically), you shouldn’t touch it.
this one is very good …. one things can u add in this i.e. the facility to delete the previous drawing??
Hi Ali,
If you read the tutorial carefully, you could see that all objects (drawings) are stored in the aShapes array.