geektimes

Graphics2D.js — объекты, интерактивность, анимация на canvas… И ничего лишнего

  • четверг, 6 ноября 2014 г. в 02:10:59
http://habrahabr.ru/post/239261/

Доброго new Date().getTimeOfDay();




HTML5 Canvas незаменим, когда нужно что-то динамически нарисовать. Но если мы захотим что-то динамически изменять — нам придётся хранить состояние элементов и перерисовывать при необходимости.
Если мы захотим реагировать на события — нам придётся ловить координаты мыши и определять, находятся ли они внутри нужной фигуры.
И т.д.

Частые повторяющиеся задачи. Так и появляются фреймворки и библиотеки.

Впрочем, случай с Graphics2D.js немного другой: мне просто захотелось порисовать. С объектной моделью, анимацией и событиями. И — ничего лишнего.
Но максимально расширяемо: идей много, и всё можно вынести в плагины.

keyten.github.io/Graphics2D/


(русская версия сайта будет сегодня-завтра)
Итак…

Начинаем


Контекст:
var ctx = Graphics2D.id('mycanvas');

// ну или так:
var ctx = Graphics2D.query('canvas', 1); // второй <canvas>

// или
var ctx = Graphics2D.query( document.getElementById('mycanvas') );


Нарисуем… ну, например, небольшой круг, который будет анимироваться при наведении мыши:
ctx.circle({
	cx : 300,
	cy : 300,
	radius : 50,
	fill : '#f0a'
}).mouseover(function(){
	this.animate({
		scale : 2,
		opacity : 0.5,
	}, 300);
}).mouseout(function(){
	this.animate({
		scale : 0.5,
		opacity : 1
	}, 300);
});

jsfiddle.net/wzemyho6/

А теперь… пусть их будет 100:
for(var i = 0; i < 100; i++){
	ctx.circle({
		cx : Math.floor(Math.random() * 700),
		cy : Math.floor(Math.random() * 400),
		radius : 10,
		fill : 'rgb(' + [Math.floor(Math.random() * 255), Math.floor(Math.random() * 255), Math.floor(Math.random() * 255)].join(',') + ')'
	}).mouseover(function(){
		this.animate({
			radius : 20,
			opacity : 0.5,
		}, 300);
	}).mouseout(function(){
		this.animate({
			radius : 10,
			opacity : 1
		}, 300);
	});
}

jsfiddle.net/9v63govv/4/
Что удивляет — неплохая производительность: анимация начинает ощутимо тормозить начинает при 2-3 тысячах.
UPD. С введением requestAnimationFrame (большое спасибо Gerh) ситуация очень сильно улучшилась

Объекты


Встроенных рисуемых объектов 6: rect, circle, path, image, text и textblock.
Все фигуры наследуются от внутреннего класса Shape, который содержит большинство методов, изменяющих объект (трансформации, анимация, события, заливка, обводка, прозрачность...).
Разница между text и textblock: второй умеет переносить строки (автоматически и вручную через \n), в качестве координат указываются координаты блока, а не надписи.
В объекте path — кривые — квадратичные и кубические Безье, эллиптические… всё, что позволяет canvas. И вдобавок, всё расширяемо: например, один из плагинов добавляет рисование Catmull-Rom.

Любой объект мы можем создавать, указывая параметры по порядку, либо в объекте (последний позволяет дополнительные параметры:

ctx.circle(150, 150, 70, 'red', '2px black'); // fill, stroke
ctx.circle({
	cx : 150,
	cy : 150,
	radius : 70,
	fill : 'red',
	stroke : '5px dot red 0.5 round',

	opacity : 0.5 // а вот дополнительный параметр
});

В любом объекте мы можем указывать заливку и обводку одновременно, причём последняя позволяет сразу несколько параметров.

А вот градиент:

var rect = ctx.rect(100, 100, 200, 200, {
	colors : ['red', 'green', 'blue'],
	from : 'top',
	to : 'bottom'
});

Также можно создать градиент отдельным объектом ctx.gradient и залить им сразу несколько фигур. А потом любое изменение градиента будет отражаться на всех фигурах.
Да и инлайновый градиент тоже экземпляр класса градиентов, например, изменим один из цветов:

rect.fill().color(0, 'yellow');


Пути рисуются одним из трёх вариантов:
ctx.path("M10,10 L200,200 Z", null, "2px blue");
ctx.path([ [10,10], [200,200], [400,100,450,150] ]);
ctx.path([
	{ name : 'moveTo', arguments:[10,10] },
	{ name : 'lineTo', arguments:[200,200] },
	{ name : 'closePath' }
]);


Объекты без заливки и обводки не рисуются, но могут реагировать на события (это нужно включить функцией path.events(true)… так будет через пару дней).

Строковый формат — не SVG, хотя поддерживает полный его синтаксис (пропуск пробелов перед минусов, пропуск повторяющихся функций и т.п.). Поддерживает только функции M, L, C, Q и Z (только абсолютные координаты) — moveTo, lineTo, bezier, quadratic и closePath.

На днях будет плагин, добавляющий полную поддержку SVG-путей :)

Можно обрабатывать отдельные точки путей:
path.point(0).name; // -> moveTo
path.point(0).set('x', 20);

path.before(1,  'L20,20 L30,50');


Нативный контекст


Мы можем создать функцию, рисующую на нативном контексте (например, чтобы оптимизировать какую-то медленную функциональность) и добавить её в перерисовку:

ctx.push({
	draw : function(ctx){
		ctx.fillRect(200, 200, 10, 10);
	}
});


При большом желании можно добавить также обработку событий (просто добавить функцию isPointIn)… Или даже унаследовать от Graphics2D.Shape (получив кучу функций для изменения стилей и трансформации)…
Впрочем, это отдельная тема, о которой я, при желании хабрачитателей, расскажу.

Кроме того, большинство фигур и их методов умеют принимать CSS-значения, например:
ctx.rect("10pt", "10pt", "2em", "2em", "blue")

Это функциональность, в смысле которой я до сих пор не уверен (будет интересно увидеть ваши комментарии на эту тему).

Плагины


Как я уже упоминал, Graphics2D достаточно расширяем, вот некоторые уже существующие плагины:
— Sprite — просто-спрайты и спрайтовая анимация.
— ImageAnim — анимация, когда разные кадры в разных файлах.
— CatmullRom — рисование кривых Catmull-Rom (в рамках объекта Path).
Планируются и другие (расширенная обработка событий, поддержка SVG-путей...) — как я говорил, идей много, и всё пойдёт в плагины.

keyten.github.io/Graphics2D/


(русская версия сайта будет сегодня-завтра)
Github: github.com/keyten/Graphics2D.
Лицензия: MIT / LGPL.

Некоторые демо: Bezier, Gradients, Transformations, Textblock.

На этом всё, интересно услышать ваши отзывы.

И ещё: большое спасибо TheShock-у за немалую помощь.