Позиционирование маркеров

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

В библиотеке SVG.js работа с маркерами упрощается за счёт абстракций, позволяющих не взаимодействовать напрямую с <marker> и <defs>, а использовать декларативный API.

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


Базовая модель позиционирования

Маркер в SVG определяется отдельным контейнером с собственной системой координат. При привязке к элементу учитываются:

  • точка привязки (начало, конец, вершины)
  • ориентация
  • смещение внутри маркера
  • масштабирование

В SVG.js базовый пример создания маркера:

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

const marker = draw.marker(10, 10, function(add) {
  add.path('M0,0 L10,5 L0,10 Z').fill('#000')
})

const line = draw.line(50, 50, 200, 200)
line.marker('end', marker)

Точки привязки маркеров

SVG поддерживает три типа позиционирования маркеров:

  • start — начало пути
  • mid — промежуточные точки (например, вершины polyline)
  • end — конец пути

В SVG.js это задаётся методом .marker(position, marker):

line.marker('start', marker)
line.marker('mid', marker)
line.marker('end', marker)

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

  • mid применяется ко всем внутренним вершинам
  • для path маркеры размещаются в узлах сегментов
  • порядок точек напрямую влияет на визуальное расположение

Управление точкой привязки внутри маркера

Каждый маркер имеет параметры:

  • refX
  • refY

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

В SVG.js:

const marker = draw.marker(10, 10, function(add) {
  add.circle(10).fill('red')
}).ref(5, 5)

Ключевые моменты:

  • (0,0) — левый верх маркера
  • изменение refX/refY смещает маркер относительно линии
  • центрирование часто требует половины размеров маркера

Пример центрированной стрелки:

marker.ref(10, 5)

Ориентация маркера

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

SVG.js поддерживает:

marker.orient('auto')

или

marker.orient('auto-start-reverse')

Различия:

  • auto — направление по касательной линии
  • auto-start-reverse — автоматически переворачивает маркер в начале

Это особенно важно для стрелок:

line.marker('start', marker)
line.marker('end', marker)
marker.orient('auto-start-reverse')

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

Размер маркера может зависеть от толщины линии или быть фиксированным.

Параметр:

  • markerUnits

В SVG.js:

marker.units('strokeWidth')

или

marker.units('userSpaceOnUse')

Различия:

strokeWidth

  • маркер масштабируется вместе с толщиной линии
  • подходит для стрелок

userSpaceOnUse

  • фиксированный размер
  • не зависит от stroke-width

Пример:

line.stroke({ width: 5 })
marker.units('strokeWidth')

Смещение и выравнивание

Даже при правильной точке привязки часто требуется дополнительное смещение.

Это достигается комбинацией:

  • refX / refY
  • размеров маркера
  • геометрии фигуры внутри маркера

Пример тонкой настройки стрелки:

const marker = draw.marker(20, 20, function(add) {
  add.path('M0,0 L20,10 L0,20 Z').fill('#000')
}).ref(20, 10)

Здесь:

  • острие стрелки находится в точке (20,10)
  • именно оно совмещается с концом линии

Позиционирование на сложных путях

Для path с кривыми (C, Q, A) маркеры размещаются:

  • в начальной точке
  • в конечной точке
  • в узлах сегментов

SVG.js не требует ручного расчёта касательных — браузер автоматически определяет угол.

Пример:

const path = draw.path('M50,200 C150,50 250,350 350,200')
path.marker('end', marker)

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

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

Маркеры на полилиниях и полигонах

Для polyline:

const polyline = draw.polyline('50,150 150,50 250,150 350,50')
polyline.marker('mid', marker)

Поведение:

  • маркер размещается в каждой вершине
  • ориентация определяется углом между сегментами

Для polygon:

  • аналогично polyline
  • последняя точка соединяется с первой

Инверсия и зеркалирование

При использовании одного маркера для начала и конца линии может возникнуть проблема направления.

Решения:

1. auto-start-reverse

marker.orient('auto-start-reverse')

2. отдельные маркеры

line.marker('start', startMarker)
line.marker('end', endMarker)

Использование transform внутри маркера

Внутренние элементы маркера можно трансформировать:

const marker = draw.marker(20, 20, function(add) {
  add.path('M0,0 L20,10 L0,20 Z')
    .fill('black')
    .transform({ scale: 0.8, translateX: 2 })
})

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

  • корректировать визуальное выравнивание
  • компенсировать refX/refY
  • создавать сложные формы

Контроль области видимости (viewBox)

Маркеры используют собственный viewBox, влияющий на масштабирование и позиционирование.

В SVG.js:

marker.viewbox(0, 0, 20, 20)

Связь параметров:

  • viewBox определяет внутреннюю систему координат
  • width/height — внешний размер
  • refX/refY — позиция привязки внутри viewBox

Практический шаблон стрелки

Часто используемый паттерн:

const arrow = draw.marker(10, 10, function(add) {
  add.path('M0,0 L10,5 L0,10 Z').fill('#000')
})
  .ref(10, 5)
  .orient('auto')
  .units('strokeWidth')

const line = draw.line(50, 50, 300, 50)
line.stroke({ width: 2 })
line.marker('end', arrow)

Ключевые параметры:

  • ref(10,5) — острие стрелки
  • orient('auto') — направление
  • units('strokeWidth') — масштабирование

Типичные проблемы позиционирования

Смещение маркера от линии

Причины:

  • неверный refX/refY
  • неправильный viewBox

Решение:

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

Неправильное направление

Причины:

  • отсутствует orient('auto')
  • несовпадение формы стрелки

Несоответствие масштаба

Причины:

  • использование userSpaceOnUse
  • несоответствие viewBox и размеров

Дублирование маркеров в вершинах

Для mid:

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

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

Динамическое изменение позиции

line.plot(100, 100, 300, 300)

Маркеры автоматически пересчитываются.


Анимация

line.animate().plot(50, 200, 300, 50)

Маркеры следуют за геометрией.


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

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

line1.marker('end', arrow)
line2.marker('end', arrow)

Это снижает нагрузку и упрощает управление стилями.


Влияние порядка трансформаций

Если линия трансформируется:

line.rotate(45)
  • маркер наследует ориентацию
  • позиционирование происходит после трансформации

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

Если SVG вложен в группы (<g>):

const group = draw.group()
group.add(line)
  • маркеры остаются в глобальной системе
  • но визуально следуют трансформациям группы

Итоговая структура позиционирования

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

  • геометрии линии
  • точки привязки (start/mid/end)
  • refX/refY
  • orient
  • markerUnits
  • viewBox
  • трансформаций

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