Доброго 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);
});
А теперь… пусть их будет 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({
scale : 2,
opacity : 0.5,
}, 300);
}).mouseout(function(){
this.animate({
scale : 0.5,
opacity : 1
}, 300);
});
}
jsfiddle.net/9v63govv/
Что удивляет даже меня самого — неплохая производительность: анимация начинает ощутимо тормозить начинает при 2-3 тысячах.
Объекты
Встроенных рисуемых объектов 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-у за немалую помощь.
Автор: Keyten