Перевод статьи. Англоязычный оригинал опубликован на SitePoint – "Introducing GraphicsJS, a Powerful Lightweight Graphics Library".
HTML5 – основа основ современного веба. И сегодня, когда встает задача создать интерактивную графику, выбор чаще всего падает на такие технологии, как SVG и Canvas. Flash позабыт, Silverlight – редкая птица, обитающая на задворках веба, и почти никто не помнит сторонние ActiveX и Java-плагины.
Плюсы и минусы SVG и Canvas хорошо известны – в целом все сводится к тому, что для создания интерактивных элементов и работы с ними больше подходит SVG. Это векторный формат, основанный на XML, и, когда изображение загружается на страницу с использованием тега
<svg>
, каждый его внутренний элемент становится доступен в SVG DOM.В данной статье я хочу рассказать о GraphicsJS. Это мощная графическая JavaScript-библиотека с открытым исходным кодом, основанная на технологии SVG (VML для старых версий IE). Начну с краткого введения в основы GraphicsJS, а затем проиллюстрирую возможности библиотеки двумя небольшими, но наглядными примерами. Первый из них посвящен изобразительному искусству. Второй покажет, как менее чем за 50 строк кода сделать простую арт-игру в жанре таймкиллера.
Почему GraphicsJS
Библиотек, облегчающих работу с SVG, довольно много: в число лучших входят Raphael, Snap.svg и BonsaiJS. У каждой есть сильные и слабые стороны, однако их детальное сравнение будет темой одной из следующих публикаций. Данная же статья – исключительно о GraphicsJS, и сейчас я попытаюсь объяснить, чем эта библиотека хороша и выделяется среди прочих.
Во-первых, GraphicsJS весит совсем немного и обладает очень гибким JavaScript API. Она предоставляет богатые возможности для форматирования текста, а также виртуальный DOM – независимый от специфики HTML DOM в разных браузерах.
Во-вторых, код этой библиотеки был открыт только прошлой осенью. Компания AnyChart, один из лидеров в разработке решений для интерактивной визуализации данных, использует GraphicsJS как графический движок в своих коммерческих продуктах уже порядка трех лет (с момента выхода AnyChart 7.0), так что эта библиотека проверена в боевых условиях. (Дисклеймер: я руководитель R&D в AnyChart и ведущий разработчик GraphicsJS.)
В-третьих, в отличие от других продуктов AnyChart – JavaScript-библиотек для построения графиков – GraphicsJS бесплатна для использования как в коммерческих, так и в некоммерческих целях. Библиотека доступна на GitHub под лицензией Apache.
В-четвертых, GraphicsJS обладает кросс-браузерной совместимостью, включая поддержку Internet Explorer 6.0+, Safari 3.0+, Firefox 3.0+, Opera 9.5+. В старых версиях IE библиотека использует VML, во всех остальных браузерах – SVG.
Наконец, GraphicsJS позволяет эффективно комбинировать графику и анимацию. Посмотрите главную галерею библиотеки: здесь есть такие примеры, как пылающий костер, вращающаяся галактика, дождь, процедурно генерируемые листья, головоломка «Пятнашки» и так далее. В развернутой документации и детальном описании API можно найти еще больше примеров использования GraphicsJS.
Основы GraphicsJS
Для начала работы с GraphicsJS нужно подключить саму библиотеку и создать блочный элемент HTML-кода для будущего рисунка:
<html lang="en">
<head>
<meta charset="utf-8" />
<title>GraphicsJS Basic Example</title>
</head>
<body>
<div id="stage-container" style="width: 400px; height: 375px;"></div>
<script src="https://cdn.anychart.com/js/latest/graphics.min.js"></script>
<script>
// Здесь код GraphicsJS
</script>
</body>
</html>
Затем надо построить рабочую область и что-нибудь в ней нарисовать, например прямоугольник, круг или какую-то другую фигуру:
// создаем рабочую область
var stage = acgraph.create('stage-container');
// рисуем прямоугольник
var stage.rect(25, 50, 350, 300);
Вот пример на CodePen, в котором мы идем чуть дальше и рисуем знак Даров Смерти.
Наш первый шедевр
Контур, заливка цветом и узором
Любую фигуру или линию можно раскрашивать, используя настройки заливки и контура. Контур (граница) есть у всего, но заливка доступна только для фигур и замкнутых линий.
GraphicsJS предлагает многочисленные настройки этих параметров, вплоть до линейного и радиального градиента, причем и в заливке, и в контуре. Линии могут быть пунктирными, а также поддерживается заливка изображением с несколькими режимами замощения. Однако такой набор функций можно найти практически в любой библиотеке. Что отличает GraphicsJS – так это опция заливки штриховкой и узором, которая позволяет не только выбирать из тридцати двух (!) готовых вариантов заполнения, но и легко создавать собственные паттерны из фигур или текста.
А теперь посмотрим, что конкретно можно сделать. Я нарисую небольшую картинку (человечек, стоящий возле дома) и затем доработаю ее с помощью заливки цветом и узорами. Пусть для простоты это будет изображение в стиле наивного искусства (постараюсь не докатиться до ар брют). Поехали:
// создаем рабочую область
var stage = acgraph.create('stage-container');
// рисуем рамку
var frame = stage.rect(25, 50, 350, 300);
// рисуем дом
var walls = stage.rect(50, 250, 200, 100);
var roof = stage.path()
.moveTo(50, 250)
.lineTo(150, 180)
.lineTo(250, 250)
.close();
// рисуем человечка
var head = stage.circle(330, 280, 10);
var neck = stage.path().moveTo(330, 290).lineTo(330, 300);
var kilt = stage.triangleUp(330, 320, 20);
var rightLeg = stage.path().moveTo(320, 330).lineTo(320, 340);
var leftLeg = stage.path().moveTo(340, 330).lineTo(340, 340);
Результат можно посмотреть на CodePen.
Как видите, здесь мы используем переменные: все методы, которые что-либо рисуют в рабочей области, возвращают ссылку на созданный объект, которую можно использовать для его изменения или удаления.
Кроме того, в GraphicsJS можно активно применять цепные вызовы (например,
stage.path().moveTo(320, 330).lineTo(320, 340);
), что позволяет сократить код. Использовать эту возможность нужно аккуратно, но при правильном подходе цепной вызов действительно делает код более компактным и легко читаемым.Теперь можно дать получившуюся у нас картинку какому-нибудь ребенку и попросить раскрасить ее – ведь даже ребенок способен освоить следующий прием:
// раскрашиваем картинку
// элегантная рамка
frame.stroke(["red", "green", "blue"], 2, "2 2 2");
// кирпичные стены
walls.fill(acgraph.hatchFill('horizontalbrick'));
// соломенная крыша
roof.fill("#e4d96f");
// клетчатый килт
kilt.fill(acgraph.hatchFill('plaid'));
Теперь наш пример выглядит вот так.
Сейчас на картинке изображен горец в килте. Он стоит возле своего кирпичного замка с соломенной крышей. Можно даже рискнуть и действительно назвать нашу картинку произведением искусства, авторские права на которое мы хотим защитить. Что ж, проделаем это с помощью оригинальной заливки текстовым паттерном, которую мы сами и настроим:
// 169 - символьный код значка копирайта
var text = acgraph.text().text(String.fromCharCode(169)).opacity(0.2);
var pattern_font = stage.pattern(text.getBounds());
pattern_font.addChild(text);
// заполняем паттерном все изображение
frame.fill(pattern_font);
Как видите, это очень просто: нужно создать экземпляр текстового объекта, затем построить паттерн в рабочей области и, наконец, вставить текст в паттерн.
Рисуем арт-игру (таймкиллер) менее чем за 50 строк кода
В следующей части статьи я покажу, как с помощью GraphicsJS нарисовать игру-кликер типа Cookie Clicker меньше чем за 50 строк кода.
Название этой игры – «Дворник на ветру». В ней пользователь выступает в роли дворника, который подметает улицу ветреным осенним днем. Здесь частично используется код примера с процедурно генерируемыми листьями из галереи GraphicsJS.
Финальный вариант игры можно посмотреть на CodePen (или в конце этой статьи).
Слои, zIndex, виртуальный DOM
Начнем с создания рабочей области (как в предыдущем примере). И затем объявим несколько исходных переменных:
// создаем рабочую область
var stage = acgraph.create("stage-container");
// цветовые палитры для листьев
var palette_fill = ['#5f8c3f', '#cb9226', '#515523', '#f2ad33', '#8b0f01'];
var palette_stroke = ['#43622c', '#8e661b', '#393b19', '#a97924', '#610b01'];
// счетчик
var leavesCounter = 0;
Для создания игры нам пригодится возможность работы со слоем – объектом, отвечающим за группировку элементов в GraphicsJS. Элементы должны быть сгруппированы, чтобы к ним было удобно применять одинаковые изменения, например трансформации. Слои можно модифицировать в режиме приостановки (о нем расскажу чуть ниже): это улучшает производительность и впечатления от использования.
В данном примере функциональность слоя помогает сгруппировать листья и не дать им закрыть собой надпись, сообщающую, сколько листьев мы вымели. Для этого сначала создадим надпись, а затем с помощью метода
stage.layer
создадим слой для всей рабочей области и назначим последнему более низкий zIndex
, чем у надписи.// создаем надпись для отображения счетчика
var counterLabel = stage.text(10,10, "Swiped: 0", {fontSize: 20});
// слой для листьев
var gameLayer = stage.layer().zIndex(counterLabel.zIndex()-1);
Теперь, сколько бы мы ни создавали листьев в этом слое, они никогда не окажутся поверх текста.
Трансформации
Далее добавим функцию для отрисовки листьев, воспользовавшись удобным API для трансформаций, который позволяет перемещать, масштабировать, поворачивать и обрезать как элементы, так и группы элементов. В связке с другими возможностями, которые предоставляет GraphicsJS, – слоями и виртуальным DOM – это очень мощный инструмент.
function drawLeaf(x, y) {
// выбираем произвольный цвет из палитры
var index = Math.floor(Math.random() * 5);
var fill = palette_fill[index];
var stroke = palette_stroke[index];
// генерируем произвольные масштабирующий коэффициент и угол поворота
var scale = Math.round(Math.random() * 30) / 10 + 1;
var angle = Math.round(Math.random() * 360 * 100) / 100;
// создаем новый путь (лист)
var path = acgraph.path();
// задаем раскраску и рисуем лист
path.fill(fill).stroke(stroke, 1, 'none', 'round', 'round');
var size = 18;
path.moveTo(x, y)
.curveTo(x + size / 2, y - size / 2, x + 3 * size / 4, y + size / 4, x + size, y)
.curveTo(x + 3 * size / 4, y + size / 3, x + size / 3, y + size / 3, x, y);
// применяем произвольные трансформации
path.scale(scale, scale, x, y).rotate(angle, x, y);
return path;
};
Как видите, каждая линия создается одним и тем же образом, но затем трансформируется. В результате получается очень симпатичный случайный узор из листьев.
Работа с событиями
Все объекты, рабочие области и слои в GraphicsJS умеют обрабатывать события. Список всех доступных событий есть в EventType API. При этом рабочие области поддерживают четыре специальных события для контроля рендеринга.
В примере с игрой мы используем слушатели событий, привязанные к каждому объекту (листу), чтобы заставить листья исчезать по одному при наведении на них мыши. Чтобы добиться этого, добавим следующий код в конец функции
drawLeaf
, перед оператором return
:path.listen("mouseover", function(){
path.remove();
counterLabel.text("Swiped: " + leavesCounter++);
if (gameLayer.numChildren() < 200) shakeTree(300);
});
Отсюда также ясно, что для подсчета листьев используется слой.
if (gameLayer.numChildren() < 200) shakeTree(300);
Заметьте, на самом деле мы не храним здесь количество листьев. Листья – это линии, которые добавляются в тот или иной слой или удаляются оттуда, и потому можно отследить, сколько у нас дочерних объектов (а значит, и сколько листьев остается).
Библиотека GraphicsJS дает возможность использовать виртуальный DOM, абстракцию HTML DOM, легкую и независимую от специфики применения SVG/VML в разных браузерах. Эта технология пригодится для реализации целого ряда полезных функций, таких как контроль за всеми объектами и слоями, применение трансформаций к группам и оптимизация рендеринга с помощью методов, которые позволяют отслеживать и контролировать весь его процесс.
Оптимизация производительности
Благодаря виртуальному DOM, а также обработчикам событий пользователи GraphicsJS могут контролировать рендеринг. О том, как эти вещи связаны, можно прочитать в статье "Производительность" из документации библиотеки.
На время, когда в нашей игре генерируются листья, рендеринг нужно приостановить, а затем возобновить его, как только все изменения будут произведены:
function shakeTree(n){
stage.suspend(); // приостанавливаем рендеринг
for (var i = 0; i < n; i++) {
var x = Math.random() * stage.width()/2 + 50;
var y = Math.random() * stage.height()/2 + 50;
gameLayer.addChild(drawLeaf(x, y)); // добавляем лист
}
stage.resume(); // возобновляем рендеринг
}
При таком способе добавления элементов новые листья появляются практически мгновенно.
// первый раз сбрасываем все листья
shakeTree(500);
Ну и наконец, сбрасываем все листья, вызвав метод
shakeTree()
.Конечный результат
Заключение
Переход на HTML5 изменил Веб. Когда дело касается современных веб-приложений или даже простого сайта, мы часто сталкиваемся с задачами, которые требуют манипуляций с графикой. Хотя невозможно найти решение, которое идеально работает в абсолютно любой ситуации, я предложил бы вам обратить внимание на библиотеку GraphicsJS. У нее открытый код и open-source лицензия, она очень функциональна и производительна, а также оснащена отличной браузерной поддержкой и множеством фич. Все это делает GraphicsJS интересным, удобным и, конечно, эффективным решением.
Буду рад получить обратную связь по поводу GraphicsJS в комментариях. Уже используете эту библиотеку? Готовы ли рассмотреть возможность ее применения в новом проекте? Интересно было бы узнать, почему, равно как и почему нет. Кстати, сейчас я работаю над созданием списка лучших графических JavaScript-библиотек и статьей, где они будут сравниваться, – так что предлагаю написать в комментариях, какие библиотеки вы бы хотели там видеть.
Cсылки
- Общая информация
- Библиотеки
Автор оригинальной статьи на SitePoint: Роман Любушкин (Roman Lubushkin).
Поделиться с друзьями
Комментарии (5)
tomgif
22.06.2017 16:55Смотрю и вижу canvas
roman_lubushkin
23.06.2017 11:08Да, API весьма напоминает canvas, если вы про это.
Очень надеемся что в будущем дойдут руки до сanvas рендерера и можно будет одним API работать и с вектором и с растром.
vintage
Я не очень понял, как в AnyChart эффективно обновить данные графиков.
vintage
Разобрался. Нужно создать stage и указать его в качестве контейнера, тогда можно будет применять suspend и resume.
Ребята, а чего у вас всё такое медленное?
http://mol.js.org/app/bench/#bench=chart%2Frope%2F/sort=fill/sample=hcharts~mol~anychart
http://mol.js.org/app/bench/#bench=chart%2Fbar%2F/sort=fill/sample=hcharts~mol~anychart
roman_lubushkin
Да, с производительностью сейчас есть некоторые проблемы, но 80% этих проблем про AnyChart, его внутреннюю архитектуру и перегруженность фичами, а не про Graphics. Мы довольно долго не обращали на эти проблемы внимания, т.к из нашей аналитики, на графиках общего назначения редко бывает больше 200 — 300 точек. А для больших данных у нас есть AnyStock.
Сейчас же игнорировать их уже нельзя, следующий релиз запланирован на сентябрь и будет полностью посвящен размеру JS'ки и производительности.