Как нарисовать соединяющую линию между двумя элементами? HTML
Возникла проблема , у меня имеются 2 блока , которые с помощью Drag and Drop можно перемещать по странице.
Не могу придумать как реализовать прямую линию между ними, которая будет перерисовываться в зависимости от положения блоков.
Что лучше для этого использовать svg или canvas?
Как можно это реализовать?
Ответы (2 шт):
Автор решения: Александр Лесив
→ Ссылка
Когда я делал приложение для визуализации графов, я столкнулся с такой же проблемой. Сначала, я посмотрел на уже существующие решения похожих по функционалу продуктов.
- Вот в этом приложении для визуализации схем баз данных, которое использует для отрисовки линий canvas, при изменении хотя бы одной линии перерисовывается весь холст, поскольку canvas не очень подходит для таких вещей.
- А вот в этом для отрисовки линий используется svg и мы можем наблюдать плавное изменение линий при перемещении элементов. Поэтому я рекомендовал бы вам использовать именно svg (хотя зависит конечно от вашего приложения).
Общий алгоритм создания линий с помощью svg такой:
- При создании приложения (или той его части, где будут отрисовываться лини) создаем на заднем фоне svg-контейнер.
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
// Это просто пример родительского элемента
const parent = document.body;
parent.append(svg);
- Для создание линии между двумя точками нужно создать 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);
- Каждый раз при обновлении координат точек, к которым привязана линия устанавливать новые значения для атрибутов линии с помощью метода 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>