Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Pane for drawing in pseudo 3D

DZone's Guide to

Pane for drawing in pseudo 3D

· Web Dev Zone
Free Resource

Learn how to build modern digital experience apps with Crafter CMS. Download this eBook now. Brought to you in partnership with Crafter Software

 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
download in package

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
download in package

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.

Crafter is a modern CMS platform for building modern websites and content-rich digital experiences. Download this eBook now. Brought to you in partnership with Crafter Software.

Topics:

Published at DZone with permission of Andrey Prikaznov, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}