SVG предоставляет возможности для структурирования документа посредством специальных элементов, которые позволяют определять и группировать объекты, а также ссылаться на них в дальнейшем. Они упрощают повторное использование кода, сохраняя его чистым и читаемым. В данной статье будут рассмотрены эти элементы, а также их различия и преимущества.
Группировка с использованием элемента <g>
Элемент <g> используется для логической группировки набора связанных графических элементов. Это можно сравнить с группировкой объектов в графических редакторах.
Элемент <g> объединяет в группу все свое содержимое. Как правило, ему задается идентификатор, по которому будет производиться обращение в дальнейшем. Любые стили, применяемые к элементу <g>, будут также применены ко всем его потомкам. Это позволяет задавать стили и преобразования, а также добавлять интерактивность и анимацию сразу целой группе объектов.
В качестве примера будем использовать нарисованную в SVG птичку. Она состоит из нескольких отдельных фигур, описываемых кругами и путями. [По невыясненным причинам автором описаны круги путями вместо использования circle, хоть и было обещано использовать круги. Не спрашивайте почему. Я это исправил. Не спрашивайте зачем. — Пер.]
<svg width="1144.12px" height="400px" viewBox="0 0 572.06 200">
<style>
svg{background-color:white;}
#wing{fill:#81CCAA;}
#body{fill:#B8E4C2;}
#pupil{fill:#1F2600;}
#beak{fill:#F69C0D;}
.eye-ball{fill:#F6FDC4;}
</style>
<g id="bird">
<g id="body">
<path d="M48.42,78.11c0-17.45,14.14-31.58,31.59-31.58s31.59,14.14,31.59,31.58c0,17.44-14.14,31.59-31.59,31.59S48.42,95.56,48.42,78.11"/>
<path d="M109.19,69.88c0,0-8.5-27.33-42.51-18.53c-34.02,8.81-20.65,91.11,45.25,84.73c40.39-3.65,48.59-24.6,48.59-24.6S124.68,106.02,109.19,69.88"/>
<path id="wing" d="M105.78,75.09c4.56,0,8.84,1.13,12.62,3.11c0,0,0.01-0.01,0.01-0.01l36.23,12.38c0,0-13.78,30.81-41.96,38.09c-1.51,0.39-2.82,0.59-3.99,0.62c-0.96,0.1-1.92,0.16-2.9,0.16c-15.01,0-27.17-12.17-27.17-27.17C78.61,87.26,90.78,75.09,105.78,75.09"/>
</g>
<g id="head">
<path id="beak" d="M50.43,68.52c0,0-8.81,2.58-10.93,4.86l9.12,9.87C48.61,83.24,48.76,74.28,50.43,68.52"/>
<circle class="eye-ball" cx="72" cy="71.5" r="11"/>
<circle id="pupil" cx="72" cy="71.5" r="7"/>
<circle class="eye-ball" cx="77" cy="74" r="5"/>
</g>
</g>
</svg>
Допустим, вы рисуете такую птичку в Illustrator. Если нужно переместить ее из одного места в другое, то группировка объектов упрощает эту задачу, так как позволяет не перемещать каждый элемент по отдельности. Группировка в SVG делает примерно то же самое. В примере выше мы помимо общей группы для всей птички (id="bird") выделили также подгруппы, определяющие отдельно голову и тело (id="body", id="head").
Если вы измените цвет заливки группы #body, изменится заливка всех элементов внутри группы. Это может быть очень удобно. [Речь об элементах, у которых цвет заливки не задан явно. Например, в том же #body элемент #wing имеет свой цвет. — Пер.]
Группировка элементов может быть очень полезна не только для организации и структурирования документа. Особую пользу она может принести, если вы захотите добавить к SVG-графике интерактивности или задать какие-то преобразования. Сгруппировав элементы, можно перемещать их, масштабировать или поворачивать все вместе, сохраняя их положение друг относительно друга.
Так, в случае со сгруппированной птицей можно отмасштабировать ее всего одной строкой CSS:
#bird {transform: scale(2);}
Группировка добавляет удобств при реализации интерактивности. Вы можете повесить события мыши на всю птицу целиком и заставить ее реагировать также целиком. Это куда удобнее, чем проделывать то же самое для каждого элемента в отдельности.
Элемент <g> имеет еще одну важную и интересную особенность: он может содержать теги <title> и <desc>, которые позволят изображению быть обработанным Screen Reader'ами [Программы воспроизведения текста для людей с ограниченными возможностями; не нашел емкого и адекватного перевода термина. — Пер.]; также они позволяют сделать код более читаемым для человека. Пример использования:
<g id="bird">
<title>Bird</title>
<desc>An image of a cute little green bird with an orange beak.</desc>
<!-- ... -->
</g>
Повторное использование элементов с помощью <use>
При работе с графикой часто можно встретить ситуацию, когда применяются повторяющиеся элементы. В графических редакторах в этом случае обычно используется метод копировать-вставить, что удобнее создания элемента с нуля.
В SVG же подобную функциональность реализует элемент <use>. Он может применяться для повторного использования как отдельных элементов, так и групп элементов.
Элемент <use> принимает в качестве атрибутов координаты x и y, высоту (height), ширину (width) и ссылку на исходный элемент (xlink:href). В качестве ссылки выступает идентификатор объекта.
Например, если мы хотим добавить еще одну птичку, вместо копирования ее кода можно использовать тег <use>:
<use x="100" y="100" xlink:href="#bird" />
Обратите внимание, что в атрибуте xlink:href вы можете ссылаться на любой SVG-элемент, даже находящийся во внешнем файле. Это очень удобно использовать для организации (например, можно иметь файл с повторно используемыми компонентами) или для кэширования часто используемых файлов.
Допустим, наша птичка была создана в отдельном файле animals.svg. В этом случае ссылаться на нее можно так:
<use x="100" y="100" xlink:href="path/to/animals.svg#bird" />
Нужно учитывать, что ссылки на внешние SVG в <use> не работают в большинстве версий IE (до IE 11). Рекомендую ознакомиться со статьей от Chris Coyier, в коей рассказывается об обходе этого ограничения.
Самые внимательные читатели наверняка заметили, что координаты, задаваемые элементу <use> отсчитываются не от начала координат всего SVG-изображения. На самом деле это сокращенная форма записи атрибута transform. Следующие две строчки являются эквивалентными:
<use x="100" y="100" xlink:href="#bird" />
<use xlink:href="#bird" transform="translate(100, 100)" />

Проще говоря, координаты элемента <use> задаются относительно исходного элемента. Такое поведение не всегда оптимально и может быть недостатком.
Другим недостатком <use> является то, что копии будут использовать те же стили, что и исходный элемент. При применении стилей или преобразований к группе #bird эти стили и преобразования будут распространяться на все ее копии.
Однако вы все-таки можете применить независимое преобразование к элементу <use>. Например, следующая строка кода позволяет повторно использовать птичку, размеры которой будут составлять лишь половину от размеров исходной:
<use x="100" y="100" xlink:href="#bird" transform="scale(0.5)" />
[При этом принцип работы системы координат может показаться несколько неожиданным. Она также масштабируется. Если исходный элемент был спозиционирован в 100 пикселях от края изображения, то такая его копия будет расположена в 50 пикселях от края. На задаваемые x и y это тоже распространяется. Таким образом, слова о расположении копии относительно исходного элемента не совсем верны. — Пер.]
В отличие от преобразований, переопределить стили копии нельзя. Таким образом, если вы захотите создать армию птичек разного цвета, то использовать для этого <use> не получится (если только исходный элемент не определен внутри <defs> без своих стилей, но об этом в следующем разделе).
Элемент <use> позволяет повторно использовать элемент, который уже отображается на SVG-изображении. Если же вы хотите просто определить элемент, не отображая его, а затем отрисовать в нужном месте, когда это потребуется, на помощь придет элемент <defs>.
Повторное использование хранимых элементов с помощью <defs>
Элемент <defs> может использоваться для хранения содержимого, которое не будет отображаться при определении. Содержимое в нем хранится в скрытом виде и ждет своего часа, когда оно будет использовано и отображено другими элементами SVG, что делает его идеальным, например, для использования в узорах.
Храниться в <defs> может что угодно, начиная с группы элементов, вроде нашей птички, и заканчивая маской или градиентом. Это шаблон для дальнейшего использования. Сам по себе он никогда не отображается, только использующие его сущности.
Ниже приведен пример, в котором градиент сначала определяется, а затем используется для заливки прямоугольника:
<svg>
<defs>
<linearGradient id="gradient">
<stop offset="0%" style="stop-color: deepPink"></stop>
<stop offset="100%" style="stop-color: #009966"></stop>
</linearGradient>
</defs>
<rect width="100" height="100" stroke="#eee" stroke-width="5" fill="url(#gradient)"></rect>
</svg>
[Автором допущена досадная ошибка: не были указаны высота и ширина прямоугольника. — Пер.]
Определение линейного градиента внутри <defs> гарантирует, что он не будет внезапно отображен сам по себе, только при использовании где-либо.
В предыдущем разделе было упомянуто два недостатка элемента <use>:
- Положение нового элемента задается относительно исходного.
- Стили исходного элемента не могут быть переопределены в копиях.
Ну и к тому же исходный элемент отображается сам по себе.
Всех этих недостатков можно избежать, используя <defs>. Мало того, что оригинальный элемент не отображается, так еще и при использовании элемента, определенного внутри <defs>, положение каждого экземпляра задается относительно начала системы координат.
В следующем примере мы будем рисовать дерево [И думать забудьте о двоичном! — Пер.]. Оно состоит из ствола и листьев. Листья собраны в группу с id="leaves", а вместе со стволом они входят в более общую группу с id="tree".
<svg width="500.79px" height="200px" viewBox="0 0 500.79 200">
<style type="text/css">
#leaves{fill:#8CC63F;}
#bark{fill:#A27729;}
</style>
<g id="tree">
<path id="bark" d="M91.33,165.51c0,0,4.18-27.65,1.73-35.82l-18.55-25.03l3.01-2.74l17.45,19.87l1.91-37.6h4.44l1.83,24.53
l15.26-16.35l3.27,4.36l-16.07,19.34c0,0-2.72,0-1.09,19.34c1.63,19.34,3,29.7,3,29.7L91.33,165.51z"/>
<g id="leaves">
<path class="leaf" d="M96.97,79.07c0,0-14.92,4.34-23.52-14.05c0,0,19.4-7.98,24.37,11.9c0,0-9.68-3.57-13.07-6.73
C84.75,70.2,91.82,77.99,96.97,79.07z"/>
<path class="leaf" d="M74.07,100.91c0,0-15.94-1.51-17.2-22.39c0,0,21.62-0.27,18.83,20.66c0,0-7.92-7.1-9.97-11.41
C65.73,87.77,69.55,97.92,74.07,100.91z"/>
<!-- ... -->
</g>
</g>
</svg>
На выходе наше дерево будет выглядеть как-то так:

[К сожалению, код был сокращен автором, потеряв большую часть листьев. То, что осталось, напоминает мне часть какого-то инопланетного индустриального пейзажа. — Пер.]
Если обернуть группу #tree в элемент <defs>, дерево перестанет отображаться.
<svg width="500.79px" height="200px" viewBox="0 0 500.79 200">
<style type="text/css">
#leaves{fill:#8CC63F;}
#bark{fill:#A27729;}
</style>
<defs>
<g id="tree">
<!-- ... -->
</g>
</defs>
</svg>
Теперь дерево служит в качестве шаблона. Мы можем использовать его с помощью элемента <use>, как и любой другой элемент. Единственное отличие заключается в том, что координаты x и y задаются относительно начала координат.
В качестве примера нарисуем мини-лес.
<use xlink:href="#tree" x="50" y="100" />
<use xlink:href="#tree" x="200" y="100" />
<use xlink:href="#tree" x="350" y="100" />
Как вы можете видеть, каждое дерево расположено относительно начала координат, за которое в данном случае берется левый верхний угол изображения. Деревья располагаются независимо друг от друга и от расположения в шаблоне.
При повторном использовании элементов, определенных в <defs>, вы можете применять отдельных стили для каждого дерева, пока эти стили не определены для исходного шаблона. Если же дерево внутри <defs> будет иметь свои стили, то переопределить их будет нельзя. Таким образом, <defs> прекрасно подходит для того, чтобы определить самую основу, которая будет использована и раскрашена при необходимости. Используя только <use> без <defs>, добиться такой гибкости было бы невозможно.
Обратите внимание, что элементы внутри <defs> не отображаются, то есть ведут себя точно так же, как элемент <g> с установленным свойством display="none". Тем не менее, потомки <defs> всегда представлены в исходном дереве [Речь о дереве объектов SVG, полагаю. — Пер.], и на них всегда могут сослаться другие элементы. Таким образом, значение свойства display элемента <defs> (или его потомков) не мешает другим элементам ссылаться на него, даже если это свойство установлено в none.
[Не уверен в правильности понимания того, что хотел сказать автор в данном абзаце. Я пробовал ставить display="none" для элемента внутри <defs>, и этот элемент переставал отображаться, что вроде бы логично. Видимо, речь о том, что самому <defs> можно поставить display="none", и ничего не изменится. В следующем разделе это описано более внятно. На всякий случай добавлю абзац в оригинале. — Пер.]
Группировка элементов с использованием <symbol>
Элемент <symbol> похож на <g>: он также предоставляет возможность группировать элементы. Можно выделить два основных отличия:
- Элемент <symbol> не отображается сам по себе. Этим он похож на <defs>.
- Элемент <symbol> может иметь собственные атрибуты viewBox и preserveAspectRatio. Это позволяет ему уместиться в области просмотра (viewport) так, как этого хотите вы, а не как это определено по умолчанию.
В большинстве случаев <symbol> подходит для определения повторно используемых элементов (символов). Он все так же служит шаблоном для <use>. А имея собственные атрибуты viewBox и preserveAspectRatio, он может растягиваться на прямоугольную область просмотра, задаваемую в ссылающемся на него элементе <use>. Учтите, что элементы <symbol> определяют новую область просмотра каждый раз, когда вызываются элементом <use>.
Это прекрасная особенность элемента <symbol>. Она позволяет определять элементы, не зависящие от области просмотра, в которую они попадут. Они будут всегда отображаться заданным образом.
Но для понимания всей этой магии вы должны знать, как вообще работают атрибуты viewBox и preserveAspectRatio. По этой теме у Chris Coyier есть статья, объясняющая, чем так хорош элемент <symbol>, почему он является хорошим выбором для реализации иконок и как в целом его использовать.
Мною уже написана обширная статья про viewport, viewBox и preserveAspectRatio. Можете с ней ознакомиться: Understanding SVG Coordinate Systems and Transformations (Part 1) – The viewport, viewBox, and preserveAspectRatio.
Стоит учесть, что свойство display не применяется к элементу <symbol>. Даже если установить его отличным от display="none", элемент все равно не будет отображаться. И в то же на время элемент <symbol> можно ссылаться, даже если у него или его потомков установлено свойство display="none".
Заключение
Все эти элементы являются структурными контейнерами SVG, позволяющими облегчить повторное использование элементов, делая при этом код чище и понятнее. Каждый из этих элементов имеет свою сферу применения. Теперь, зная, что каждый из них делает и чем они отличаются друг от друга, вы сможете решить, какой из них использовать в зависимости от ситуации.
Надеюсь, вам понравилась статья, и вы нашли ее полезной. Спасибо за чтение!
От переводчика
Перевод местами достаточно вольный, но не в ущерб смыслу или содержанию. Все, что не относится напрямую к оригиналу, вынесено в примечания. По коду местами делал небольшие правки, но это либо совсем некритично, либо отмечено в примечаниях.
С предложениями, пожеланиями и замечаниями, как обычно, в ЛС.
Автор: gwer