Нарисовать стрелки между DOM элементами

На прошлой неделе занимался задачей реализии стрелок для функциональности onboarding (coach marks).

arrow-prototype.png

Для себя определил следующие требования:

  • Декларативность API. Хочется иметь возможность указать 2 DOM объекта для отрисовки стрелки
  • Использование кривых Безье
  • Возможность настраивать внешний вид
  • Возможность коррекции точек начала и конца стрелки
  • Чистый JS или Angular библиотека

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

Интересный момент заключается в том, что хорошая библиотека под эти требования сходу не гуглится. Косвенно это подтверждает множество (больше 20) решений на github с 0-10 звёздами, где авторы пытались решить подобную задачу. Альтернативное находится много ответов как самостоятельно реализовать SVG стрелки (один, два).

Рассмотренные варианты

Почти сходу находится react-archer. Решение хорошее, но легко встроить в Angular не представляется возможным.

swoopy

swoopy и подход bloomberg.

<div data-arrow-target="#a">A</div>
<div id="a">B</div>
<div data-arrow-target="#c">C</div>
<div id="c">D</div>

<!-- dependency -->
<script
  src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"
  charset="utf-8"
></script>
<!-- library -->
<script src="/src/libs/swoopy/swoopyArrow.js"></script>
<script src="/src/libs/swoopy/arrowConnector.js"></script>
<style type="text/css">
  path.arrow-connector,
  #arrowhead {
    fill: none;
    stroke: black;
  }
</style>
<script>
  // configure arrows
  const connect = arrowConnector()
    .type("swoopy") // options: 'straight', 'swoopy', 'kooky', 'loopy', 'random'
    .edges(true) // allow arrows to attach to box edge midpoints (it'll pick closest pair)
    .corners(false); // allow arrows to attach to box corners (it'll pick closest pair)
  // draw arrows
  connect();
</script>

Имеет зависимость от библиотеки d3. Внешний вид настраивается через d3. Непосредственно соединительные стрелки между DOM элементами не имеют настроек или плохо документированы.

domarrow

domarrow работает на веб компоненте внутри которого div.

Атрибуты компонента connection

  • head, tail - концы стрелок
  • fromX, fromY, toX, toY - значение от 0 до 1 положение стрелки на контейнере
<div id="a">A</div>
<div id="b">B</div>
<div id="c">C</div>

<link rel="stylesheet" href="/src/libs/domarrow/domarrow.css" />
<connection
  from="#a"
  to="#b"
  color="red"
  tail
  fromX="0.8"
  fromY="1.2"
  toX="-0.1"
  toY="0.25"
></connection>
<connection
  from="#a"
  to="#c"
  color="green"
  fromX="1"
  tail
  onlyVisible
></connection>

Работать на порядок проще, чем с swoopy. Не получится сделать кривые безье без переписываения на svg.

arrows-svg

arrows-svg достаточно проработанное решение на SVG, в проекте есть тесты. API удовлетворяет требованиям.

<div id="a">A</div>
<div id="b">B</div>

<script src="/src/libs/arrows-svg/arrows.js"></script>
<script>
  const arrow = arrowCreate({
    className: "arrow",
    from: {
      direction: "bottom",
      node: document.getElementById("a"),
      translation: [0, 1]
    },
    to: {
      direction: "top",
      node: document.getElementById("b"),
      translation: [0.1, -0.5]
    }
  });
  document.body.appendChild(arrow.node);
</script>
<style>
  .arrow {
    pointer-events: none;
  }

  .arrow__path {
    stroke: white;
    fill: transparent;
    width: 4px;
    stroke-width: 3px;
  }

  .arrow__head line {
    stroke: white;
    stroke-width: 3px;
  }
</style>

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

leader-line

leader-line - one love

<div id="a">A</div>
<div id="b">B</div>
<div id="c">C</div>
<div id="d">D</div>

<script src="/src/libs/leader-line/leader-line.min.js"></script>
<script>
  new LeaderLine(
    LeaderLine.pointAnchor(document.getElementById("a"), { x: "110%" }),
    LeaderLine.pointAnchor(document.getElementById("b"), { x: 50, y: -20 }),
    {
      color: "white",
      size: 3,
      startSocket: "right",
      endSocket: "top"
    }
  );
  new LeaderLine(document.getElementById("c"), document.getElementById("d"), {
    color: "white",
    size: 3
  });
</script>

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

Результат

Для реализации выбран leader-line

Похожие записи

+function ($) { "use strict"; }(window.jQuery);

  • объявляется IIFE (немедленно выполняемая функция-выражение)
  • в функцию передаётся объект библиотеки jQuery, которая внутри будет доступна через переменную с именем $
  • включается «строгий режим»

Поисковый запрос с помощью RxJS

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

HTML атрибуты и DOM свойства

  • Значение HTML атрибута указывает начальное значение;
  • Значение DOM свойства является текущим значением;
  • Атрибуты инициализируют DOM свойства.

RxJS. Delay from array

import { of, from } from 'rxjs'; 
import { map, concatMap, delay } from 'rxjs/operators';

from([2,4,6,8]).pipe(
  concatMap(item => of(item).pipe(delay(1000)))
).subscribe(console.log);