#javascript #css #svg #dom-events
#javascript #css #svg #dom-события
Вопрос:
У меня есть SVG карта и простой плагин, который добавляет функции масштабирования и перетаскивания.
<svg>
<g class="main-container draggable" transform="matrix(1 0 0 1 0 0)">
<path id="AT-1" title="Burgenland" class="land" d=".../>
</g>
</svg>
const maxScale = 5,
minScale = 0.15;
var selected,
scale = 1,
svg = document.querySelector('svg');
function beginDrag(e) {
e.stopPropagation();
let target = e.target;
if (target.classList.contains('draggable')) {
selected = target;
} else {
selected = document.querySelector('.main-container');
}
selected.dataset.startMouseX = e.clientX;
selected.dataset.startMouseY = e.clientY;
}
function drag(e) {
if (!selected) return;
e.stopPropagation();
let startX = parseFloat(selected.dataset.startMouseX),
startY = parseFloat(selected.dataset.startMouseY),
dx = (e.clientX - startX),
dy = (e.clientY - startY);
if (selected.classList.contains('draggable')) {
let selectedBox = selected.getBoundingClientRect(),
boundaryBox = selected.parentElement.getBoundingClientRect();
if (selectedBox.right dx > boundaryBox.right) {
dx = (boundaryBox.right - selectedBox.right);
} else if (selectedBox.left dx < boundaryBox.left) {
dx = (boundaryBox.left - selectedBox.left);
}
if (selectedBox.bottom dy > boundaryBox.bottom) {
dy = (boundaryBox.bottom - selectedBox.bottom);
}
else if (selectedBox.top dy < boundaryBox.top) {
dy = (boundaryBox.top - selectedBox.top);
}
}
let currentMatrix = selected.transform.baseVal.consolidate().matrix,
newMatrix = currentMatrix.translate(dx / scale, dy / scale),
transform = svg.createSVGTransformFromMatrix(newMatrix);
selected.transform.baseVal.initialize(transform);
selected.dataset.startMouseX = dx startX;
selected.dataset.startMouseY = dy startY;
}
function endDrag(e) {
e.stopPropagation();
if (selected) {
selected = undefined;
}
}
function zoom(e) {
e.stopPropagation();
e.preventDefault();
let delta = e.wheelDelta,
container = document.querySelector('svg .main-container'),
scaleStep = delta > 0 ? 1.25 : 0.8;
if (scale * scaleStep > maxScale) {
scaleStep = maxScale / scale;
}
if (scale * scaleStep < minScale) {
scaleStep = minScale / scale;
}
scale *= scaleStep;
let box = svg.getBoundingClientRect();
let point = svg.createSVGPoint();
point.x = e.clientX - box.left;
point.y = e.clientY - box.top;
let currentZoomMatrix = container.getCTM();
point = point.matrixTransform(currentZoomMatrix.inverse());
let matrix = svg.createSVGMatrix()
.translate(point.x, point.y)
.scale(scaleStep)
.translate(-point.x, -point.y);
let newZoomMatrix = currentZoomMatrix.multiply(matrix);
container.transform.baseVal.initialize(svg.createSVGTransformFromMatrix(newZoomMatrix));
console.log("scale", scale);
let t = newZoomMatrix;
console.log("zoomMatrix", t.a, t.b, t.c, t.d, t.e, t.f);
}
document.querySelector('svg').addEventListener('mousedown', beginDrag);
document.querySelector('svg').addEventListener('mousewheel', zoom);
svg.addEventListener('mousemove', drag);
window.addEventListener('mouseup', endDrag);
На первый взгляд, это работает нормально, однако в некоторых ситуациях ведет себя странно.
Например, если вы уменьшите масштаб, я могу свободно перетаскивать его в любых направлениях без каких-либо проблем.
Но если я увеличу масштаб до того, что часть карты превысит родительский элемент, любая попытка переместить его вызывает скачок всей карты и блокирует эту функциональность.
Второе — прямо сейчас я могу перемещать карту только в границах SVG-элемента, и я хочу иметь возможность перетаскивать ее за его пределы таким же образом, как это работает здесь:https://www.amcharts.com/svg-maps/?map=austria
Вот фрагмент с моим кодом: https://jsfiddle.net/marektchas/qo1eynag/3 /
Ответ №1:
Публикую здесь простой способ для функции панорамирования.
var svgCanvas = document.getElementById("canvas");
var viewPort = document.getElementById("viewport");
var drag = false;
var offset = { x: 0, y: 0 };
var factor = .1;
var matrix = new DOMMatrix();
svgCanvas.addEventListener('pointerdown', function (event) {
drag = true;
offset = { x: event.offsetX, y: event.offsetY };
});
svgCanvas.addEventListener('pointermove', function (event) {
if (drag) {
var tx = event.offsetX - offset.x;
var ty = event.offsetY - offset.y;
offset = {
x: event.offsetX,
y: event.offsetY
};
matrix.preMultiplySelf(new DOMMatrix()
.translateSelf(tx, ty));
viewPort.style.transform = matrix.toString();
}
});
svgCanvas.addEventListener('pointerup', function (event) {
drag = false;
});
svgCanvas.addEventListener('wheel', function (event) {
var zoom = event.deltaY > 0 ? -1 : 1;
var scale = 1 factor * zoom;
offset = {
x: event.offsetX,
y: event.offsetY
};
matrix.preMultiplySelf(new DOMMatrix()
.translateSelf(offset.x, offset.y)
.scaleSelf(scale, scale)
.translateSelf(-offset.x, -offset.y));
viewPort.style.transform = matrix.toString();
});
html,
body {
height: 100%;
margin: 0;
box-sizing: border-box;
font-size: 62.5%;
}
body{
display: flex;
}
#around{
display: flex;
width: 100%;
border: 1px dashed orange;
}
#canvas{
flex: 1;
height: auto;
}
<div id="around">
<svg id="canvas" style="border: 1px solid blue;">
<g id="viewport">
<rect x="100" y="100" width="200" height="200" fill="red"/>
</g>
</svg>
</div>
Ответ №2:
Кажется, я нашел решение, однако я не совсем уверен, как именно это работает.
Все работает как ожидалось, если я удалю .перетаскиваемый класс из элемента g