SVG объект — как его правильно масштабировать и перетаскивать?

#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