Use для клонирования

Векторная графика в SVG поддерживает повторное использование уже созданных элементов без их дублирования в DOM. Для этого применяется механизм ссылок, реализуемый через элемент <use>. В библиотеке SVG.js этот механизм представлен удобным API для клонирования и переиспользования объектов.

В отличие от прямого копирования (clone()), <use> создаёт ссылку на исходный элемент. Это означает:

  • изменения в оригинале автоматически отражаются во всех его экземплярах;
  • уменьшается объём DOM;
  • повышается производительность при работе с большим количеством одинаковых объектов.

Создание элемента и его повторное использование

Исходный элемент создаётся обычным способом, после чего используется метод use():

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

const rect = draw.rect(100, 100).fill('#3498db')

const copy1 = draw.use(rect).move(120, 0)
const copy2 = draw.use(rect).move(0, 120)
const copy3 = draw.use(rect).move(120, 120)

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

  • rect остаётся единственным исходным объектом
  • copy1, copy2, copy3 — ссылки, а не полноценные копии
  • все экземпляры визуально независимы по позиции, но зависимы по содержимому

Внутренний механизм работы

Метод use() создаёт SVG-элемент <use>, который содержит ссылку на оригинал через атрибут href (или xlink:href в старых версиях SVG):

<use href="#SvgjsRect1001" x="120" y="0"></use>

Сам оригинал автоматически получает уникальный id, если он не задан вручную.


Отличие use() от clone()

Характеристика use() clone()
Тип копии Ссылка Полная копия
Связь с оригиналом Да Нет
Изменения оригинала Отражаются Не отражаются
Производительность Выше Ниже
Использование памяти Меньше Больше

Пример сравнения:

const original = draw.circle(50).fill('red')

// ссылка
const ref = draw.use(original).move(100, 0)

// копия
const clone = original.clone().move(200, 0)

original.fill('blue')

Результат:

  • ref станет синим
  • clone останется красным

Применение трансформаций

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

const star = draw.polygon('50,0 21,90 98,35 2,35 79,90').fill('gold')

draw.use(star).move(100, 0)
draw.use(star).move(200, 0).scale(0.5)
draw.use(star).move(300, 0).rotate(45)

Трансформации применяются к <use>, а не к оригиналу.


Изменение оригинала и влияние на копии

Любые изменения исходного элемента автоматически распространяются:

const circle = draw.circle(80).fill('green')

const a = draw.use(circle).move(100, 0)
const b = draw.use(circle).move(200, 0)

circle.fill('orange')

После изменения:

  • оба элемента (a, b) изменят цвет на оранжевый

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

Важно учитывать:

  • стили, заданные у оригинала, применяются ко всем экземплярам
  • стили, заданные у <use>, могут переопределять оригинал

Пример:

const rect = draw.rect(100, 100).fill('blue')

const u1 = draw.use(rect).move(120, 0)
const u2 = draw.use(rect).move(240, 0).fill('red')

Результат:

  • u1 — синий
  • u2 — красный (локальное переопределение)

Использование с группами

use() работает не только с примитивами, но и с группами:

const group = draw.group()
group.circle(50).fill('red')
group.rect(50, 50).move(25, 25).fill('blue')

draw.use(group).move(100, 0)
draw.use(group).move(200, 0)

Это особенно полезно для сложных составных объектов.


Оптимизация через <defs>

Часто используемые элементы можно помещать в <defs>:

const defs = draw.defs()

const shape = defs.rect(80, 80).fill('#2ecc71')

draw.use(shape).move(50, 50)
draw.use(shape).move(150, 150)

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

  • элементы не отображаются напрямую
  • используются только через <use>
  • структура SVG становится чище

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

1. Нельзя напрямую изменить содержимое <use>

const u = draw.use(rect)

// не сработает как ожидается
u.fill('red')

Причина: <use> не содержит реального содержимого, а лишь ссылается на него.


2. События

События можно навешивать как на оригинал, так и на <use>:

rect.on('click', () => console.log('original'))

const u = draw.use(rect)
u.on('click', () => console.log('use'))

3. Работа с ID

Если оригинал удаляется, <use> теряет ссылку:

rect.remove()

После этого:

  • все <use> перестанут отображаться

Практический пример: сетка объектов

const tile = draw.rect(40, 40).fill('#9b59b6')

for (let x = 0; x < 5; x++) {
  for (let y = 0; y < 5; y++) {
    draw.use(tile).move(x * 50, y * 50)
  }
}

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

  • создаётся 25 элементов
  • в DOM хранится только один исходный объект

Комбинирование с анимацией

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

const circle = draw.circle(50).fill('blue')

draw.use(circle).move(100, 0).animate().rotate(360, true)
draw.use(circle).move(200, 0).animate().scale(1.5)

Каждый <use> анимируется независимо.


Когда использовать use()

Оптимальные сценарии:

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

Когда не подходит:

  • требуется независимое изменение каждого элемента
  • необходима полная изоляция объектов

Сравнение подходов на практике

// НЕэффективно
for (let i = 0; i < 1000; i++) {
  draw.circle(10).move(i * 2, 0)
}

// эффективно
const dot = draw.circle(10)

for (let i = 0; i < 1000; i++) {
  draw.use(dot).move(i * 2, 0)
}

Во втором случае:

  • меньше DOM-узлов
  • быстрее рендеринг
  • проще управление

Выводы по механике use()

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