Мы начинаем серию статей, в которых вы можете рассказать о своём сервисе или разработке, которые могут быть полезны для фронтенд-разработчиков. Есть о чём рассказать? Пишите нам на wst@web-standards.ru. Редакция.
Рассказывать про актуальность векторной графики уже нет никакого смысла — количеством различных размеров, разрешений (а теперь еще и плотностей) экранов веб-разработчика не удивишь. А вот вопрос подготовки и создания изображений для всего этого разнообразия набирает актуальность с каждым днем. В этой статье я хочу рассказать не о том, как научить дизайнера использовать Illustrator, а о том, как облегчить жизнь разработчика при работе с SVG.
В последнее время SVG начинают использовать не только в качестве замены растра для иконок и логотипов начинает, но и для создания сложной графики и динамических диаграмм. За это нужно сказать спасибо множеству отличных JavaScript-библиотек, которые позволяют реализовывать все фантазии дизайнеров быстро и легко: Raphaël, Snap.svg, SVG.js, а также тех, что визуализируют разнообразные данные в любом вообразимом виде: D3, Highcharts, GRaphaël.
Но я говорил об облегчении жизни разработчиков: в чем она нелегка, если хороших инструментов так много? Проблемы начинаются, когда перед программистами ставится одна из следующих задач:
- отрисовать графики на сервере, чтобы вставить в email-рассылку;
- закэшировать результат, потому что каждая отрисовка очередного сложного изображения «подвешивает» браузер на слабой машине;
- дать пользователю возможность скачать файл в PNG или PDF.
Очевидно, основное преимущество всех этих библиотек становится их самым большим минусом — они работают только в браузере.
Решения этой проблемы уж больно прямолинейные — перетащить браузер на сервер. Тут и решения «headless», например, у highcharts или freckle, или попытки перенести DOM в Node.js для Raphaël.
Это работает, но инфраструктура для столь небольших потребностей получается монструозной, сложной и часто медленной.
Почему же тогда просто не использовать сторонние серверные библиотеки? Ни один программист в мире не захочет поддерживать две реализации одного и того же на двух разных языках. Это ведет к ошибкам, дополнительным сложностям и высокой итоговой стоимости поддержки и разработки новой функциональности.
Мы все это понимаем, поэтому и создали Svable. Это сервис, который позволит вам перенести всю вашу существующую генерацию SVG из браузера на сервер или запускать один и тот же код как на клиенте, так и на сервере.
Как это реализуется? Система состоит из двух частей: самой платформы и адаптеров. Платформа — это API, который принимает на вход специально сформированный JSON с высокоуровневыми командами, вроде: rect
, circle
, getBBox
и т.д. В ответ вы получаете результирующий SVG, PDF или PNG. Мы конвертируем результат, если вам это нужно.
Возьмем для примера вот этот SVG. Как видим, это круг с полупрозрачным stroke
и белым квадратом, который рисуется ровно по центру этого круга. И все это на фоне белого прямоугольника с закругленными краями. Допустим, нам позарез нужно отрисовать подобное на сервере. Для этого потребуется сформировать POST-запрос на наш сервис со следующим JSON:
{
'paper': {
'attrs': [ { 'width': '640' }, { 'height': '480' }],
'access_key': 'my_key',
'format': 'svg',
'children': [
{
'type': 'rect',
'attrs': [
{ 'x': '0' },
{ 'y': '0' },
{ 'width': '640' },
{ 'height': '480' },
{ 'rx': '10' },
{ 'fill': '#fff' }
]},
{
'type': 'circle',
'svable_id': 'main_circle',
'attrs': [
{ 'cx': '320' },
{ 'cy': '240' },
{ 'r': '60' },
{ 'fill': '#223fa3' },
{ 'stroke': '#000000' },
{ 'stroke-width': '80' },
{ 'stroke-opacity': '0.5' }
]},
{
'type': 'rect',
'attrs': [
{ 'x': 'main_circle.cx - 10' },
{ 'y': 'main_circle.cy - 10' },
{ 'fill': '#fff' },
{ 'width': '20' },
{ 'height': '20' }
]}
]}
}
Разберем его по частям: в корневом объекте paper
вы описываете размеры вашего полотна, viewBox
, а также указываете ваш персональный ключ доступа и растровый формат (по умолчанию в ответ вам придет SVG):
'paper': {
'attrs': [{ 'width': '640' }, { 'height': '480' }],
'access_key': 'my_key',
'format': 'svg',
В массиве children
указываются все объекты, которые попадут на ваш холст. Первым из них идет фоновый прямоугольник. Тут все достаточно просто и почти один в один повторяет формат самого SVG-документа:
'type': 'rect',
'attrs': [
{ 'x': '0' },
{ 'y': '0' },
{ 'width': '640' },
{ 'height': '480' },
{ 'rx': '10' },
{ 'fill': '#fff' }
]
Далее опишем круг. Тут все тоже довольно банально, но есть одно отличие — атрибут svable_id
, который позволит вам сослаться конкретно на этот объект в тот момент, когда вам понадобятся любые его параметры:
'type': 'circle',
'svable_id': 'main_circle',
'attrs': [
{ 'cx': '320' },
{ 'cy': '240' },
{ 'r': '60' },
{ 'fill': '#223fa3' },
{ 'stroke': '#000000' },
{ 'stroke-width': '80' },
{ 'stroke-opacity': '0.5' }
]
Затем опишем последний квадрат. Напомним, что он должен позиционироваться относительно центра круга. Тут вам и пригодится то, что вы указали в svable_id
:
'type': 'rect',
'attrs': [
{ 'x': 'main_circle.cx - 10' },
{ 'y': 'main_circle.cy - 10' },
{ 'fill': '#fff' },
{ 'width': '20' },
{ 'height': '20' }
]
Однако обычно никто не рисует SVG на сервере с нуля. Поэтому мы создали адаптеры под популярные JavaScript-библиотеки: уже готов Raphaël, завершаем работу над Snap.svg и D3.
Адаптеры как раз занимаются тем, что преобразовывают код, написанный для браузерных библиотек в JSON, который понимает наша платформа. В итоге вы легко можете запускать свой код там, где вам сейчас это выгодно, лишь вызовите адаптер в нужный момент.
Возьмем уже знакомый нам SVG и отрисуем с помощью Raphaël:
var paper = Raphael(0, 0, 640, 480);
paper
.rect(0, 0, 640, 480, 10)
.attr({
fill: '#fff',
stroke: 'none'
});
var circle = paper
.circle(320, 240, 60)
.attr({
fill: '#223fa3',
stroke: '#000',
'stroke-width': 80,
'stroke-opacity': 0.5
});
paper
.rect(circle.attr('cx') - 10, circle.attr('cy') - 10, 20, 20)
.attr({
fill: '#fff',
stroke: 'none'
});
А теперь перенесем его на сервер и отрисуем с помощью Node.js:
var Svable = require('svable');
var paper = Svable(0, 0, 640, 480, 'raphael');
paper
.rect(0, 0, 640, 480, 10)
.attr({
fill: '#fff',
stroke: 'none'
});
var circle = paper
.circle(320, 240, 60)
.attr({
fill: '#223fa3',
stroke: '#000',
'stroke-width': 80,
'stroke-opacity': 0.5
});
paper
.rect(circle.attr('cx') - 10, circle.attr('cy') - 10, 20, 20)
.attr({
fill: '#fff',
stroke: 'none'
});
console.log(paper.burnSync());
Как видите, вся разница лишь в первоначальном вызове объекта и итоговом получении результата.
В данном случае вызов paper.burnSync()
синхронно вернет нам необходимый XML, с которым вы уже можете поступить как захотите. Любители асинхронности могут воспользоваться методом burn
— он возвращает промис.
Таким образом, вы получаете возможность вставить итоговый файл в почтовую рассылку, отдать пользователю на скачивание, сохранить на сервере, чтобы в следующий раз сэкономить как время пользователя, так и деньги на генерации, и все это без дублирования кода и страданий программистов.
Мы сейчас находимся на финальной стадии разработки, и нам нужны первые клиенты со сложными задачами для пробных интеграций. Мы не только решим их проблемы, но и дадим выгодные условия на время бета-теста. Напишите нам, обсудим конкретно ваш случай: svable.com.