大家好。请告诉我从代码组织的角度来看,如何实现拖放元素以及在拖动元素之间绘制链接。
我部分设法实现了这一目标,但是遇到了性能或焦点损失的问题(我想)。
在阅读article并理解了拖放的基本知识之后,我想在组件之间创建链接,但是有一个困难:在拖动元素并同时绘制链接线时,如果拖动元素从焦点飞出并掉到了画布上。
以下是常规逻辑:
let svg = document.querySelector('svg');
svg.addEventListener('mousedown', startDrag);
svg.addEventListener('mousemove', drag);
svg.addEventListener('mouseup', endDrag);
svg.addEventListener('mouseleave', endDrag);
let selectedElement = null;
function startDrag(evt) {
// select element evt.target
// search all need lines communication with this element
}
function drag(evt) {
// set coords selectedElement (x, y) by mouse position with offeset
// set coords need LINES, BUT have problem (;,,,;)(\/
}
function endDrag(evt) {
selectedElement = false;
// clear temp map object line with
}
我非常感谢您的帮助,并考虑如何紧凑地介绍问题所在
我录制了一个视频,但是直到那时我才意识到,没有翻译者https://youtu.be/y5cdIWu7_qQ,我什么也说不出来。
我非常希望您能理解我...
// Перетаскивание элементов
(function () {
let $document = $(document);
const $svg = $('#main .canvas-svg'); // SVG
// PC
$document.on('mousedown', $svg, startDrag);
$document.on('mousemove', $svg, drag);
$document.on('mouseup', $svg, endDrag);
$document.on('mouseleave', $svg, endDrag);
// MOBILE
$document.on('touchstart', $svg, startDrag);
$document.on('touchmove', $svg, drag);
$document.on('touchend', $svg, endDrag);
$document.on('touchleave', $svg, endDrag);
$document.on('touchcancel', $svg, endDrag);
let selectedElement = false,
offsetElement,
transformElement;
let selectDrawLineChanel = false; // текущая созданная (рисующая) линия при создании канал связи
let mapMoveLineChanel = false; // список перемещаемых линий (связей) при перетаскивании блока
function startDrag(e) {
if (e.target.classList.contains('output-chanel')) { // при наведении на точку канала
selectDrawLineChanel = document.createElementNS('http://www.w3.org/2000/svg', 'line');
//initialiseDragging(e, selectDrawLineChanel);
let outputChanel = e.target;
let groupParent = e.target.closest('.draggable-group');
let positionChanelOutput = getAbsolutePositionElement(outputChanel, outputChanel.cx.baseVal.value, outputChanel.cy.baseVal.value);
console.log(groupParent);
let attrsLine = {
x1: positionChanelOutput.x,
y1: positionChanelOutput.y,
dataNameDeviceOutput: groupParent.getAttributeNS(null, 'data-name-device'),
dataNameChanelOutput: outputChanel.getAttributeNS(null, 'data-name-chanel'),
strokeWidth: 2,
stroke: '#000',
strokeLinecap : 'round',
};
setAttrElement(selectDrawLineChanel, attrsLine, null);
let groupChanelLine = activeSVG.querySelector('.chanel-lines');
groupChanelLine.appendChild(selectDrawLineChanel);
console.log('startDrawLine');
} else if (e.target.classList.contains('draggable')) { // при наведении на элемент
selectedElement = e.target;
initialiseDragging(e, selectedElement);
console.log('startDragElement')
} else if (e.target.parentNode.classList.contains('draggable-group')) { // при нажатии на устройство при перемещении
selectedElement = e.target.parentNode;
initialiseDragging(e, selectedElement);
// поиск всех линий, относящахся к данному блоку при перемешении самого блока
let nameDevice = selectedElement.getAttributeNS(null,'data-name-device'); // определяем имя девайса
let groupChanelLines = activeSVG.querySelector('.chanel-lines');// ищем контейнер всех линий
let selectMoveArrayLineChanel = groupChanelLines.querySelectorAll('[data-name-device-output],[data-name-device-input]'); // найти все линии с содержащие data-name-device-output | data-name-device-input внутри контейнера
if (selectMoveArrayLineChanel.length) { // если нашел линии, то есть смысл дальше идти
mapMoveLineChanel = new Map(); // объект мап line (lineChanel) - circle (chanelOutput | chanelInput)
for (let i = 0; i < selectMoveArrayLineChanel.length; i++){ // перебираем линии
let itemLine = selectMoveArrayLineChanel[i]; // одна линия
if(itemLine.getAttributeNS(null, 'data-name-device-output') === nameDevice){ // поиск в output
let nameChanelOutput = itemLine.getAttributeNS(null, 'data-name-chanel-output'); // достаем из аттрибута линии название канала
let circleChanelOutput = selectedElement.querySelector(`.chanel-list-output [data-name-chanel="${nameChanelOutput}"]`); // находим окружность
mapMoveLineChanel.set(itemLine, circleChanelOutput);
console.log('add MAP output');
}else if(itemLine.getAttributeNS(null, 'data-name-device-input') === nameDevice){ // поиск в input
let nameChanelInput = itemLine.getAttributeNS(null, 'data-name-chanel-input'); // ищем порядковый номер канала
let circleChanelInput = selectedElement.querySelector(`.chanel-list-input [data-name-chanel="${nameChanelInput}"]`); // поиск окружности канала
mapMoveLineChanel.set(itemLine, circleChanelInput);
console.log('add MAP input');
}
}
}
console.log('startDragElement');
}
}
function drag(e) {
e.preventDefault();
if(selectDrawLineChanel){ // рисуем линию за курсором мыши
let coord = getMousePosition(e);
setAttrElement(selectDrawLineChanel, {
x2: coord.x,
y2: coord.y
});
//console.log('drawLine: x =',coord.x, 'offsetElementX =', offsetElement.x, 'y =', coord.y, 'offsetElementY =', offsetElement.y);
} else if (selectedElement) { // при перетаскивании элемента
let coord = getMousePosition(e);
transformElement.setTranslate(coord.x - offsetElement.x, coord.y - offsetElement.y); // переносим сам элемент
/* PROBLEM HERE -----------------> */
if(mapMoveLineChanel){ // перебираем все линии с их каналами относящиеся к перетаскиваему блоку
mapMoveLineChanel.forEach((circle, line, map) => {
let coordsCircle = getAbsolutePositionElement(circle, circle.getAttributeNS(null, 'cx'), circle.getAttributeNS(null, 'cy'));
setAttrElement(line, { // init new coords for line by position circle-chanel
x1:coordsCircle.x,
y1:coordsCircle.y,
});
console.log(line, coordsCircle, mapMoveLineChanel);
});
}
/* <----------------- PROBLEM HERE */
console.log('drag');
}
}
function endDrag(e) {
if (selectDrawLineChanel) {
console.log(e.target);
if (e.target.classList.contains('input-chanel')) {
let outputChanel = e.target;
let groupParent = e.target.closest('.draggable-group');
let cx = outputChanel.cx.baseVal.value;
let cy = outputChanel.cy.baseVal.value;
let cc = getAbsolutePositionElement(outputChanel, cx, cy);
setAttrElement(selectDrawLineChanel, {
x2: cc.x,
y2: cc.y,
dataNameDeviceInput: groupParent.getAttributeNS(null, 'data-name-device'), // сохраняем имя девайса
dataNameChanelInput: e.target.getAttributeNS(null, 'data-name-chanel') // сохраняем порядковый номер (ид) канала в данном девайсе
});
selectDrawLineChanel = false;
console.log('endDrawLine (ok)', cc.cx, cc.cy, e.target);
} else {
selectDrawLineChanel.remove();
selectDrawLineChanel = false;
console.log('endDrawLine (cancel)');
}
}
if(selectedElement){
selectedElement = false;
console.log('mapMoveLineChanel =', mapMoveLineChanel);
mapMoveLineChanel = false;
console.log('endDragElement');
}
}
function getAbsolutePositionElement(elem, x, y) {
let svg = elem.ownerSVGElement;
// Get the cx and cy coordinates
let pt = svg.createSVGPoint();
pt.x = x;
pt.y = y;
while (true) {
// Get this elements transform
let transform = elem.transform.baseVal.consolidate();
// If it has a transform, then apply it to our point
if (transform) {
let matrix = elem.transform.baseVal.consolidate().matrix;
pt = pt.matrixTransform(matrix);
}
// If this element's parent is the root SVG element, then stop
if (elem.parentNode === svg)
break;
// Otherwise step up to the parent element and repeat the process
elem = elem.parentNode;
}
return pt;
}
function setAttrElement(element, attrs, namespace = null) {
for (let attrName in attrs) {
let snakeAttrName = attrName.replace(/([A-Z])/g, "-$1").toLowerCase();
element.setAttributeNS(namespace, snakeAttrName, attrs[attrName]);
}
return element;
}
function convertCoords(element, x, y) {
let offset = activeSVG.getBoundingClientRect();
let matrix = element.getScreenCTM();
return {
x: (matrix.a * x) + (matrix.c * y) + matrix.e - offset.left,
y: (matrix.b * x) + (matrix.d * y) + matrix.f - offset.top
};
}
function getMousePosition(e) {
let CTM = activeSVG.getScreenCTM(); // Возвращает DOMMatrix, представляющую матрицу, которая преобразует систему координат текущего элемента в систему координат окна просмотра SVG для фрагмента документа SVG.
if (e.touches) { e = e.touches[0]; } // iPhone
return {
x: (e.clientX - CTM.e) / CTM.a,
y: (e.clientY - CTM.f) / CTM.d
}
}
// инициализация положения перетаскиваемого элемента (блока)
function initialiseDragging(e, element) {
offsetElement = getMousePosition(e);
// Make sure the first transformElement on the element is a translate transformElement
let transforms = element.transform.baseVal;
if (transforms.length === 0 || transforms.getItem(0).type !== SVGTransform.SVG_TRANSFORM_TRANSLATE) {
// Create an transformElement that translates by (0, 0)
let translate = activeSVG.createSVGTransform();
translate.setTranslate(0, 0);
element.transform.baseVal.insertItemBefore(translate, 0);
}
// Get initial translation
transformElement = transforms.getItem(0);
offsetElement.x -= transformElement.matrix.e;
offsetElement.y -= transformElement.matrix.f;
}
})();