Сущность SVG — J. David Eisenberg

Содержание

О книге и переводе

О книге

«Сущность SVG» открывает двери в мир богатого языка, который позволяет взглянуть на графику в веб с новой стороны. Масштабируемая Векторная Графика (Scalable Vector Graphics) — основанный на XML язык описания графики. Является текстом, и как любой текст, может быть прочитан как человеком, так и поисковым роботом. К тому же SVG не зависит от платформы. Этот язык дает огромную власть дизайнерам и веб разработчикам, которые умеют с ним обращаться. И эта книга помогает научится пользоваться этой властью.

Книга написана для веб разработчиков, которые хотят создавать динамическую графику на своих веб сайтах, получая какие-то данные от пользователя.

Об авторе

David Eisenberg — программист и преподаватель из Сан-Хосе, Калифорния. Он разработал курсы по CSS, Javascript, CGI и XML. Пишет статьи для xml.com и alisapart.com.

Дэвид — это талант в объяснении, обучении и подаче материала. После изучения его материалов вопросов остается очень мало, если таковые вообще остаются.

О переводе

«Сущность SVG» обошла стороной русскоязычные издательства. И судя по всему, к нам она уже не попадет (год издания 2002). Чтобы исправить эту вопиющую несправедливость, эту дискриминацию отечественных веб разработчиков, мы решили своими силами перевести книгу на русский. Спасибо нам )).

Если тебе встретится кривая фраза в переводе, помни, что мы не профессиональные переводчики и нам позволительно немного грешить. А еще лучше напиши, где ошибка, чтобы мы исправили. Перевод будем по ходу дополнять нашими знаниями и примерами с учетом современных веб тенденций и реалий.

О примерах

Помни, что SVG поддерживается не всеми браузерами. Поэтому для просмотров примеров используй те, которые его поддерживают. Сами примеры отображаются в обычном HTML документе. Почему так можно прочитать в статье «Внедрение SVG в HTML».

Начнем

SVG (Scalable Vector Graphics — Масштабируемая Векторная Графика), является приложением к XML, которое дает возможность представлять графическую информацию в компактной, портативной форме. Интерес к графике SVG очень быстро возрастает, а инструменты для создания и просмотра файлов SVG предлагаются большинством компаний. Начнем мы со знакомства с двумя системами компьютерной графики. А так же узнаем как SVG соприкасается с миром графики. В конце главы рассмотрим коротким пример, в котором используется много понятий, на которых мы детально остановимся в последующих главах.

Графические системы

Существуют две основные системы для отображения графической информации на компьютере — растровая и векторная графика.

Растровая графика

В растровой графике картинка представлена как прямоугольная область, состоящая из множества точек, расставленных по своим местам. Такая точка на экране называется пиксель.

прямоугольник в растровой графике
Прямоугольник в растровой графике

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

Векторная графика

В системе векторной графики изображение описывается как серия геометрических форм. Вместо того, чтобы получить набор пикселей, векторная программа получает команды и рисует образ по определенному набору координат.

прямоугольник в векторной графике
Прямоугольник в векторной графике

Если Вы планируете сделать рисунок на клетчатой бумаге, в растровой графике необходимо описать, какая клетка будет заполнена каким цветом. В векторной графике нужно задать начальную, конечную координаты, и выбрать тип линии, которая их соединит. Некоторые описывают векторную графику как набор инструкций для черчения, в то время как растровая графика — это точки разного цвета в определенных местах.

Векторная графика «понимает», чем она является: квадрат «знает», что он квадрат, а текст «в курсе», что он текст. Это потому, что в векторной графике изображение представлено в виде объектов, а не набора пикселей. Векторные объекты могут менять свои цвет и форму, в то время, как растровое изображение — нет. В векторном изображении легко найти текст, независимо от его цвета, шрифта и положения.

Еще способ понять разницу между растровой векторной графикой, если представить растровую графику как рисунок на холсте, а векторную — в виде линий и форм, сделанных из тянущегося материала.

Применение растровой графики

Растровая графика наиболее подходящая для использования в фотографиях, которые крайне редко состоят из отдельных линий или кривых. Сканированные изображения часто сохраняются как растровый рисунок даже если изначально были сделаны как векторный рисунок — мы хотим сохранить рисунок как единое целое, не думая об его отдельных элементах. Факс, например, не определяет, что вы нарисовали — он просто передает пиксели из одного места в другое в форме растрового изображения.

Инструменты для создания изображения в растровой форме более распространены и, в общем, проще в использовании, чем инструменты для векторной графики. Существует много способов сжать и сохранить растровое изображение, и все они доступны. Широко доступны библиотеки программ для чтения и создания изображений в сжатых форматах, таких как JPEG, GIF, PNG. Существует некоторые причины, почему веб браузеры (до прихода SVG) поддерживали только растровые изображения.

Применение векторной графики

Векторная графика используется в:

Именно потому, что большинство этих файлов кодируются двоичным форматом или плотно упакованный битовый поток (bitstreams), для пользовательского агента очень сложно разобрать вставленный текст, а серверу сложно динамично создать файл векторного изображения на основании внешних данных. Большинство векторных рисунков являются запатентованными, а коды для их просмотра или создания — недоступны (прим. ред. — так было в 2002 году, сейчас ситуация меняется. Есть целые сайты с бесплатно доступными изображениями в SVG формате).

Масштабируемость

Хотя векторная графика пока еще не так популярна как растровая, она имеет одну особенность, которая делает ее бесценной во многих приложениях — возможность изменять масштаб изображения без потерь в качестве. Для примера, возьмем два изображения кота. Оба рисунка отображаются на экране при разрешении 72 пикселя на дюйм.

кот в растровом изображении
Кот в растровом изображении
Кот в векторном изображении (png картинка результата svg рендеринга)

При увеличении растрового изображения, необходимо как-то растянуть каждый пиксель. Наиболее простой способ — при увеличении в 4 раза, увеличить каждый пиксель в 4 раза. Результат, как видно ниже, не очень лицеприятный:

результат увеличения растрового изображения
Результат увеличения растрового изображения

Чтобы исправить ситуацию можно попробовать такие техники как «выделение контуров» и «подавление помех». Но эти техники трудоемки и, в случае автоматической реализации, ресурсоемки. Более того, так как все пиксели в рисунке безымянны, нет гарантии, что алгоритм сможет правильно определить края рисунка.

результат увеличения растрового изображения с подавлением помех
Результат увеличения растрового изображения с подавлением помех

Чтобы увеличить векторное изображение в четыре раза, просто даем программе команду увеличить все координаты на 4 и перерисовать рисунок. Результатом будет изображение, у которого закругления и сглаженные углы отображены с ощутимо меньшим эффектом ступенек:

Кот Фигура головы кота Cat
Результат увеличения векторного изображения

Создание SVG графики

В этом разделе мы создадим SVG файл, который отобразит картинку кота, которую вы видели ранее. Этот пример поможет представить о чем пойдет речь детально в последующих главах. Данный файл будет служить хорошим примером того, как писать SVG файлы.

Структура документа

Первой строкой будет XML-декларация, как и у любого другого XML-документа. Затем последует определение DOCTYPE. Корневой элемент <svg> определяет ширину (width) и высоту (height) рисунка в пикселях. Содержание элемента <title> пользователь увидит в строке заголовка браузера или в качестве всплывающей подсказки при наведении курсора мыши на рисунок. Элемент <desc> позволяет вам сделать полное описание рисунка (прим. ред. — это описание смогут прочитать поисковые роботы и альтернативные (невизуальные) пользовательские агенты).

<?xml version="1.0"?>
<!DOCTYPE svg>
<svg xmlns="http://www.w3.org/2000/svg" width="140" height="170">
<title>Кот</title>
<desc>Фигура головы кота</desc>
<!-- рисовать начнем тут -->
</svg>

Основные фигуры

Мы рисуем морду кота добавляя элемент <circle>. Атрибуты элемента точно определяют центр круга (х и y координаты) и радиус. Точка (0.0) является верхним левым углом рисунка. Координаты х растут по мере того, как Вы двигаетесь горизонтально вправо, координаты у растут по мере того, как Вы двигаетесь вертикально вниз.

система координат

Расположение круга и его размер — это часть структуры рисунка. Цвет, которым нарисован рисунок — часть его представления. Так как мы знакомы с XML приложениями, нам хочется разделить структуру и представление для максимальной гибкости. Информация представления содержится в атрибуте style. Его значение будет представлять собой серию пар «свойство: значение» представления. Для контура используем черный цвет, а цвет заливки устанавливаем в none, чтобы сделать морду прозрачной.

<?xml version="1.0"?>
<!DOCTYPE svg>
<svg xmlns="http://www.w3.org/2000/svg" width="140" height="170">
<title>Кот</title>
<desc>Фигура головы кота</desc>
<circle cx="70" cy="95" r="50" style="stroke: black; fill: none" />
</svg>
Шаг 1 — основная фигура в нашем примере — круг

Стили как атрибуты

Нарисуем еще два круга для глаз. Хотя цвета их заливки и контура являются частью презентации (оформления), SVG позволяет Вам определить их в качестве индивидуальных атрибутов. В данном примере мы задаем цвета заливки и контура как два отдельных атрибута, без использования атрибута style. Таким вариантом, конечно, лучше не злоупотреблять, а в идеале вообще не использовать, но об этом в последующих главах. Тут такой вариант используется только в качестве демонстрации, что SVG и так умеет.

Заметка

Для сокращения кода в примере, выкинем XML и DOCTYPE декларации.

<svg width="140" height="170">
<title>Кот</title>
<desc>Фигура головы кота</desc>

<circle cx="70" cy="95" r="50" style="stroke: black; fill: none" />
<circle cx="55" cy="80" r="5" stroke="black" fill="#339933" />
<circle cx="85" cy="80" r="5" stroke="black" fill="#339933" />
</svg>
Шаг 2 — имеем контур морды и глаза

Группируем графические объекты

Теперь добавляем нашему коту усы с правой стороны при помощи двух элементов <line>. Мы будем работать с этими усами как с единым элементом (сейчас вы поймете почему). Итак, мы заключаем их в группирующий элемент <g> и задаем ему идентификатор (id). Линия определяется координатами х и у начальной точки (атрибуты х1 и у1) и конечной (х2 и у2).

<svg width="140" height="170">
<title>Кот</title>
<desc>Фигура головы кота</desc>

<circle cx="70" cy="95" r="50" style="stroke: black; fill: none" />
<circle cx="55" cy="80" r="5" stroke="black" fill="#339933" />
<circle cx="85" cy="80" r="5" stroke="black" fill="#339933" />

<g id="whiskers">
  <line x1="75" y1="95" x2="135" y2="85" style="stroke: black;" />
  <line x1="75" y1="95" x2="135" y2="105" style="stroke: black;" />
</g>
</svg>
Шаг 3 — рисуем усы

Трансформируем систему координат

Сейчас мы применим <use> к группе усов справа и трансформируем ее в усы с левой стороны. Для этого сначала меняем систему координат умножая x-координаты на -1 в значении scale атрибута transform. Это значит, что точка с координатами (75,95) получит координаты (-75, 95). В новой системе координаты возрастают влево. В итоге, чтобы «клонированные» усы оказались на нужном месте, нам необходимо перенести (translate) систему координат на 140 пикселей (ширина всего нашего SVG-холста) вправо, в отрицательном направлении.

<svg width="140" height="170">
<title>Кот</title>
<desc>Фигура головы кота</desc>

<circle cx="70" cy="95" r="50" style="stroke: black; fill: none" />
<circle cx="55" cy="80" r="5" stroke="black" fill="#339933" />
<circle cx="85" cy="80" r="5" stroke="black" fill="#339933" />

<g id="whiskers">
  <line x1="75" y1="95" x2="135" y2="85" style="stroke: black;" />
  <line x1="75" y1="95" x2="135" y2="105" style="stroke: black;" />
</g>
<use xlink:href="#whiskers" transform="scale(-1 1) translate(-140 0)" />
</svg>
Шаг 4 — усы с обоих сторон

Значения атрибута transoform представляют собой список трансформаций, которые следуют друг за другом и разделяются пробелом.

Другие базовые фигуры

Тут рисуем уши и рот при помощи элемента <polyline> (ломанная линия), который использует пары значений координат х и у для указания точек линии. Для перечисления этих точек используется атрибут points: пары значений точек линии между собой разделяются запятыми.

<svg width="140" height="170">
<title>Кот</title>
<desc>Фигура головы кота</desc>

<circle cx="70" cy="95" r="50" style="stroke: black; fill: none" />
<circle cx="55" cy="80" r="5" stroke="black" fill="#339933" />
<circle cx="85" cy="80" r="5" stroke="black" fill="#339933" />

<g id="whiskers">
  <line x1="75" y1="95" x2="135" y2="85" style="stroke: black;" />
  <line x1="75" y1="95" x2="135" y2="105" style="stroke: black;" />
</g>
<use xlink:href="#whiskers" transform="scale(-1 1) translate(-140 0)" />
<!— уши -->
<polyline points="108 62, 90 10, 70 45, 50, 10, 32, 62" style="stroke: black; fill: none;" />
<!-- рот -->
<polyline points="35 110, 45 120, 95 120, 105, 110" style="stroke: black; fill: none;" />
</svg>
Шаг 5 — уши и рот

Маршруты

Все основные фигуры — это всего лишь сокращения более общего элемента <path>. Сейчас мы им воспользуемся чтобы добавить нос коту. <path> определяет последовательность линий и кривых (маршрут), в наиболее компактной форме.

<svg width="140" height="170">
<title>Кот</title>
<desc>Фигура головы кота</desc>

<circle cx="70" cy="95" r="50" style="stroke: black; fill: none" />
<circle cx="55" cy="80" r="5" stroke="black" fill="#339933" />
<circle cx="85" cy="80" r="5" stroke="black" fill="#339933" />

<g id="whiskers">
  <line x1="75" y1="95" x2="135" y2="85" style="stroke: black;" />
  <line x1="75" y1="95" x2="135" y2="105" style="stroke: black;" />
</g>
<use xlink:href="#whiskers" transform="scale(-1 1) translate(-140 0)" />
<!— уши -->
<polyline points="108 62, 90 10, 70 45, 50, 10, 32, 62" style="stroke: black; fill: none;" />
<!-- рот -->
<polyline points="35 110, 45 120, 95 120, 105, 110" style="stroke: black; fill: none;" />
<!-- нос -->
<path d="M 75 90 L 65 90 A 5 10 0 0 0 75 90" style="stroke: black; fill: #ffcccc" />
</svg>

Маршрут для носа можно прочитать так: перейти к координатам (75,90), нарисовать линию к координатам (65,90), нарисовать эллиптическую дугу с радиусом по х = 5 и радиусом по у = 10, и закончить снова в точке с координатами (75,90).

Шаг 6 — нос

Текст

И наконец, так как этот кот нарисован достаточно «криво», есть вероятность, что не все догадаются, что это — кот. Для таких добавим подпись текстом. В этом нам поможет элемент <text>. Атрибуты х и у определяют положение текста. В атрибуте style можно задать имя используемого шрифта, его размер, цвет и прочее свойства отображения. В отличии от других элементов, которые нам встретились в этом примере, <text> является элементом-контейнером. Его содержимое — это текст, который мы хотим показать.

<svg width="140" height="170">
<title>Кот</title>
<desc>Фигура головы кота</desc>

<circle cx="70" cy="95" r="50" style="stroke: black; fill: none" />
<circle cx="55" cy="80" r="5" stroke="black" fill="#339933" />
<circle cx="85" cy="80" r="5" stroke="black" fill="#339933" />

<g id="whiskers">
  <line x1="75" y1="95" x2="135" y2="85" style="stroke: black;" />
  <line x1="75" y1="95" x2="135" y2="105" style="stroke: black;" />
</g>
<use xlink:href="#whiskers" transform="scale(-1 1) translate(-140 0)" />
<!— уши -->
<polyline points="108 62, 90 10, 70 45, 50, 10, 32, 62" style="stroke: black; fill: none;" />
<!-- рот -->
<polyline points="35 110, 45 120, 95 120, 105, 110" style="stroke: black; fill: none;" />
<!-- нос -->
<path d="M 75 90 L 65 90 A 5 10 0 0 0 75 90" style="stroke: black; fill: #ffcccc" />
<text x="55" y="165" style="font-family: sans-serif; font-size: 14pt; stroke: none; fill: black;">Cat</text>
</svg>
Шаг 7 — подпись и финиш

Посмотреть результат в живую.

Этим мы заканчиваем наш краткий обзор SVG. В последующих главах мы исследуем его (SVG) значительно глубже.

Координаты

Мир SVG — это бесконечный холст. В этой главе мы обсудим как дать понять браузеру какая именно часть этого холста Вам интересна, каковы размеры этой части и как найти точки на этом участке.

Область просмотра

Участок документа, который будет использоваться для отображения SVG-документа, называется областью просмотра. Его размер можно задать с помощью атрибутов width и height элемента <svg>.

область просмотра имеет свои размеры в отличии от холста

Значением данного атрибута может быть просто число, без указания единиц измерения. Такая запись будет соответствовать пикселям. Вы также можете явно задать единицы измерения ширины и высоты:

em размер относительно размера шрифта, 1em обычно равен высоте символа
ex высота буквы x
px пиксели
рt точка (1/72 дюйма)
рс picas (1/6 дюйма)
cm сантиметры
mm миллиметры
in дюймы
<svg width="200" height="150px">
<svg width="200px" height="200px">

Оба варианта определяют область в 200 пикселей шириной и 150 пикселей высотой.

<svg width="2cm" height="3cm">

Область шириной в 2 сантиметра и высотой 3 сантиметра.

<svg width="2cm" height="36pt">

Можно, хотя это необычно, смешивать единицы измерения. в этом примере определяем участок шириной 2 сантиметра и 36 точек высотой.

Если у Вас один элемент <svg> вложен в другой <svg> элемент, размеры вложенного можно задать в процентах, относительно размеров родителя. Пример вложения увидим в последующих главах. (прим. ред. — вообще <svg> может быть вложен не только в <svg> элемент: в HTML5 можно напрямую вставлять SVG-документ в HTML-документе. Поэтому более справедливым будет сказать, что размеры в процентах могут быть относительно родителя, которым может оказаться любой HTML-контейнер).

Использование системы координат по умолчанию

По умолчанию для элемента <svg>, просмотрщик устанавливает систему координат с двумя осями: X и Y. Значение по оси X (х-координата), увеличивается, если Вы двигаетесь вправо. Для оси Y (у-координата), увеличивается, если двигаетесь вниз. Верхний левый угол графического окна имеет х- и у-координаты равные нулю (*). Эту точку, (0, 0) еще называют началом.

система координат по умолчанию

Система координат является чисто геометрической системой. Точки не имеют ни ширины, ни длины, а линии координатной сетки считаются бесконечно тонкими. Мы еще вернемся к данному вопросу в последующих главах.

* — в данной книге мы пишем координаты в скобках парами: х- и у-координата, где х-координата стоит первой. Т.е., (10, 30) — это 10 по оси X и 30 по — Y.

Определим область просмотра в 200 пикселей шириной и 200 пикселей высотой. После чего нарисуем прямоугольник, верхний левый край которого имеет координаты (10, 10). Ширина прямоугольника будет 50 пикселей, а высота 30.

<svg width="200" height="200">
  <rect x="10" y="10" width="50" height="30" style="stroke: black; fill: none;" />
</svg>
простой прямоугольник
Прямоугольник с использованием координат по умолчанию

Даже если Вы вообще не определили единицы измерения в целом, можно их использовать напрямую для SVG элементов:

<svg width="200" height="200">
  <rect x="10" y="10" width="50mm" height="30mm" style="stroke: black; fill: none;" />
</svg>

Если размеры области просмотра заданы в явных единицах измерения, это никак не влияет на остальные элементы. В следующем примере задаем область просмотра с размерами в мм. Прямоугольнику размеры указываем без единиц измерения, при этом его размеры будут пикселях.

<svg width="70mm" height="70mm">
  <rect x="10" y="10" width="50" height="30" style="stroke: black; fill: none;" />
</svg>
размеры области просмотра с единицами измерения, у прямоугольника - нет
Размеры области просмотра с единицами измерения, у прямоугольника — нет

Определяем пользовательскую систему координат

В приведенных примерах выше, цифры без единиц измерения считались пикселями. Но это не всегда то, что Вам нужно. Например, Вам хочется установить систему, где бы каждая координата представляла бы 1/16 см (это не образец хорошего дизайна — наследовать не нужно :) ). В данной системе, каждая грань квадрата 40 на 40 будет равна 2.5 см.

Для решения таких задач служит атрибут viewBox элемента <svg>. Значение этого атрибута состоит из четырех цифр, которые задают минимальные значения х- и у-координат, а так же пределы значений по осям.

Итак, для того, чтобы установить систему координат в 16-единиц-на-1-сантиметр для области рисования с размерами 4 сантиметра на 5, используем следующий код:

<svg width="4cm" height="5cm" viewBox="0 0 64 80">

В следующем примере нарисуем домик, котрый будет отображен в пересчитанной системе координат:

<svg width="4cm" height="5cm" viewBox="0 0 64 80">
  <rect x="10" y="35" width="40" height="40" style="stroke: black; fill: none;" />
  <!-- крыша -->
  <polyline points="10 35, 30 7.68, 50 35" style="stroke:black; fill: none;" />
  <!-- двери -->
  <polyline points="30 75, 30 55, 40 55, 40 75" style="stroke:black; fill: none;" />
</svg>

Сетка координат и более темные цифры показывают новую систему координат пользователя. Более светлые цифры расположены с интервалом в 1 сантиметр.

новая система координат пользователя
Новая система координат пользователя

Цифры в значении атрибута viewBox, могут быть разделены запятыми или пробелами. Если ширина или длина равна нулю, графика не будет отображаться. Это является ошибкой при определении отрицательного значения для ширины или высоты viewBox.

Сохранение пропорций

В предыдущем примере, пропорции или соотношение ширины и высоты графического окна и viewBox были идентичными (4/5=64/80). Что же происходит, если пропорции области просмотра и viewBox будут разными? Например, где пропорциональность viewBox равна 1/1, а у области просмотра — 1/3?

<svg width="45px" height="135px" viewBox="0 0 90 90">

SVG в такой ситуации может пойти тремя путями:

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

Во втором случае, так как изображение будет больше, чем область просмотра по одной из осей, Вам будет необходимо определить, какая часть будет обрезана. В примере, картинка будет равномерно масштабироваться пока значения и ширины, и высоты картинки не будут меньше 135 пикселей. В итоге высота картинки идеально совпадает с высотой области просмотра. Но Вам необходимо решить какую невмещающуюся часть картинки обрезать, чтобы она поместилась область просмотра шириной 45 пикселей: правый край, левый или оба.

Задаем выравнивание с preserveAspectRatio

Атрибут preserveAspectRatio позволяет определить выравнивание отмасштабированного изображения относительно области просмотра. Так же может сделать так чтобы изображение полностью соответствовало области просмотра или были обрезаны части изображения.

preserveAspectRatio="alignment [meet | slice]"

где alignment определяет ось и выравнивание. Принимает одно из значений: xMinYMin, xMinYMid, xMinYMax, xMidYMin, xMidYMid, xMidYMax, xMaxYMin, xMaxYMid или xMaxYMax. Эти значения приводят к одному из способов выравнивания, которые расписаны в таблице ниже.

Заметка

y-выравнивание начинается с заглавной буквы для удобство восприятия, т.к. x- и y- выравнивания пишутся одним словом.

Выравнивание по оси X
значениедействие
xMin выровнять в соответствии с минимальным значением x viewBox — левая граница области просмотра.
xMid выровнять в соответствии с центральной точкой по x viewBox — центр по оси X в области просмотра.
xMax выровнять в соответствии с максимальным значением x viewBox — правая граница области просмотра.
Выравнивание по оси Y
значениедействие
YMin выровнять в соответствии с минимальным значением y viewBox — верхняя граница области просмотра.
YMid выровнять в соответствии с центральной точкой по y viewBox — центр по оси Y в области просмотра.
YMax выровнять в соответствии с максимальным значением y viewBox — нижняя граница области просмотра.

Итак, если Вы хотите, чтобы изображение с viewBox= "0 0 90 90" полностью вместилось в область просмотра с width=45 и height=135, располагалось в верхнем левом углу, необходимо написать:

<svg width="45px" height="135px" viewBox="0 0 90 90" preserveAspectRatio="xMinYMin meet">

Заметка

В данном примере изображение получит ширину равную ширине области просмотра. В таком случае визуально получили бы такой же результат даже если задали значения xMidYmin или xMaxYMin. Но для большей корректности следует указывать одинаковые значения (min-min, mid-mid, max-max).

Но все это достаточно абстрактно и туманно. Нужны конкретные примеры, которые разъяснили бы всю эту мутную теорию. Вот несколько таких, которые показывают Вам, как срабатывает выравнивание в комбинации со спецификаторами meet и slice.

Использование спецификатора meet

Спецификатор meet указывает, что изображение должно сохранить свои пропорции и быть полностью видимым. Следующие примеры демонстрируют действие meet вместе с различными вариантами выравнивания:

<!-- высокое окно просмотра -->
<svg preserveAspectRatio="xMinYMin meet" viewBox="0 0 90 90" width="45" height="135">
<svg preserveAspectRatio="xMidYMid meet" viewBox="0 0 90 90" width="45" height="135">
<svg preserveAspectRatio="xMaxYMax meet" viewBox="0 0 90 90" width="45" height="135">
<!-- узкое окно просмотра -->
<svg preserveAspectRatio="xMinYMin meet" viewBox="0 0 90 90" width="135" height="45">
<svg preserveAspectRatio="xMidYMid meet" viewBox="0 0 90 90" width="135" height="45">
<svg preserveAspectRatio="xMaxYMax meet" viewBox="0 0 90 90" width="135" height="45">
Выравнивание в сочетании с meet
Выравнивание в сочетании с meet

Использование спецификатора slice

Спецификатор slice означает, что изображение должно сохранить свои пропорции и будет отмасштабировано в соответствии с максимальным граничным значением области просмотра. Части изображения, которые не помещаются в область просмотра, будут обрезаны.

<!-- высокое окно просмотра -->
<svg preserveAspectRatio="xMinYMin slice" viewBox="0 0 90 90" width="45" height="135">
<svg preserveAspectRatio="xMidYMid slice" viewBox="0 0 90 90" width="45" height="135">
<svg preserveAspectRatio="xMaxYMax slice" viewBox="0 0 90 90" width="45" height="135">
<!-- узкое окно просмотра -->
<svg preserveAspectRatio="xMinYMin slice" viewBox="0 0 90 90" width="135" height="45">
<svg preserveAspectRatio="xMidYMid slice" viewBox="0 0 90 90" width="135" height="45">
<svg preserveAspectRatio="xMaxYMax slice" viewBox="0 0 90 90" width="135" height="45">
выравнивание в сочетании со slice
Выравнивание в сочетании со slice

Использование спецификатора none

Спецификатор none означает, что изображение должно полностью отобразится в области просмотра даже если придется исказить пропорции изображения.

<!-- высокое окно просмотра -->
<svg preserveAspectRatio="none" viewBox="0 0 90 90" width="45" height="135">
<!-- узкое окно просмотра -->
<svg preserveAspectRatio="none" viewBox="0 0 90 90" width="135" height="45">
Выравнивание в сочетании с none
Выравнивание в сочетании с none

Вложенные системы координат

Вы можете создать новую область просмотра со своей системой координат в любое время, вложив еще один элемент <svg> элемент в Ваш SVG-документ. Создается эффект «мини-холста», поверх которого Вы можете рисовать. Мы использовали данную технику для создания этой иллюстрации:

пример использования вложенных систем координат

Вместо того, чтобы рисовать прямоугольники, потом изменять масштаб и положение кота внутри каждого из них (грубый метод), мы предпринимаем следующие действия:

На более простом примере разберем как можно применить вложенные <svg>. Нарисуем две фигуры: круг и прямоугольник на основном холсте. И добавим новый холст, который будет расположен внутри прямоугольника.

пример использования вложенных систем координат

Сначала создаем SVG для основной системы координат и для круга. Обратите, внимание, что пользовательская система координат совпадает с основной:

<svg width="200" height="200" viewBox="0 0 200 200">
  <circle cx="25" cy="25" r="25" style="stroke: black; fill: none;" />
</svg>
круг

Теперь рисуем границы, которыми обозначим, где будет новое окно просмотра:

<svg width="200" height="200" viewBox="0 0 200 200">
  <circle cx="25" cy="25" r="25" style="stroke: black; fill: none;" />
  <rect x="100" y="5" width="30" height="80" style="stroke: blue; fill: none;" />
</svg>
круг и контур для нового холста

Сейчас, добавляем еще один элемент <svg> для новой области просмотра. Вдобавок к определению viewBox, width, height и preserveAspectRatio, Вы можете также определить атрибуты х и у. Это будут координаты положения нового холста относительно родительского <svg> элемента. По умолчанию они равны нулю.

<svg width="200" height="200" viewBox="0 0 200 200">
  <circle cx="25" cy="25" r="25" style="stroke: black; fill: none;" />
  <rect x="100" y="5" width="30" height="80" style="stroke: blue; fill: none;" />
  <svg x="100px" y="5px" width="30px" height="80px" viewBox="0 0 50 50" preserveAspectRatio="xMaxYMax meet">
  </svg>
</svg>

Новая система координат вложенного <svg> позволит добавить круг точно в прямоугольник без изменений кода для круга:

<svg width="200" height="200" viewBox="0 0 200 200">
  <circle cx="25" cy="25" r="25" style="stroke: black; fill: none;" />
  <rect x="100" y="5" width="30" height="80" style="stroke: blue; fill: none;" />
  <svg x="100px" y="5px" width="30px" height="80px" viewBox="0 0 50 50" preserveAspectRatio="xMaxYMax meet">
    <circle cx="25" cy="25" r="25" style="stroke: black; fill: none;"/>
  </svg>
</svg>

Основные фигуры. Линии.

После того, как система координат установлена в <svg> элементе, можно начинать рисовать. В данной главе мы покажем основные фигуры, которые можно использовать для создания большинства элементов в большинстве рисунков: линии, прямоугольники, многоугольники, круги и эллипсы. Начнем с линий.

Линии

SVG позволяет рисовать прямые линии с помощью элемента <line>. Нужно только задать х и у координаты конечных точек линий. Координаты можно указывать без единиц измерения, они будут расцениваться как пиксели, или же можно указывать явно единицы (em, in и т.д.).

<line x1="start-x" y1="start-y" x2="end-x" y2="end-y" />

Нарисуем несколько линий:

<svg width="200px" height="200px" viewBox="0 0 200 200">
  <!-- горизонтальная линия -->
  <line x1="40" y1="20" x2="80" y2="20" style="stroke: black;" />
  <!-- вертикальная линия -->
  <line x1="0.7cm" y1="1cm" x2="0.7cm" y2="2.0cm" style="stroke: black;" />
  <!-- диагональная линия -->
  <line x1="30" y1="30" x2="85" y2="85" style="stroke: black;" />
</svg>
пример линий в SVG

Характеристики кисти

Линии являются следом от кисточки, которая рисует по холсту. Размер, цвет и стиль кисточки являются частью оформления линии — пойдут в атрибут style.

Толщина кисти — stroke-width

В предыдущих главах уже говорилось, что линии холста являются бесконечно тонкими. Тогда как линия или кисточка взаимодействует с линиями координатной сеткой? Ответ — линии координатной сетки совпадают с центром кисти. На следующем примере нарисованы линии, в которых толщина кисти равна десяти пикселям, для того, чтобы сделать эффект более видным:

<svg width="200px" height="200px" viewBox="0 0 200 200">
  <!-- горизонтальная линия -->
  <line x1="40" y1="20" x2="80" y2="20" style="stroke-width: 10; stroke: black;" />
  <!-- вертикальная линия -->
  <line x1="0.7cm" y1="1cm" x2="0.7cm" y2="2.0cm" style="stroke-width: 10; stroke: black;" />
  <!-- диагональная линия -->
  <line x1="30" y1="30" x2="85" y2="85" style="stroke-width: 10; stroke: black;" />
</svg>
линии толщиной равной 10

Цвет кисти

Цвет кисти можно определить несколькими способами:

<svg width="200px" height="200px" viewBox="0 0 200 200">
  <!-- красная -->
  <line x1="10" y1="10" x2="50" y2="10" style="stroke: red; stroke-width: 5;"/>

  <!-- светло зеленая -->
  <line x1="10" y1="20" x2="50" y2="20" style="stroke: #9f9; stroke-width: 5;" />

  <!-- светло голубая -->
  <line x1="10" y1="30" x2="50" y2="30" style="stroke: #9999ff; stroke-width: 5;" />

  <!-- medium orange -->
  <line x1="10" y1="40" x2="50" y2="40" style="stroke: rgb(255, 128, 64); stroke-width: 5;" />

  <!-- пурпурный -->
  <line x1="10" y1="50" x2="50" y2="50" style="stroke: rgb(60%, 20%, 60%); stroke-width: 5;" />
</svg>
линии разных цветов

Прозрачность кисти — stroke-opacity

Вы можете управлять прозрачностью линии свойством stroke-opacity, котрое может принимать значение от 0 до 1: 0 — полностью прозрачна, 1 — полностью непрозрачна. Значение меньше нуля будет изменено на 0, а значение больше единицы будет изменено на 1.

<svg width="200px" height="200px" viewBox="0 0 200 200">
  <line x1="10" y1="10" x2="50" y2="10" style="stroke-opacity: 0.2; stroke: black; stroke-width: 5;" />
  <line x1="10" y1="20" x2="50" y2="20" style="stroke-opacity: 0.4; stroke: black; stroke-width: 5;" />
  <line x1="10" y1="30" x2="50" y2="30" style="stroke-opacity: 0.6; stroke: black; stroke-width: 5;" />
  <line x1="10" y1="40" x2="50" y2="40" style="stroke-opacity: 0.8; stroke: black; stroke-width: 5;" />
  <line x1="10" y1="50" x2="50" y2="50" style="stroke-opacity: 1.0; stroke: black; stroke-width: 5;" />
</svg>
линии с разной прозрачностью

Прерывистость кисти — stroke-dasharray

Если Вам необходима прерывистая линия или линия с точками, используйте свойство stroke-dasharray. Его значение состоит из списка чисел, разделенных запятыми или пробелами, которые определяют длину отрезков и промежутков между ними. Список должен иметь четное количество компонентов. Если задать нечетное, SVG повторит список еще раз, чтобы общее количество компонентов стало четным.

<svg width="200px" height="200px" viewBox="0 0 200 200">
  <!-- 9 пикселей тире, 5 пробел -->
  <line x1="10" y1="10" x2="100" y2="10" style="stroke-dasharray: 9, 5; stroke: black; stroke-width: 2;" />
  <!-- 5 пикселей тире, 3 пробел, 9 тире, 2 пробел -->
  <line x1="10" y1="20" x2="100" y2="20" style="stroke-dasharray: 5, 3, 9, 2; stroke: black; stroke-width: 2;" />
  <!-- нечетное количество цифр. это эквивалентно: 9px тире, 3px пробел, 5px тире, 9px пробел, 3px тире, 5px пробел -->
  <line x1="10" y1="30" x2="100" y2="30" style="stroke-dasharray: 9, 3, 5; stroke: black; stroke-width: 2;" />
</svg>
прерывистые линии

Основные фигуры. Прямоугольники.

Прямоугольник — самая простая из основных фигур, отвечает за нее элемент <rect>. Задаем х- и у-координаты верхнего левого угла прямоугольника, его ширину (width) и высоту (height). Прямоугольник будет залит цветом (свойство fill), который мы укажем, по умолчанию — это черный цвет. Цвет заливки можно задать всеми теми же способами, что и для линии. Если указать значение «none», прямоугольник будет без заливки, то есть прозрачным. Правила указания степени прозрачности заливки прямоугольника (fill-opacity) аналогичны правилам для линии. Оба свойства «fill» и «fill-opacity» являются свойствами представления и описываются внутри атрибута style.

Заметка

Визуально x и y координаты в параметрах прямоугольника могут оказаться не левым верхним углом, если например к фигуре будет применена трансформация.

Контуры прямоугольника рисуются той же кисточкой, что и линии, с теми же самыми свойствами. По умолчанию для кисточки используется значение none (stroke: none) и контур не рисуется. Несколько примеров:

<svg width="200px" height="200px" viewBox="0 0 200 200">
  <!-- черный прямоугольник без контура -->
  <rect x="10" y="10" width="30" height="50" />

  <!-- без заливки с черным контуром -->
  <rect x="50" y="10" width="20" height="40" style="fill: none; stroke: black;" />

  <!-- голубая заливка, полупрозрачный красный контур -->
  <rect x="10" y="70" width="25" height="30" style="fill: #0000ff; stroke: red; stroke-width: 7; stroke-opacity: 0.5;" />

  <!-- полупрозрачная желтая заливка, зеленый контур пунктиром -->
  <rect x="50" y="70" width="35" height="20" style="fill: yellow; fill-opacity: 0.5; stroke: green; stroke-width: 2; stroke-dasharray: 5 2" />
</svg>

Результат в «живую» или смотрим на картинке:

примеры прямоугольников в SVG

Заметка

Контур рисуется таким образом, что его половина находится внутри прямоугольника, а другая половина — вне его. Смотрим на один из прямоугольников в увеличенном виде:

контур прямоугольника расположен на половину в нем и на половину вне его

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

Закругленные прямоугольники

Чтобы нарисовать прямоугольник с закругленными углами, нужно задать радиусы закругления угла по осям x (rx) и y (ry). Максимальное число для rx — половина ширины (width) прямоугольника. Максимальное значение для ry — половина высоты (height). Если задать только одно из rx или ry, тогда неуказанное будет приравнено к указанному.

<svg width="200px" height="200px" viewBox="0 0 200 200">
  <!-- rx and ry equal, increasing -->
  <rect x="10" y="10" width="20" height="40" rx="2" ry="2" style="stroke: black; fill: none;" />
  <rect x="40" y="10" width="20" height="40" rx="5" style="stroke: black; fill: none;" />
  <rect x="70" y="10" width="20" height="40" ry="10" style="stroke: black; fill: none;" />
  <!-- rx and ry unequal -->
  <rect x="10" y="60" width="20" height="40" rx="10" ry="5" style="stroke: black; fill: none;" />
  <rect x="40" y="60" width="20" height="40" rx="5" ry="10" style="stroke: black; fill: none;" />
</svg>

Результат в «живую» или смотрим на картинке:

скругленные прямоугольники в SVG

Основные фигуры. Круги и эллипсы.

Чтобы нарисовать круг, используйте элемент <circle>. Укажите центр круга по осям X и Y атрибутами cx и cy, а так же радиус атрибутом r. По аналогии с прямоугольником, круг по умолчанию заливается черным цветом и не рисуется контур. Чтобы это изменить используются все те же самые свойства, как и для прямоугольника: fill и stroke.

Эллипс рисуется с помощью <ellipse>. В атрибутах указываем координаты центра (cx, cy), а вместо одного параметра для радиуса, теперь их два: по оси X (rx) и по оси Y (ry).

Для круга и для эллипса:

<svg width="200px" height="200px" viewBox="0 0 200 200">
  <circle cx="30" cy="30" r="20" style="stroke: black; fill: none;" />
  <circle cx="80" cy="30" r="20" style="stroke-width: 5; stroke: black; fill: none;" />
  <ellipse cx="30" cy="80" rx="10" ry="20" style="stroke: black; fill: none;" />
  <ellipse cx="80" cy="80" rx="20" ry="10" style="stroke: black; fill: none;" />
</svg>

Результат в «живую» или смотрим на картинке:

примеры кругов и эллипсов в SVG

Основные фигуры. Многоугольник.

В дополнение к прямоугольнику, кругу и эллипсу, можно так же нарисовать шестиугольник, восьмиугольник, звезду или другие произвольные фигуры замкнутой формы. Элемент <polygon> позволяет указать серию точек, которые описывают геометрическую фигуру. Она может быть залита и иметь контур. Атрибут points содержит серию пар х- и у-координат, разделенных запятыми или пробелами. В этой серии чисел должно присутствовать четное количество компонентов. Возвращаться в начальную точку нет необходимости: фигура сама автоматически замкнет контур. Посмотрим в действии на <polygon>: нарисуем параллелограмм, звезду и фигуру неправильной формы:

<svg width="200px" height="200px" viewBox="0 0 200 200">
  <!-- параллелограмм -->
  <polygon points="15,10 55, 10 45, 20 5, 20" style="fill: red; stroke: black;" />
  <!-- звезда -->
  <polygon points="35,37.5 37.9,46.1 46.9,46.1 39.7,51.5 42.3,60.1 35,55 27.7,60.1 30.3,51.5 23.1,46.1 32.1,46.1" style="fill: #ccffcc; stroke: green;" />
  <!-- странная фигура -->
  <polygon points="60 60, 65 72, 80 60, 90 90, 72 80, 72 85, 50 95" style="fill: yellow; fill-opacity: 0.5; stroke: black; stroke-width: 2;" />
</svg>

Результат в «живую» или смотрим на картинке:

примеры многоугольников в SVG

Заливка многоугольников с пересекающимися линиями

У многоугольников, показанных выше, очень легко закрасить внутреннюю поверхность. Линии, которые формируют многоугольник не пересекаются друг с другом. Поэтому внутреннюю часть легко отличить от внешней. Другое дело когда линии пересекаются. Бывает сразу и не скажешь, где внутренняя часть фигуры. Для тех кто сомневается, смотрим следующий пример:

<svg width="200px" height="200px" viewBox="0 0 200 200">
  <polygon points="48,16 16,96 96,48 0,48 80,96" style="stroke: black; fill: none;" />
</svg>
пример многоугольника с неопределяемой на глаз внутренней частью

И так, средняя секция звезды находится внутри или снаружи контура?

В SVG есть два алгоритма для определения находится ли часть многоугольника внутри этого многоугольника или снаружи. Контролирует это правило fill-rule (относится к оформлению). Имеет два варианта значения: nonzero или evenodd. В зависимости от выбранного значения правила получается разный эффект. Посмотрим на примере разницу:

<svg width="200px" height="200px" viewBox="0 0 200 200">
  <polygon style="fill-rule: nonzero; fill: yellow; stroke: black;" points="48,16 16,96 96,48 0,48 80,96" />
  <polygon style="fill-rule: evenodd; fill: #00ff00; stroke: black;" points="148,16 116,96 196,48 100,48 180,96" />
</svg>

Результат в «живую» или смотрим на картинке:

полигоны с разными правилами заливки

Объяснение правил заливки

Не обязательно знать детали fill-rules для того, чтобы пользоваться этим правилом, но мы все же расскажем как оно работают. Правило nonzero определяет положение точки (находится внутри или за пределами многоугольника), путем проведения луча от точки в бесконечность в любом направлении. Счет начинается с нуля. К этому счету добавляется 1, каждый раз когда луч пересекает контур фигуры, который рисовался слева направо. И вычитаем 1, когда пересекает контур с направлением справа налево. После подсчета переходов, если результат равен нулю, то точка находится вне фигуры. В противном случае, она находится внутри.

правило заливки nonzero

Правило evenodd также рисует луч от точки в бесконечность, с подсчетом количества раз пересечения контуров фигуры. Но в данном случае подсчитывается только количество раз, сколько раз данный луч пересек контуры фигуры. Если общее количествово пересечений нечетное — точка внутри многоугольника, если четное — за пределами.

правило заливки evenodd

Основные фигуры. Ломанная линия.

В конце нашего «обзора» основных фигур, мы возвращаемся к прямым линиям. Иногда может понадобиться нарисовать целую серию линий, но чтобы они не были замкнутой фигурой. Тут можно использовать несколько элементов <line>. Но проще будет применить элемент <polyline>. У него те же атрибуты, что и у <polygon>. Отличием будет только отсутствие замыкающего контура. Для примера нарисуем значок электрического сопротивления:

<svg width="200px" height="200px" viewBox="0 0 200 200">
  <polyline points="5 20, 20 20, 25 10, 35 30, 45 10, 55 30, 65 10, 75 30, 80 20, 95 20" style="stroke: black; stroke-width: 3; fill: none;" />

</svg>
пример ломанной линии в SVG

Если не указать заливку как fill: none для ломаной линии, тогда просмотрщик попытается залить внутреннее пространство черным цветом. Например вот так:

пример ломанной линии с заливкой

Концы линий и соединения

При использовании <line> или <polyline> можно указать форму концов линий задав свойству stroke-linecap одно из значений: butt, round или square. Следующий пример демонстрирует все три значения. Серыми линиями показано, где фактически расположены края линии: видим, что круглые (round) и квадратные (square) края вышли за пределы линии, трапециевидные (butt), они используются по умолчанию, не выходят за пределы линии:

<svg width="200px" height="200px" viewBox="0 0 200 200">
  <line x1="10" y1="15" x2="50" y2="15" style="stroke-linecap: butt; stroke-width: 15;"/>

  <line x1="10" y1="45" x2="50" y2="45" style="stroke-linecap: round; stroke-width: 15;"/>

  <line x1="10" y1="75" x2="50" y2="75" style="stroke-linecap: square; stroke-width: 15;"/>

  <!-- вертикальные линии -->
  <line x1="10" y1="0" x2="10" y2="100" style="stroke: #999;"/>
  <line x1="50" y1="0" x2="50" y2="100" style="stroke: #999;"/>
</svg>
разные концы линий в SVG

Так же можно повлиять на вид соединения линий свойством stroke-linejoin, которое может иметь следующие значения: miter (угол), round (скругленное), bevel (срез).

<polyline style="stroke-linejoin: miter; stroke: black; stroke-width: 12; fill: none;" points="30 30, 45 15, 60 30"/>
<polyline style="stroke-linejoin: round; stroke: black; stroke-width: 12; fill: none;" points="90 30, 105 15, 120 30"/>
<polyline style="stroke-linejoin: bevel; stroke-width: 12; stroke: black; fill: none;" points="150 30, 165 15, 180 30"/>
соединение линий под углом

Если линии соединяются под острым углом и имеют соединение вида miter, есть возможность увеличить толщину линий: установить соотношение угла к толщине линии свойством stroke-miterlimit. По умолчанию его значение равно 4.

Структура SVG документа и представление

SVG позволяет разделять структуру документа и его представление. Сейчас попробуем сравнить и сопоставить эти два понятия, обсудить аспекты представления документа более детально и показать некоторые элементы SVG, которые Вы сможете использовать для того, чтобы сделать структуру Вашего документа более четкой, читаемой и более простой в обслуживании.

Одна из целей XML — предоставить доступ к структуре данных и отделить эту структуру от ее визуального представления. Рассматривая SVG-код для рисунка кота в «Создание SVG графики», можно было догадаться по структуре кода, что это кот — об этом говорит расположение и размер геометрических фигур, которые формируют рисунок. Если бы нужно было сделать структурные изменения, например, уменьшить усы, округлить нос, или сделать уши длиннее с закругленными кончиками — получим изображение кролика. И не имеет значения, какой была основа представления. Таким образом, структура подсказывает Вам, что собой представляет графика.

Мы не хотим сказать, что информация о визуальном оформлении неважна. Если бы мы нарисовали кота толстыми фиолетовыми линиями и сделали серую заливку внутренней поверхности рисунка, мы бы все еще смогли узнать кота на рисунке, но внешний вид такого кота был бы менее лицеприятен. Эта разница показана на рисунке ниже. XML помогает Вам разделить структуру и представление. К сожалению, большинство обсуждений XML уделяют больше внимания структуре в ущерб представлению. Мы исправим эту несправедливость, расписав в деталях, как использовать представление в SVG.

структура против представления

Использование стилей с SVG

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

Встроенные стили

В следующем примере использованы встроенные стили. Это именно тот способ, который мы использовали во всех предыдущих примерах: атрибуту style задавали серию визуальных свойств со значениями:

<circle cx="20" cy="20" r="10" style="stroke: black; stroke-width: 1.5; fill: blue; fill-opacity: 0.6" />

Внутренние таблицы стилей

Вообще нет необходимость вставлять стили в каждый элемент SVG, тем самым захламляя код. Можно создать внутренние таблицы стилей для сохранения часто используемых стилей, которые можно применить для всех элементов определенного типа или применить только к элементам, содержащих определенный класс. В следующем примере установлена внутренняя таблица стилей, на основе которой все круги будут создаваться с утолщенной пунктирной линией и светло желтой заливкой. Таблицу стилей помещена в элемент <defs>, который мы обсудим немного позже. На примере будет нарисовано несколько кругов. Два последних будут использовать inline стили, корректирующие оформление, заданное внутренней таблицей стилей:

<svg width="200px" height="200px" viewBox="0 0 200 200">
<defs>
  <style type="text/css"><![CDATA[
  circle {
  fill: #ffc;
  stroke: blue;
  stroke-width: 2;
  stroke-dasharray: 5 3
  }
  ]]></style>
</defs>
<circle cx="20" cy="20" r="10" />
<circle cx="60" cy="20" r="15" />
<circle cx="20" cy="60" r="10" style="fill: #cfc" />
<circle cx="60" cy="60" r="15" style="stroke-width: 1; stroke-dasharray: none;" />
</svg>
пример использования внутренних таблиц стилей

Внешние таблицы стилей

Представим ситуацию, Вы используете внутренние таблицы стилей для целой серии SVG-документов. Для этого приходится копировать и вставлять таблицы стилей в каждый документ. Думаю уже сейчас понятно, что это не практично. А если вспомнить, что довольно чсто приходится вносить изменения в стили, тогда лень, которая является чуть ли не главным двигателем прогресса, просто завопит от возмущения — кому же охота делать обезьянью работу по внесению изменений стилей в каждый из SVG-документов?!

Вот тут-то и пригодятся внешние таблицы стилей — все содержимое элемента <style> (включая <! CDATA[ и ]]>) сохраняем во внешнем файле, который и станет внешней таблицей стилей. Следующий пример демонстрирует содержимое такой внешней таблице с именем ext_style.css. Эта таблица использует множество селекторов, включая *, который указывает на все элементы:

* { fill:none; stroke: black; } /* по умолчанию для всех элментов */
rect { stroke-dasharray: 7 3; }
circle.yellow { fill: yellow; }
.thick { stroke-width: 5; }
.semiblue { fill:blue; fill-opacity: 0.5; }

Внешняя таблица готова. Теперь посмотрим как ее подключить к SVG-документу:

<?xml version="1.0"?>
<?xml-stylesheet href="ext_style.css" type="text/css"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="200px" height="200px" viewBox="0 0 200 200" preserveAspectRatio="xMinYMin meet">
<line x1="10" y1="10" x2="40" y2="10" />
<rect x="10" y="20" width="40" height="30" />
<circle class="yellow" cx="70" cy="20" r="10" />
<polygon class="thick" points="60 50, 60 80, 90 80" />
<polygon class="thick semiblue" points="100 30, 150 30, 150 50, 130 50" />
</svg>

Результат смотрим в «живую» или на картинке ниже:

пример использования внешней таблицы стилей
Заметка

Линейные стили почти всегда будут выполняться быстрее, чем стили во внутренней или внешней таблице стилей: таблицы стилей и классы увеличивают время выполнение из-за поиска и анализа.

Атрибуты представления

Подавляющее большинство Ваших SVG-документов будут использовать стили для оформления, но не помешает знать, что SVG позволяет также задавать оформление и атрибутами представления. Например, вместо:

<circle cx="10" cy="10" r="5" style="fill: red; stroke:black; stroke-width: 2;" />

можно написать так:

<circle cx="10" cy="10" r="5" fill="red" stroke="black" stroke-width="2" />

Такая запись может показаться смесью структуры и представления. И тут вы абсолютно правы. И даже следует подумать: а какой от этого толк? А толк вот в чем: атрибуты представления могут пригодиться, когда Вы будете создавать документы SVG конвертируя данные из XML-источника в SVG (об этом детальней в последующих главах). В данном случае будет проще создать отдельные атрибуты, чем содержимое атрибута style. Атрибуты представления также могут пригодится в случаях когда отсутствует поддержка таблиц стилей.

Атрибуты представления имеют самый низкий приоритет среди способов задать оформление. Любое определение, исходящая от встроенных стилей, внутренних или внешних таблиц стилей будет «перекрывать» атрибут представления. В следующем примере круг будет залит красным, а не зеленым цветом.

<svg width="200" height="200">
<defs>
<style type="text/css"><![CDATA[
circle { fill: red; }
]]></style>
</defs>
<circle cx="20" cy="20" r="15" fill="green" />
</svg>

И еще раз, использование атрибутов style или таблиц стилей у вас должно преобладать ведь они позволяют применить сложные серии характеристик заливки и кисти для всех элементов, находящихся внутри документа без необходимости дублирования информации в каждом элементе. Сила и гибкость таблиц стилей позволяет делать изменения в оформлении документов сколько бы их ни было с минимальными усилиями.

Группирование и привязка SVG объектов

Большинство не абстрактных изображений состоит из групп фигур и линий, которые формируют узнаваемые объекты. Подобные группы элементов SVG позволяет объединить чтобы сделать код более понятным, структурированным и удобным.

Элемент <g>

Элемент <g> объединяет все дочерние элементы в группу. Зачастую имеет уникальный id. Так же может в себе содержать <title> и <desc> чтобы улучшить доступность для невизуальных браузеров. В дополнение к этому, <g> обеспечивает удобство и сокращение объема кода при использовании стилей: заданный стиль для <g> будет распространятся на все дочерние элементы. В следующем примере избежим дублирования style="fill:none; stroke:black;" для элементов рисунка:

<svg width="240px" height="240px" viewBox="0 0 240 240">
<title>Рисунок группами</title>
<desc>Рисунок дома и людей</desc>
<g id="house" style="fill: none; stroke: black;">
  <desc>Дос с дверью</desc>
  <rect x="6" y="50" width="60" height="60"/>
  <polyline points="6 50, 36 9, 66 50"/>
  <polyline points="36 110, 36 80, 50 80, 50 110"/>
</g>
<g id="man" style="fill: none; stroke: black;">
  <desc>Мужик</desc>
  <circle cx="85" cy="56" r="10"/>
  <line x1="85" y1="66" x2="85" y2="80"/>
  <polyline points="76 104, 85 80, 94 104" />
  <polyline points="76 70, 85 76, 94 70" />
</g>
<g id="woman" style="fill: none; stroke: black;">
  <desc>Тетка</desc>
  <circle cx="110" cy="56" r="10"/>
  <polyline points="110 66, 110 80, 100 90, 120 90, 110 80"/>
  <line x1="104" y1="104" x2="108" y2="90"/>
  <line x1="112" y1="90" x2="116" y2="104"/>
  <polyline points="101 70, 110 76, 119 70" />
</g>
</svg>
использование элемента g

Допускается вложенность группы элементов в другую группу.

Элемент <g> по своей сущности очень напоминает группирование слоев в Adobe Photoshop и Adobe Illustrator.

Элемент <use>

Часто в дизайне встречаются повторяющиеся элементы. Например, рекламный буклет может содержать логотип компании в верхнем левом и нижнем правом углу каждой страницы. В каком-нибудь Adobe Illustrator достаточно один раз нарисовать логотип, сгруппировать все его составляющие, скопировать сгруппированный рисунок и вставлять его сколько угодно раз в нужные места. Элемент <use> предоставляет аналог функции копи-паста для группы элементов, объединенных с помощью <g>.

Чтобы скопировать-вставить определенную группу (она должна иметь id), добавляем элемент <use>, указываем какую группу будем копировать (атрибут xlink:href) и задаем положение копии (относительно положения оригинала). Возьмем предыдущий пример и сделаем копии дома и людей:

...
<use xlink:href="#house" x="70" y="100"/>
<use xlink:href="#woman" x="-80" y="100"/>
<use xlink:href="#man" x="-30" y="100"/>
использование элемента use

Элемент <defs>

В предыдущем примере сложно не заметить недостатки:

Элемент <defs> позволяет решить эти проблемы. Заключаем сгруппированные объекты в <defs> — определяем элементы без их отображения. Спецификация SVG рекомендует группы, которые будут повторно использоваться, заключать в <defs>, чтобы код получился более гибким и компактным. В следующем примере дом, мужчина и женщина определены так, чтобы их верхний левый угол находился в (0, 0) и не был указан цвет заливки. Так как группы находятся внутри <defs>, они не будут отображаться на экране, но будут служить в качестве «шаблонов» для использования в будущем. Добавим еще одну группу couple, которая, использует <use> для групп man и woman. Заметьте, что нижний рисунок не может использовать couple, потому что поменяли местами женщину и мужчину:

<svg width="240px" height="240px" viewBox="0 0 240 240">
<title>Рисуем группами</title>
<desc>На рисунке дом и люди</desc>
<defs>
  <g id="house" style="stroke: black;">
    <desc>Дом с дверью</desc>
    <rect x="0" y="41" width="60" height="60" />
    <polyline points="0 41, 30 0, 60 41" />
    <polyline points="30 101, 30 71, 44 71, 44 101" />
  </g>
  <g id="man" style="fill: none; stroke: black;">
    <desc>Мужик</desc>
    <circle cx="10" cy="10" r="10" />
    <line x1="10" y1="20" x2="10" y2="44" />
    <polyline points="1 58, 10 44, 19 58" />
    <polyline points="1 24, 10 30, 19 24" />
  </g>
  <g id="woman" style="fill: none; stroke: black;">
    <desc>Тетка</desc>
    <circle cx="10" cy="10" r="10" />
    <polyline points="10 20, 10 34, 0 44, 20 44, 10 34" />
    <line x1="4" y1="58" x2="8" y2="44" />
    <line x1="12" y1="44" x2="16" y2="58" />
    <polyline points="1 24, 10 30, 19 24" />
  </g>
  <g id="couple">
    <desc>Мужик и тетка</desc>
    <use xlink:href="#man" x="0" y="0" />
    <use xlink:href="#woman" x="25" y="0" />
  </g>
</defs>

<!-- используем определенные ранее группы -->
<use xlink:href="#house" x="0" y="0" style="fill: #cfc;" />
<use xlink:href="#couple" x="70" y="40" />

<use xlink:href="#house" x="120" y="0" style="fill: #99f;" />
<use xlink:href="#couple" x="190" y="40" />

<use xlink:href="#woman" x="0" y="145" />
<use xlink:href="#man" x="25" y="145" />
<use xlink:href="#house" x="65" y="105" style="fill: #c00;" />
</svg>
использование элемента defs

Посмотреть в живую.

Элемент <use> не ограничен рамками одного SVG документа: атрибут xlink:href может сослаться на любой подходящий файл или адрес в Сети. Это позволяет собрать несколько общих элементов в один SVG файл и использовать в качестве «донора». Например, создаем identity.svg, в котором соберем графику, использующуюся организацией:

<g id="company_mascot">
  <!-- талисман компании -->
</g>
<g id="company_logo" style="stroke: none;">
  <polygon points="0 20, 20 0, 40 20, 20 40" style="fill: #696;" />
  <rect x="7" y="7" width="26" height="26" style="fill: #c9c;" />
</g>
<g id="partner_logo">
  <!-- логотип партнера -->
</g>

и затем ссылаемся на него:

<use xlink:href="identity.svg#company_logo" x="200" y="200" />

Элемент <symbol>

Элемент <symbol> позволяет группировать элементы еще одним способом. В отличии от элемента <g>, <symbol> никогда не отображается: нет необходимости его скрывать с помощью <defs>.

Еще одна особенность <symbol> — это возможность использования атрибутов viewBox и preserveAspectRation, чтобы изображение смогло вписаться в окно просмотра, установленное элементом <use>. В следующем примере видим, что width и height для простой группы игнорируются (для верхних двух прямоугольников), но они используются при отображении <symbol>. Края правого нижнего прямоугольника на обрезаны, потому что для preserveAspectRation задано slice.

<svg width="240px" height="240px" viewBox="0 0 240 240">
<title>Symbols vs groups</title>
<desc>Use</desc>
<defs>
  <g id="octagon" style="stroke: black;">
    <desc>Octagon as group</desc>
    <polygon points="36 25, 25 36, 11 36, 0 25,0 11, 11 0, 25 0, 36 11"/>
  </g>
  <symbol id="sym-octagon" style="stroke: black;" preserveAspectRatio="xMidYMid slice" viewBox="0 0 40 40">
    <desc>Octagon as symbol</desc>
    <polygon points="36 25, 25 36, 11 36, 0 25,0 11, 11 0, 25 0, 36 11"/>
  </symbol>
</defs>

<use xlink:href="#octagon" x="40" y="40" width="30" height="30" style="fill: #c00;"/>
<use xlink:href="#octagon" x="80" y="40" width="40" height="60" style="fill: #cc0;"/>
<use xlink:href="#sym-octagon" x="40" y="80" width="30" height="30" style="fill: #cfc;"/>
<use xlink:href="#sym-octagon" x="80" y="80" width="40" height="60" style="fill: #699;"/>
</svg>
использование элемента symbol

Посмотреть в живую.

Элемент <image>

Если <use> позволяет повторно использовать часть SVG файла, то элемент <image> содержит или SVG файл целиком, или растровый файл (JPEG или PNG). Если использоваться будет SVG файл — атрибуты x, y, width и height устанавливают окно просмотра, в котором будет отображаться содержимое этого файла. Если растровый файл — он будет масштабироваться чтобы соответствовать прямоугольнику, определенному атрибутами. Пример внедрения растрового изображения:

<svg width="310px" height="310px" viewBox="0 0 310 310">
<!--эмулируем тень для эллипса-->
<ellipse cx="154" cy="154" rx="150" ry="120" style="fill: #999999;"/>
<!--основная фигура эллипса-->
<ellipse cx="152" cy="152" rx="150" ry="120" style="fill: #cceeff;"/>
<!-- внедряем картинку: xlink:href - ссылка на растровое изображение; x и y - положение врхнего левого угла image -->
<!-- width и height - размеры image, под которые будет подгонятся реальное изображение -->
<image xlink:href="kwanghwamun.jpg" x="72" y="92" width="160" height="120"/>
</svg>
использование элемента image

Трансформация в SVG: перемещение

До этого момента все рисунки были отображены «как есть». Но на практике обыденное дело, когда требуется изображение переместить куда-то, как-то перевернуть или изменить масштаб. Для решения этих задач существует атрибут transform. Познакомимся с ним поближе.

В материале «Группирование и привязка SVG объектов» мы познакомились с элементом <use>, который мог вставить группу графических объектов в определенное место. Посмотрим на следующий пример: определяем квадрат (он отобразится в верхнем левом углу координатной сетки), затем повторно рисуем его по координатам (50, 50) (пунктирные линии — просто обозначение границ холста):

<svg width="200px" height="200px" viewBox="0 0 200 200">
<g id="square">
  <rect x="0" y="0" width="20" height="20" style="fill: none; stroke:black; stroke-width: 2;"/>
</g>
<use xlink:href="#square" x="50" y="50"/>
</svg>
элемент use для перемещения

Как выясняется, значение х и у являются одной из кратких форм более общего и более мощного атрибута transform. В данном случае, значения х и у преобразуются в атрибут в такого вида: transform = "translate (x-value,y-value)", где translate — технический чудо-термин для «перемещения». x-value, y-value измеряются в текущей системе координат пользователя. Повторим эффект перемещения из предыдущего примера с помощью translate:

<svg width="200px" height="200px" viewBox="0 0 200 200">
<g id="square">
  <rect x="0" y="0" width="20" height="20" style="fill: none; stroke:black; stroke-width: 2;"/>
</g>
<use xlink:href="#square" transform="translate(50,50)"/>
</svg>

Результат можем лицезреть на следующем рисунке. Визульно результат удовлетворяет, но полагать, что в данном примере используется перемещение фигуры было бы ошибочным.

элемент use для перемещения

«За кулисами» в действительности происходит совсем другая история. Вместо того, чтобы перемещать сам квадрат, translate берет и перемещает всю координатную сетку. Поэтому наш квадрат будет иметь координаты (0, 0):

в реальности translate перемещает координатную сетку

Следует запомнить

Translate не перемещает сам объект, а изменяет положение координатной сетки.

На первый взгляд, использование translate кажется нелепым. Это все равно если бы для того чтобы отставить диван подальше от внешней стены дома, потребовалось переместить всю комнату вместе со стенами. На самом деле не все так плохо. Как оказывается, перемещается не вся координатная сеть общая для всех объектов, а локальная сеть конкретного перемещаемого объекта. Т.е. как бы образуется несколько систем координат.

Да, кстати, translate можно применять не только к <use>, но и к отдельным элементам, а так же к группам:

<svg width="200px" height="200px" viewBox="0 0 200 200">
<rect x="0" y="0" width="20" height="20" style="fill: none; stroke:black; stroke-width: 2" transform="translate(50,50)"/>
</svg>

Посмотреть в живую.

P.S. что будет с координатной сеткой и где она окажется, если применить трансформацию только к одному из многих элементов на холсте, узнаем в последующих материалах.

Трансформация в SVG: масштабирование

Масштаб SVG объектов происходит аналогично перемещению — с помощью изменения параметров локальной системы координат (в данном случае это будет масштабирование). Для этого используем значение scale атрибута transform:

transform="scale(value)"

умножаем координаты х и у на указанное value.

transform="scale(x-value, y-value)"

координаты x умножаются на x-value, y — на y-value.

Рассмотрим пример пропорционального масштабирования, в котором значение по обеим осям умножается на 2 (пунктирные линии с точками на рисунке показывают интересующую нас зону холста). Левый верхний угол квадрата имеет координаты (10, 10).

<svg width="200px" height="200px" viewBox="0 0 200 200">
<g id="square">
  <rect x="10" y="10" width="20" height="20" style="fill: none; stroke: black;"/>
</g>
<use xlink:href="#square" transform="scale(2)"/>
</svg>
равномерный масштаб изображения

Хм...Квадрат стал больше — это ожидалось. А вот почему он находится в другом месте?. Все становиться ясно, когда посмотрим на следующий рисунок.

равномерный масштаб изображения

Координатная сетка не переместилась: точка (0, 0) в системе координат находиться на том же месте. Но каждая координата стала в 2 раза больше, чем была — произошло увеличение всей координатной сетки. На рисунке по линиям видно, что левый верхний угол прямоугольника все еще в точке (10, 10) — объект не был перемещен.

Масштаб системы координат также объясняет, почему контур увеличенного квадрата стал толще. Stroke-width измеряется в системе координат пользователя и эта единица тоже стала в 2 раза больше. Соответственно и кисть стала толще.

Следует запомнить

Трансформация scale (масштабирование) не изменяет ни размеры объекта, ни ширину кисти — меняется только размер системы координат по отношению к холсту.

А что же получится, если задать разные коэффициенты масштабирования? Пробуем: по оси х коэффициент равен 3, по у — 1,5.

неравномерное масштабирование

Оказывается толщина кисти элемента, как и сам элемент, также масштабируется неравномерно.

В выше приведенных примерах мы применяли атрибут transform к элементу <use>. Но с таким же успехом его можно применить трансформацию и к группе элементов:

<g id="group1" transform="translate(3, 5)">
  <line x1="10" y1="10" x1="30" y2="30"/>
  <circle cx="20" cy="20" r="10"/>
</g>

Можете применить трансформацию и к отдельному объекту:

<rect x="15" y="20" width="10" height="5" transform="scale(3)" style="fill: none; stroke: black;"/>

В этом примере ширина и высота масштабированного прямоугольника должна стать в 3 раза больше, чем была. Но пример интресен еще и таким вопросом: координаты х и у определяются до или после того, как прямоугольник масштабируется? Т.е. будет ли объект «пермещен»? Ответ — SVG применяет трансформацию к системе координат до того, как будет отрисована фигура, т.е. фигура будет «перемещена» относительно немасштабируемых объектов. Следующий пример демонстрирует масштабирование прямоугольника отдельно от остальных элементов:

<!-- линии в немасштабируемой системе кординат -->
<line x1="0" y1="0" x2="100" y2="0" style="stroke: black;"/>
<line x1="0" y1="0" x2="0" y2="100" style="stroke: black;"/>
<line x1="45" y1="0" x2="45" y2="100" style="stroke: gray;"/>
<line x1="0" y1="60" x2="100" y2="60" style="stroke: gray;"/>
<!-- масштабируемый прямоугольник -->
<rect x="15" y="20" width="10" height="5" transform="scale(3)" style="fill: none; stroke: black;"/>

Результат на демо примере или рисунке ниже. Координатная сетка нарисована линиями в немасштабируемой системе координат.

масштабирование одной фигуры

Эффект масштабирования к конкретным фигурам такой же, если бы трансформация была применена к группе:

<g transform="scale(3)">
  <rect x="15" y="20" width="10" height="5" style="fill: none; stroke: black;"/>
</g>

Последовательность трансформаций в SVG

Для объекта количество возможных трансформаций не ограничивается одной: можно задать несколько. Для этого атрибуту transform задаем несколько значений через пробел. В следующем примере представлен прямоугольник, который подвергнем двум трансформациям: translation и scaling. Оси тут нарисованы для демонстрации как именно прямоугольник переместился.

<!-- рисуем оси -->
<line x1="0" y1="0" x2="0" y2="100" style="stroke: gray;"/>
<line x1="0" y1="0" x2="100" y2="0" style="stroke: gray;"/>

<rect x="10" y="10" height="20" width="15" transform="translate(30, 20) scale(2)" style="fill: gray;"/>

Ниже вариант применение трансформаций к вложенным группам:

<g transform="translate(30, 20)">
  <g transform="scale(2)">
    <rect x="10" y="10" height="20" width="15" style="fill: gray;"/>
  </g>
</g>

Оба эти варианта дадут одинаковый результат:

последовательность трансформаций

Рассмотрим каждый шаг трансформаций в отдельности:

последовательность трансформаций по шагам

Следует запомнить

Порядок, в котором выполняется последовательность трансформаций, влияет на конечный результат. Трансформация А следующая за В не даст вам тот же самый результат, что и трансформация В следующая за трансформацией А.

Для этого возьмем тот же прямоугольник и применим к нему сначала последовательность translate -> scale (на рисунке он серый). Затем возьмем его «брата-близнеца» и применим scale -> translate (черный):

<!-- оси -->
<line x1="0" y1="0" x2="0" y2="100" style="stroke: gray;"/>
<line x1="0" y1="0" x2="100" y2="0" style="stroke: gray;"/>

<rect x="10" y="10" width="20" height="15" transform="translate(30, 20) scale(2)" style="fill: #ccc;"/>
<rect x="10" y="10" width="20" height="15" transform="scale(2) translate(30, 20)" style="fill: black;"/>

Результат в живую или на рисунке:

результат зависит от последовательности трансформаций

Причиной отличия результата в данном случае является то, что первой применялось масштабирование. Перенос выполнялся уже в увеличенной системе координат.

последовательность трансформаций: масштабирование потом перенос

Прием: конвертация из декартовой системы координат

На практике можно столкнуться с задачей: перенести векторный рисунок, созданный в декартовой системе координат, в SVG. В декартовой системе координат, точка (0, 0) находится в нижнем левом углу холста, а у-координаты возрастают по мере движения вверх.

трапеция в декартовой системе координат

Выходит ситуация, когда ось-у перевернута вверх ногами по отношению к системе SVG. Чтобы рисунок отобразился в SVG так же как и в исходной системе, координаты необходимо пересчитать. Чтобы не делать это вручную, можно воспользоваться последовательностью трансформаций, чтобы SVG сам все сделал. Для этого, сначала, просто переносим картинку в SVG с исходными координатами. Для большей наглядности нарисуем оси декартовой системы координат. Результат предсказуем — рисунок получился перевернутым вверх ногами.

<svg width="200px" height="200px" viewBox="0 0 200 200">
<!-- оси -->
<line x1="0" y1="0" x2="100" y2="0" style="stroke: black;"/>
<line x1="0" y1="0" x2="0" y2="100" style="stroke: black;"/>

<!-- трапеция-->
<polygon points="40 40, 100 40, 70 70, 40 70" style="fill: gray; stroke: black;"/>
</svg>
трапеция в SVG с исходными координатами

Чтобы закончить конвертацию, выполняем следующие шаги:

  1. Находим максимальную y-координату (max-y). В данном случае она будет равна 100 (конец линии, изображающей ось y).
  2. Помещаем весь рисунок в элемент <g>.
  3. Делаем перемещение системы координат вниз на максимальное значение y-координаты: transform="translate(0, max-y)".
  4. Переворачиваем вверх ногами ось y с помощью трансформации scale со значением "-1" для оси y: transform="translate(0, max-y) scale(1, -1)".
<svg width="200px" height="200px" viewBox="0 0 200 200">
<g transform="translate(0,100) scale(1,-1)">
  <!-- оси -->
  <line x1="0" y1="0" x2="100" y2="0" style="stroke: black;"/>
  <line x1="0" y1="0" x2="0" y2="100" style="stroke: black;"/>

  <!-- трапеция -->
  <polygon points="40 40, 100 40, 70 70, 40 70" style="fill: gray; stroke: black;"/>
</g>
</svg>

Результат в живую

результат конвертации декартовой системы координат в SVG

Трансформация в SVG: поворот

В SVG можно повернуть систему координат на определенный угол. По умолчанию, угол увеличивается по мере поворота по часовой стрелке. Горизонтальной линия имеет угол 0°.

углы по умолчанию

Центром вращения (или по другому «осью вращения») по умолчанию является точка (0, 0). На следующем примере нарисуем серый квадрат. Его повернем на 45° и перекрасим в черный. Для наглядности добавим так же оси координат.

<!-- оси -->
<polyline points="100 0, 0 0, 0 100" style="stroke: black; fill: none;"/>
<!-- квадрат в положениях до и после поворота -->
<rect x="70" y="30" width="20" height="20" style="fill: gray;"/>
<rect x="70" y="30" width="20" height="20" transform="rotate(45)" style="fill: black;"/>

Результат в живую или смотрим на картинке:

квадрат до и после поворота

В большинстве случаев неудобно поворачивать всю систему координат относительно точки (0, 0). Например, хочется повернуть отдельный объект вокруг произвольной точки. Этого можно добиться серией трансформаций: translate(centerX, centerY) rotate(angle) translate(-centerX, -centerY). О также подобную задачу можно решить более простым способом, благо разработчики SVG об этом позаботились: просто в rotate следует добавить координаты точки, вокруг которой хочется повернуть объект: rotate(angle, centerX, centerY).

В данном случае проявляется эффект временной установки новой системы координат с центром в координатах в centerX и centerY. Выполняется поворот объекта вокруг этой новой оси, а затем система координат возвращается как и была. Следующий пример демонстрирует это — нарисуем стрелку и сделаем несколько ее копий вокруг произвольной точки:

<!-- центр вращения -->
<circle cx="50" cy="50" r="3" style="fill: black;" />
<!-- non-rotated arrow -->
<g id="arrow" style="stroke: black;">
  <line x1="60" y1="50" x2="90" y2="50" />
  <polygon points="90 50, 85 45, 85 55" />
</g>
<!-- вращение вокруг произвольной -->
<use xlink:href="#arrow" transform="rotate(60, 50, 50)" />
<use xlink:href="#arrow" transform="rotate(-90, 50, 50)" />
<use xlink:href="#arrow" transform="rotate(-150, 50 50)" />

Результат в живую или смотрим на картинке:

поворачиваем стрелку вокруг произвольной точки

Прием: масштабирование вокруг точки

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

translate(-centerX*(factor-1), -centerY*(factor-1)) scale(factor)

Так же можно применить коэффициент масштабирования к stroke-width чтобы толщина контура не менялась с увеличением объекта. Для примера сделаем увеличение прямоугольника вокруг точки.

<!-- центр масштабирования -->
<circle cx="50" cy="50" r="2" style="fill: black;" />
<!-- первоначальный прямоугольник -->
<g id="box" style="stroke: black; fill: none;">
<rect x="35" y="40" width="30" height="20" />
</g>

<use xlink:href="#box" transform="translate(-50,-50) scale(2)" style="stroke-width: 0.5;" />
<use xlink:href="#box" transform="translate(-75,-75) scale(2.5)" style="stroke-width: 0.4;" />
<use xlink:href="#box" transform="translate(-100,-100) scale(3)" style="stroke-width: 0.33;" />

Результат в живую или смотрим на картинке:

маcштабирование вокруг точки

Трансформации skewX и skewY

В SVG имеется еще две трансформации: skewX и skewY. Они позволяют наклонить одну из осей. Общая форма skewX(угол) и skewY(угол). Трансформация skewX сдвигает все х-координаты на определенный угол, оставляя у-координаты без изменения. skewY — у-координаты, оставляя х-координаты без изменений.

<!-- пунктирные линии нарисованы в системе координат по умолчанию, до каких-либо трансформаций -->
<g style="stroke: gray; stroke-dasharray: 4, 4;">
  <line x1="0" y1="0" x2="200" y2="0"/>
  <line x1="20" y1="0" x2="20" y2="90"/>
  <line x1="120" y1="0" x2="120" y2="90"/>
</g>

<!-- группировка поможет сдвинуть всю интересующую группу -->
<g transform="translate(20, 0)">
  <!-- сдвигаем х-координаты на 30 градусов. Начальная система координат при этом не изменяется -->
  <g transform="skewX(30)">
    <!-- чтобы было легче понять, что происходит, рисуем в базовой системе координат -->
    <polyline points="50 0, 0 0, 0 50" style="fill: none; stroke: black; stroke-width: 2;"/>
    <text x="0" y="60">skewX</text>
  </g>
</g>

<!-- пример аналогичный предыдущему, только теперь преобразовываем y-координаты -->
<g transform="translate(120, 0)">
  <g transform="skewY(30)">
    <polyline points="50 0, 0 0, 0 50" style="fill: none; stroke: black; stroke-width: 2;"/>
    <text x="0" y="60">skewY</text>
  </g>
</g>

Результат в живую или смотрим на картинке:

трансформации skewX и skewY

Paths. Moveto, lineto, и closepath

Все фигуры, описанные ранее, являются сокращенными записями более общего элемента <path> (путь). Эти сокращения делают код более читабельным и структурированным. Элемент <path> способен создавать фигуры произвольной формы с помощью произвольного числа отрезков и кривых, связанных между собой. Такая фигура может иметь контур и заливку цветом как и обычная фигура. В добавок, эти пути можно использовать для определения усеченной зоны или маски прозрачности.

Все данные для описания элемента <path> содержаться в его атрибуте d (d — data). Данные path состоят из однобуквенных команд, таких как m (moveto) или l (lineto), за которыми следуют координаты для этой команды.

Moveto, lineto и closepath

Любой маршрут должен начинаться с команды moveto. Командная буква — заглавная М, за которой следуют х и у координаты, разделенными запятыми или пробелами. Эта команда устанавливает текущее положение «ручки», которая рисует фигуру.

После чего могут идти одна или несколько команд lineto, обозначающимися заглавной L, за которой также следуют х и у координаты, разделенные запятыми или пробелами. В приведенном ниже примере показано 3 пути. Первый рисует одну линию, второй — прямой угол, третий — два угла по 30 градусов. Когда Вы «берете ручку», используя moveto, Вы начинаете новый под-маршрут. Учтите, что запятые и пробелы используются в качестве разделителей по-разному, но абсолютно законно во всех трех путях.

<g style="stroke: black; fill: none;">
  <!-- линия -->
  <path d="M 10 10 L 100 10" />
  <!-- правый угол -->
  <path d="M 10, 20 L 100, 20 L 100,50" />
  <!-- два тридцатиградусных угла -->
  <path d="M 40 60, L 10 60, L 40 42.68, M 60 60, L 90 60, L 60 42.68" />
</g>

Рассмотрим последний путь более детально:

Значение Действие
M 40 60 Передвинуть ручку в точку (40, 60)
L 10 60 Нарисовать линию (10, 60)
L 40 42.68 Нарисовать линию (40, 42.68)
M 60 60 Начать новый маршрут; Передвинуть ручку в точку (60, 60) — нет нарисованных линий
L 90 60 Нарисовать линию (90, 60)
L 60 42.68 Нарисовать линию (60, 42.68)
использование path

Заметка

Трудно не заметить, что данные элемента <path> не очень похожи на типичные XML атрибуты. Так как все данные вмещены в один общий атрибут, а не отдельный элемент для каждой точки или линии, фигура занимает меньше памяти когда анализируется DOM структура XML парсером. Такая запись позволяет передавать большие изображения при небольшой пропускной способности.

Если есть желание нарисовать с помощью path прямоугольник, то это можно сделать двумя способами: нарисовать четыре линии или нарисовать три и вызвать команду closepath, обозначенную Z, чтобы нарисовать прямую линию в начальную точку под-маршрута. Как это выглядит в коде показано в следующем примере:

<g style="stroke: black; fill: none;">
  <!-- прямоугольник из 4-х линий -->
  <path d="M 10 10, L 40 10, L 40 30, L 10 30, L 10 10" />
  <!-- прямоугольник с closepath -->
  <path d="M 60 10, L 90 10, L 90 30, L 60 30, Z" />
  <!-- два треугольника -->
  <path d="M 40 60, L 10 60, L 40 42.68, Z M 60 60, L 90 60, L 60 42.68, Z" />
</g>

Последний путь разберем детальней:

ЗначениеДействие
M 40 60Передвинуть ручку в точку (40, 60)
L 10 60Нарисовать линию (10, 60)
L 40 42.68Нарисовать линию (40, 42.68)
ZЗавершить маршрут, нарисовав прямую линию (40, 60), где этот путь начинался
M 60 60Начать новый маршрут; Передвинуть ручку в точку (60, 60) — нет нарисованных линий
L 90 60Нарисовать линию (90, 60)
L 60 42.68Нарисовать линию (60, 42.68)
ZЗавершить маршрут, нарисовав прямую линию (60, 60), где этот путь начинался
использование closepath

Относительные moveto и lineto

Команды, рассмотренные выше, были представлены заглавными буквами, а координаты считались абсолютными. Если Вы используете строчные буквы в качестве команд, координаты воспринимаются как относительные по отношению к текущему положению ручки. Таким образом, представленные ниже маршруты являются эквивалентными:

<path d="M 10 10 L 20 10 L 20 30 M 40 40 L 55 35" style="stroke: black;" />
<path d="M 10 10 l 10 0 l 0 20 m 20 10 l 15 -5" style="stroke: black;" />

Если начать маршрут со строчной m (moveto), координаты будут абсолютными, так как нет предыдущего положения ручки, от которого рассчитывать относительное положение. Все команды в этой главе могут быть заданы как заглавными, так и строчными буквами. Координаты команд, написанные заглавными буквами, являются абсолютными, а команды, написанные строчными буквами — относительными. Команда closepath, которая не имеет координат, имеет одинаковый эффект как при написании заглавными, так и строчными буквами.

Сокращение путей

Если представить себе, что дизайн — это королева, а контент — король, то пропускная способность — это королевский придворный, который поддерживает все дела, происходящие в дворце. Так как любой нетривиальный рисунок будет содержать многие десятки пар координат, элемент <path> имеет сокращенный вариант записи, чтобы представить путь минимальным количеством байт.

Команды горизонтального и вертикального lineto

Вертикальные и горизонтальные линии — банальность в графике. Логично, что это в первую очередь требует оптимизации. Горизонтальная линия определяется командой H, за которой следует абсолютная x-координата, или командой h, за которой идет относительная x-координата. Аналогично и с вертикальными линиями: команда V, за которой абсолютная y-координата и v — с относительной.

Сокращение Эквивалент Эффект
H 20 L 20 current_y нарисовать линию к абсолютному положению (20, current_y)
h 20 L 20 0 нарисовать линию к (current_x+20, current_y)
V 20 L current_x 20 нарисовать линию к абсолютному положению (current_x,20)
v 20 l current_x 20 нарисовать линию к (current_x, current_y+20)

Таким образом, мы получим прямоугольник с размерами 15 единиц в ширину и 25 единиц в высоту, с координатами верхнего левого угла (12, 24).

<path d="M 12 24 h 15 v 25 h -15 z" />

Обозначения сокращений для путей

Пути можно сократить, придерживаясь следующих правил:

  1. Можно ставить несколько серий координат после L и l, так же как делали это в элементе <polyline>. Ниже приведен код, где все шесть маршрутов рисуют один и тот же ромб (рис под кодом). Первые три — в абсолютной системе координат, следующие три — в относительной системе. Третий и шестой маршрут демонстрируют интересный прием — если Вы поставите несколько пар координат после moveto, все пары после первой будут считаться предшествующими lineto. Можно также ставить несколько одиночных координат после horizontal lineto и vertical lineto, хотя это делать бессмысленно. H 25 35 45 — тоже самое, что и H 45, а v 11 13 15 — тоже самое, что и v 39.
    <path d="M 30 30 L 55 5 L 80 30 L 55 55 Z" />
    <path d="M 30 30 L 55 5 80 30 55 55 Z" />
    <path d="M 30 30 55 5 80 30 55 55 Z" />
    <path d="m 30 30 l 25 -25 l 25 25 l -25 25 z" />
    <path d="m 30 30 l 25 -25 25 25 -25 25 z" />
    <path d="m 30 30 25 -25 25 25 -25 25 z" />
    сокращения path
  2. Все ненужные пробелы можно убрать. Нет необходимость ставить пробел после каждой командной буквы, если все команды состоят только из одной буквы. Нет необходимости ставить пробел между числом и командой, так как командная буква не может быть частью числа. Не нужно ставить пробел между положительными и отрицательными числами, так как знак минус не может быть частью положительного числа. Сократим третий и шестой маршрут из предыдущего списка:
    <path d="M30 30 55 5 80 30 55 55Z" />
    <path d="m30 30 25-25 25 25-25 25z" />
    Еще пример «правила упущения пробелов» продемонстрирован при создании прямоугольника с шириной в 15 единиц, высотой = 25 и координатами верхнего левого угла (12, 24):
    <path d="M 12 24 h 15 v 25 h -15 z" /> <!-- original -->
    <path d="M12 24h15v25h-15z" /> <!-- shorter -->

По теме