Как нарисовать соединяющую линию между двумя элементами? HTML

Возникла проблема , у меня имеются 2 блока , которые с помощью Drag and Drop можно перемещать по странице.

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

Что лучше для этого использовать svg или canvas?

Как можно это реализовать?


Ответы (2 шт):

Автор решения: Александр Лесив

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

  1. Вот в этом приложении для визуализации схем баз данных, которое использует для отрисовки линий canvas, при изменении хотя бы одной линии перерисовывается весь холст, поскольку canvas не очень подходит для таких вещей.
  2. А вот в этом для отрисовки линий используется svg и мы можем наблюдать плавное изменение линий при перемещении элементов. Поэтому я рекомендовал бы вам использовать именно svg (хотя зависит конечно от вашего приложения).

Общий алгоритм создания линий с помощью svg такой:

  1. При создании приложения (или той его части, где будут отрисовываться лини) создаем на заднем фоне svg-контейнер.

const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");

// Это просто пример родительского элемента
const parent = document.body;

parent.append(svg);

  1. Для создание линии между двумя точками нужно создать svg-элемент отвечающий за это и добавить его в svg-контейнер

// Создает прямую линию. Думаю также подойдут полилиния или кривые Безье
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');

// Установит точки конца и начала на 0 относительно svg-контейнера в котором будет находится линия
// Если это не линия, а полилиния и кривая, то нужно будет устанавливать атрибуты соответствующие типу элементу. Подробнее здесь https://developer.mozilla.org/ru/docs/Web/SVG/%D0%AD%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82
line.setAttribute('x1', 0);
line.setAttribute('y1', 0);
line.setAttribute('x2', 0);
line.setAttribute('y2', 0);

svgContainer.append(line);

  1. Каждый раз при обновлении координат точек, к которым привязана линия устанавливать новые значения для атрибутов линии с помощью метода DOM-элементов setAttribute. Думаю для наблюдения за изменениями в элементах подойдет MutationObserver, но я реализовывал это с помощью собственных событий. В любом случае решать вам.

Удачи

→ Ссылка
Автор решения: ᅠhᅠ

Два элемента

const el1 = document.getElementById('el1')
const el2 = document.getElementById('el2')
const size = 50

const canvas = document.getElementById('canvas')
const context = canvas.getContext('2d')
let width = canvas.width = innerWidth
let height = canvas.height = innerHeight

/*------------------------------------*/

let current = null
const elements = {
  el1: {
    x: Math.random() * (width - size),
    y: Math.random() * (height - size),
    startX: 0,
    startY: 0
  },

  el2: {
    x: Math.random() * (width - size),
    y: Math.random() * (height - size),
    startX: 0,
    startY: 0
  }
}

// начальное положение
translate(el1, elements.el1.x, elements.el1.y)
translate(el2, elements.el2.x, elements.el2.y)
drawLine(
  elements.el1.x,
  elements.el2.x,
  elements.el1.y,
  elements.el2.y
)

/*------------------------------------*/

el1.addEventListener('mousedown', onMouseDown)
el2.addEventListener('mousedown', onMouseDown)


function onMouseDown(e) {
  e.preventDefault()
  // координаты нажатия мыши внутри элемента
  elements[e.target.id].startX = e.x - elements[e.target.id].x
  elements[e.target.id].startY = e.y - elements[e.target.id].y

  current = e.target

  document.body.addEventListener('mousemove', onMouseMove)
  document.body.addEventListener('mouseup', onMouseUp)
}

function onMouseMove(e) {
  const x = elements[current.id].x = e.x - elements[current.id].startX
  const y = elements[current.id].y = e.y - elements[current.id].startY

  translate(current, x, y)
  drawLine(
    elements.el1.x,
    elements.el2.x,
    elements.el1.y,
    elements.el2.y
  )
}

function onMouseUp() {
  document.body.removeEventListener('mousemove', onMouseMove)
  document.body.removeEventListener('mouseup', onMouseUp)
}

/*------------------------------------*/

function translate(el, x, y) {
  el.style.transform = `translate(${x}px, ${y}px)`
}

function drawLine(x1, x2, y1, y2) {
  context.clearRect(0, 0, width, height)
  context.beginPath()
  // из центра квадрата
  context.moveTo(x1 + size / 2, y1 + size / 2)
  // в центр другого квадрата
  context.lineTo(x2 + size / 2, y2 + size / 2)
  context.stroke()
}
html,
body {
  margin: 0;
}

canvas {
  display: block;
}

div {
  position: absolute;
  width: 50px;
  height: 50px;
}

div:nth-of-type(1) {
  background-color: tomato;
}

div:nth-of-type(2) {
  background-color: lightgreen;
}
<div id="el1"></div>
<div id="el2"></div>

<canvas id="canvas"></canvas>

Много элементов

const canvas = document.getElementById('canvas')
const context = canvas.getContext('2d')
let width = canvas.width = innerWidth
let height = canvas.height = innerHeight

/*------------------------------------*/

const elements = {}
const num = 7
const size = 50
let current = null

for (let i = 0; i < num; i++) {
  // создание dom-элемента
  const element = document.createElement('div')
  element.addEventListener('mousedown', onMouseDown)
  const id = 'el' + i
  element.id = id
  document.body.prepend(element)

  // тут будут храниться и изменяться все его координаты
  elements[id] = {
    x: Math.random() * (width - size),
    y: Math.random() * (height - size),
    startX: 0,
    startY: 0
  }

  // начальное положение
  translate(element, elements[id].x, elements[id].y)
}

// соединяем линиями
connect(elements)

/*------------------------------------*/

function onMouseDown(e) {
  e.preventDefault()
  // координаты нажатия мыши внутри элемента
  elements[e.target.id].startX = e.x - elements[e.target.id].x
  elements[e.target.id].startY = e.y - elements[e.target.id].y

  current = e.target

  document.body.addEventListener('mousemove', onMouseMove)
  document.body.addEventListener('mouseup', onMouseUp)
}

function onMouseMove(e) {
  const x = elements[current.id].x = e.x - elements[current.id].startX
  const y = elements[current.id].y = e.y - elements[current.id].startY

  translate(current, x, y)
  connect(elements)
}

function onMouseUp() {
  document.body.removeEventListener('mousemove', onMouseMove)
  document.body.removeEventListener('mouseup', onMouseUp)
}

/*------------------------------------*/

function translate(el, x, y) {
  el.style.transform = `translate(${x}px, ${y}px)`
}

function connect(elements) {
  context.clearRect(0, 0, width, height)

  for (let i = 0; i < num - 1; i++) {
    drawLine(
      elements['el' + i].x,
      elements['el' + (i + 1)].x,
      elements['el' + i].y,
      elements['el' + (i + 1)].y
    )
  }
}

function drawLine(x1, x2, y1, y2) {
  context.beginPath()
  // из центра квадрата
  context.moveTo(x1 + size / 2, y1 + size / 2)
  // в центр другого квадрата
  context.lineTo(x2 + size / 2, y2 + size / 2)
  context.stroke()
}

/*------------------------------------*/

onresize = () => {
  width = canvas.width = innerWidth
  height = canvas.height = innerHeight
  connect(elements)
}
html,
body {
  margin: 0;
}

canvas {
  display: block;
}

div {
  position: absolute;
  width: 50px;
  height: 50px;
  background-color: tomato;
}
<canvas id="canvas"></canvas>

→ Ссылка