Статьи

Панель для рисования в псевдо 3D

 Сегодня мы вернемся к практическим занятиям по html5. Я думаю, что мы уже сделали хороший перерыв в наших уроках. В этом уроке я покажу вам, как создать панель для рисования, которая вращается вокруг своей оси (на объекте html5 canvas). Каждая из ваших нарисованных фигур будет вращаться в псевдо 3D режиме. Здесь определены два объекта (прототипа): точка и форма (каждая фигура состоит из нескольких точек). Основная идея — проецировать и поворачивать точки фигур, а также проводить изогнутую линию между этими точками.

Live Demo
скачать в упаковке

Теперь мы можем скачать исходный код, распаковать его и начать смотреть на код


Шаг 1. HTML

На первом этапе мы должны подготовить некоторый базовый HTML-код:

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>

Как обычно, мы включаем скрипты в заголовочный раздел документа, но в нашем случае не имеет значения, где именно он находится. Наш JS-код будет выполнен только тогда, когда документ будет загружен

Шаг 2. JS (HTML5)

Как обычно, в начале каждого кода JS мы можем определить несколько глобальных переменных:

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

Теперь мы можем представить наш первый объект низкого уровня — 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));
}

Для объекта «Точка» у нас есть целый набор внутренних свойств и только две функции: проецировать и вращать. Другой объект Shape — это более сложный объект:

// 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();
}

У него меньше параметров, но больше функций (addPoint, rotate и draw). Теперь мы можем начать добавлять несколько основных функций сцены: инициализация главной сцены (canvas) (sceneInit), функция рендеринга (drawScene) и генератор случайных цветов (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;
    }
}

В sceneInit мы добавили две фигуры. Как вы уже заметили, для обработки событий мыши мы используем экземпляр класса CPointer. Вот:

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
скачать в упаковке

Вывод

Сегодня мы создали вращающуюся панель, где мы можем рисовать (с помощью мыши) с использованием HTML5. Я надеюсь, вам понравился наш урок. Удачи и добро пожаловать обратно.