Знакомство с HTML5 Canvas

Появилось желание взяться за изучение «убийцы Flash’а» HTML5, а в частности элемента Canvas. Это достаточно интересная технология, позволяющая создавать различные растровые 2d изображения средствами JavaScript. Просидев несколько часов за изучением технологии и вспоминании JavaScript, я написал небольшое интерактивное «приложение», которое, в первую очередь, предназначено для обучения меня и вас. Это обычные круги и прямоугольники, которые можно двигать мышью. Для ловли событий я использую jQuery, мне он кажется наиболее удобным.

Демо
Исходник

Логика работы

Принцип работы прост: сначала я записываю все создаваемые объекты в общий массив объектов и отрисовываю сцену. Затем в событии, выполняемом при нажатии на сцену, я определяю тип нажатого объекта и добавляю необходимую информацию(тип, номер и смещение курсора относительно начальной точки) о нажатом объекте в массив. После этого описывается событие, которое выполняется при перемещении мыши по сцене, в котором я обновляю координаты перемещаемого объекта в общем массиве объектов. Затем просто перерисовывается вся сцена. Ну и последнее событие, выполняемое при отпускании нажатой кнопки мыши, в котором я просто очищаю массив с информацией о выбранном объекте.

Код

Простенький HTML с созданием canvas и подключением JavaScript кода:

<!DOCTYPE html>
<html> 
<head>
	<title>html5movobj</title>
</head>
<body>
	<canvas id="canvas" width="1000" height="400"> Этот элемент не поддерживается </canvas>
	<script type="text/javascript" src="jquery-1.9.0.min.js"></script>
	<script type="text/javascript" src="scripts.js"></script>
</body> 
</html>

 
Создадим глобальные массивы, в objs будет храниться вся информация об объектах, а в move_select будет храниться информация о перемещаемом объекте:

var objs = {Circle: [], Rectangle: []};
var move_select;

 
Создадим 3 класса: Circle, Rectangle и Draw. Circle, Rectangle — это классы с помощью которых я буду заносить информацию о создаваемых объектах в массив objs. А Draw — это класс, который я создал для более удобного использования функций context:

function Circle(x, y, radius, color) {
	this.x = x;
	this.y = y;
	this.radius = radius;
	this.color = color;
}
 
function Rectangle(x1, y1, x2, y2, color) {
	this.x1 = x1;
	this.y1 = y1;
	this.x2 = x2;
	this.y2 = y2;
	this.color = color;
}
 
function Draw(ctx) {
	this.ctx = ctx;
}

 
Создаём метод класса Draw, для перерисовки сцены:

Draw.prototype.scene = function(canvas) {
	// очистим сцену
	this.ctx.clearRect(0, 0, canvas.width, canvas.height);
 
	// рисуем круги
	for (var i = 0; i < objs.Circle.length; i++) {
		this.ctx.beginPath();
		this.ctx.fillStyle = objs.Circle[i].color;
		this.ctx.arc(objs.Circle[i].x, objs.Circle[i].y, objs.Circle[i].radius, 0, Math.PI * 2, true);
		this.ctx.closePath();
		this.ctx.fill();
	}
 
	// рисуем прямоуголники
	for (var i = 0; i < objs.Rectangle.length; i++) {
		this.ctx.beginPath();
		this.ctx.fillStyle = objs.Rectangle[i].color;
		this.ctx.fillRect(objs.Rectangle[i].x1, objs.Rectangle[i].y1, objs.Rectangle[i].x2, objs.Rectangle[i].y2);
		this.ctx.closePath();
		this.ctx.fill();
	}
}

 
Методы, для работы с общим массивом объектов:

// методы для работы с кругами
Draw.prototype.fillCircle = function(x, y, radius, color) {
	objs.Circle.push(new Circle(x, y, radius, color));
}
 
Draw.prototype.moveCircle = function(id, offset, x, y) {
	objs.Circle[id].x = x + offset[0];
	objs.Circle[id].y = y + offset[1];
}
 
// методы для работы с прямоуголниками
Draw.prototype.fillRect = function(x1, y1, x2, y2, color) {
	objs.Rectangle.push(new Rectangle(x1, y1, x2, y2, color));
}
 
Draw.prototype.moveRect = function(id, offset, x, y) {
	objs.Rectangle[id].x1 = x + offset[0];
	objs.Rectangle[id].y1 = y + offset[1];
}

 
Инициализация canvas‘а, добавление объектов в массив и отрисовка сцены:

$(function(){
	var canvas = document.getElementById('canvas');
	context = canvas.getContext('2d');
	draw = new Draw(context);
 
	// создадим объекты
	draw.fillRect(363, 162, 20, 10, 'rgb(0, 162, 232)');
 
	draw.fillCircle(373, 128, 80, '#456');
	draw.fillCircle(343, 105, 20, '#321');
	draw.fillCircle(405, 109, 20, '#321');
	draw.fillCircle(336, 200, 20, '#321');
	draw.fillCircle(409, 202, 20, '#321');
 
	// нарисуем начальную сцену
	draw.scene(canvas);

 
Опишем действия, выполняемые при нажатии на canvas:

	// действия при нажатии на canvas
	$('#canvas').mousedown(function(e) {
		var canvasPosition = $(this).offset();
		var mouseX = (e.pageX - canvasPosition.left);
		var mouseY = (e.pageY - canvasPosition.top);
 
		for (var i = 0; i < objs.Circle.length; i++) {
			var circleX = objs.Circle[i].x;
			var circleY = objs.Circle[i].y;
			var radius = objs.Circle[i].radius;
 
			// проверка на нажатие в область круга
			if (Math.pow(mouseX - circleX, 2) + Math.pow(mouseY - circleY, 2) < Math.pow(radius, 2)) {
				// запишем информацию о выбранном элементе: тип, id, и смещение указателя мыши
				move_select = [ 'Circle', i, [circleX - mouseX, circleY - mouseY] ];
			}
		}
 
		for (var i = 0; i < objs.Rectangle.length; i++) {
			var rectX1 = objs.Rectangle[i].x1;
			var rectY1 = objs.Rectangle[i].y1;
			var rectX2 = objs.Rectangle[i].x1 + objs.Rectangle[i].x2;
			var rectY2 = objs.Rectangle[i].y1 + objs.Rectangle[i].y2;
 
			// проверка на нажатие в область прямоуголника
			if (mouseX > rectX1 && mouseX < rectX2 && mouseY > rectY1 && mouseY < rectY2) {
				// запишем информацию о выбранном элементе: тип, id, и смещение указателя мыши
				move_select = [ 'Rectangle', i, [rectX1 - mouseX, rectY1 - mouseY] ];
			}
		}
	});

 
Опишем действия, выполняемые при перемещении мыши по сцене:

	// действия при перемещении указателя
	$('#canvas').mousemove(function(e) {
		var canvasPosition = $(this).offset();
		var mouseX = (e.pageX - canvasPosition.left);
		var mouseY = (e.pageY - canvasPosition.top);
 
		if (move_select != undefined) {
			// обновим координаты элемента
			if (move_select[0] == 'Circle') {
				draw.moveCircle(move_select[1], move_select[2], mouseX, mouseY);
			} else if (move_select[0] == 'Rectangle') {
				draw.moveRect(move_select[1], move_select[2], mouseX, mouseY);
			}
 
			// перерисуем сцену
			draw.scene(canvas);
		}
	});

 
Опишем действия, выполняемые при отпускании нажатой кнопки мыши и завершим скрипт:

	// действия при отпуске кнопки нажатия
	$('#canvas').mouseup(function(e) {
		move_select = undefined;
	});
});
Перейти к верхней панели