Символы и их переиспользование

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

В библиотеке SVG.js работа с символами организована через объектную модель, позволяющую создавать, редактировать и использовать символы в декларативном и удобном стиле.


Создание символа

Символ создаётся с помощью метода symbol(). Он формирует контейнер <symbol>, внутри которого можно размещать любые SVG-элементы:

const draw = SVG().addTo('#canvas').size(600, 400)

const icon = draw.symbol()

icon.circle(50).fill('#f06')
icon.rect(80, 30).move(10, 60).fill('#0af')

Ключевые особенности:

  • <symbol> не отображается напрямую на сцене
  • служит шаблоном для последующего использования
  • может содержать сложную структуру: группы, пути, текст

Использование символа через <use>

Для отображения символа применяется метод use(), который создаёт элемент <use> — ссылку на ранее объявленный символ:

draw.use(icon).move(100, 100)
draw.use(icon).move(200, 100).scale(0.5)

Особенности:

  • каждый вызов use() создаёт независимый экземпляр
  • трансформации (move, scale, rotate) применяются к экземпляру, а не к оригиналу
  • исходный символ остаётся неизменным

Повторное использование и оптимизация

Символы позволяют избежать дублирования:

for (let i = 0; i < 5; i++) {
  draw.use(icon).move(i * 100, 200)
}

Преимущества:

  • уменьшение размера SVG-документа
  • ускорение рендеринга
  • централизованное изменение (изменение символа влияет на все экземпляры)

Настройка viewBox символа

Символы могут иметь собственную систему координат через viewbox():

icon.viewbox(0, 0, 100, 100)

Это позволяет:

  • масштабировать символ без искажения
  • использовать единые пропорции
  • удобно проектировать сложные иконки

Изменение содержимого символа

Символ — полноценный контейнер, его можно изменять после создания:

icon.clear()

icon.rect(100, 100).fill('#333')
icon.text('SVG').move(10, 40).fill('#fff')

Все экземпляры автоматически отразят изменения.


Клонирование символов

Иногда требуется создать новый символ на основе существующего:

const icon2 = icon.clone()

icon2.findOne('circle').fill('#0f0')

Отличия:

  • clone() создаёт независимую копию
  • изменения не влияют на оригинал

Вложенные символы

Символ может содержать другие символы:

const base = draw.symbol()
base.circle(40).fill('#ccc')

const complex = draw.symbol()
complex.use(base).move(0, 0)
complex.use(base).move(50, 0)

Это позволяет строить композиции:

  • иконки из базовых элементов
  • повторяющиеся паттерны
  • модульную графику

Работа с атрибутами символов

Символы поддерживают стандартные SVG-атрибуты:

icon.attr({
  id: 'mySymbol',
  preserveAspectRatio: 'xMidYMid meet'
})

Полезные атрибуты:

  • viewBox — область координат
  • preserveAspectRatio — поведение масштабирования
  • id — идентификатор для ссылок

Использование через ID

Помимо объектного API, символ можно использовать через строковый идентификатор:

draw.use('#mySymbol').move(300, 100)

Это удобно при:

  • работе с внешними SVG
  • интеграции с HTML
  • динамической подгрузке ресурсов

Стилизация экземпляров

Каждый экземпляр можно стилизовать независимо:

draw.use(icon)
  .move(100, 100)
  .fill('#ff0')

draw.use(icon)
  .move(200, 100)
  .opacity(0.5)

Важно:

  • стили применяются к <use>, но могут переопределять стили внутри символа
  • не все свойства наследуются одинаково (например, fill может вести себя по-разному в зависимости от структуры)

Ограничения и особенности <use>

  • нельзя напрямую редактировать внутренности символа через <use>
  • события могут вести себя иначе (в зависимости от браузера)
  • некоторые CSS-правила могут не применяться ожидаемым образом

Интерактивность и события

Экземпляры символов поддерживают обработчики событий:

draw.use(icon)
  .move(100, 100)
  .click(() => {
    console.log('clicked')
  })

Однако:

  • событие срабатывает на <use>, а не на оригинальных элементах
  • делегирование может потребовать дополнительной логики

Анимация символов

Анимация может применяться как к символу, так и к экземплярам:

Анимация экземпляра:

draw.use(icon)
  .move(100, 100)
  .animate(1000)
  .move(300, 100)

Анимация внутри символа:

icon.findOne('circle')
  .animate(1000)
  .fill('#00f')

Все экземпляры отразят изменение, если анимация применяется к содержимому символа.


Организация спрайтов

Символы часто используются для создания SVG-спрайтов:

const sprite = draw.defs()

const iconA = sprite.symbol()
iconA.circle(20).fill('red')

const iconB = sprite.symbol()
iconB.rect(40, 40).fill('blue')

Преимущества:

  • централизованное хранение графики
  • удобство повторного использования
  • чистая структура документа

Символы и <defs>

Хотя SVG.js автоматически управляет размещением символов, логически они находятся внутри <defs> — специального контейнера для невидимых элементов.

const defs = draw.defs()
const symbol = defs.symbol()

Практические сценарии использования

Иконки интерфейса:

  • кнопки
  • панели инструментов
  • меню

Графические паттерны:

  • повторяющиеся элементы фона
  • декоративные элементы

Игры и визуализация:

  • персонажи
  • объекты сцены
  • повторяющиеся элементы карты

Управление производительностью

Использование символов:

  • снижает количество DOM-элементов
  • уменьшает объём памяти
  • ускоряет отрисовку при большом количестве повторов

Особенно эффективно при:

  • сотнях и тысячах одинаковых объектов
  • динамическом масштабировании
  • частых обновлениях сцены

Сравнение: symbol vs group

Характеристика symbol group (<g>)
Отображение нет да
Переиспользование да нет (копирование)
Оптимизация высокая средняя
Управление централизованное локальное

Комбинирование с другими возможностями SVG.js

Символы хорошо сочетаются с:

  • масками (mask)
  • клип-путями (clipPath)
  • градиентами (gradient)
  • фильтрами (filter)

Пример:

const gradient = draw.gradient('linear', add => {
  add.stop(0, '#f06')
  add.stop(1, '#0af')
})

icon.rect(100, 100).fill(gradient)

Частые ошибки

1. Попытка отобразить symbol напрямую

draw.add(icon) // не работает

2. Изменение экземпляра вместо символа

draw.use(icon).findOne('circle') // не даст доступа к содержимому

3. Игнорирование viewBox

Приводит к неправильному масштабированию.


Рекомендации по организации

  • хранить символы в defs
  • использовать осмысленные id
  • задавать viewBox для всех символов
  • избегать сложной вложенности без необходимости
  • использовать клоны для вариаций

Расширенные техники

Динамическое создание символов:

function createIcon(color) {
  const s = draw.symbol()
  s.circle(50).fill(color)
  return s
}

Параметризация через data-атрибуты:

const use = draw.use(icon)
use.data('type', 'primary')

Комбинирование с шаблонами:

  • генерация UI-компонентов
  • динамическая отрисовка списков
  • визуализация данных

Внутреннее устройство <use>

Элемент <use>:

  • ссылается на href или xlink:href
  • не копирует DOM, а создаёт ссылку
  • браузер рендерит его как “проекцию” оригинала

Это объясняет:

  • высокую производительность
  • ограничения на доступ к внутренним элементам
  • особенности стилизации

Поддержка браузерами

Символы и <use> поддерживаются всеми современными браузерами. Однако:

  • старые версии могут требовать xlink:href
  • различия в обработке CSS возможны

SVG.js сглаживает часть этих различий.


Итерация по экземплярам

Можно хранить ссылки на экземпляры:

const instances = []

for (let i = 0; i < 10; i++) {
  instances.push(draw.use(icon).move(i * 50, 100))
}

Позволяет:

  • массово управлять объектами
  • реализовывать анимации
  • обновлять состояние

Связь с компонентным подходом

Символы логически близки к компонентам:

  • один источник истины
  • множественные инстансы
  • независимые трансформации

Это делает их удобными для:

  • UI-библиотек
  • визуальных редакторов
  • систем дизайна