Масштаб SVG без перемещения местоположения

То, что я пытаюсь сделать, очень просто: масштабировать некоторые точки SVG с scale(0) до scale(1) при наведении курсора на родственный элемент с помощью vanilla js. Они красные в демо.

Вот базовая настройка SVG

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 
      x="0px" y="0px" viewBox="0 0 720 576" style="enable-background:new 0 0 720 576;" xml:space="preserve">
    <style type="text/css">
        .st3 {
            fill:red;
        }
        * {
            -webkit-transition:.3s;
            transition:.3s;
        }
    </style>
    <g id="Layer_4">
        <!-- Shield -->
        <path class="st8" d="M601,304.7c-32.4-15.4-68.6-24-106.8-24c-40.4,0-78.5,9.6-112.3,26.6c4.9,79.7,41.9,146.7,109.5,187.6
            C559.8,454.1,597,385.6,601,304.7z" />
        <path class="st9" d="M420.1,328.7c2.1-4.7,32.5-23.9,72.5-23.9c39.9,0,73.1,20,75.5,24.3c2.4,4.3,5.7,40-12.7,74.6
                c-19.7,36.9-53.5,50.1-61.8,50.4c-6.4,0.2-41.8-14.3-62.5-51.6C411.5,367.4,418,333.4,420.1,328.7z" />
        <circle class="st10" cx="494.9" cy="373.3" r="35.5" />
    </g>
    <g id="Layer_8">
        <!-- Dots on shield -->
        <circle class="st3" cx="578.8" cy="316.2" r="4.6" />
        <circle class="st3" cx="543.4" cy="346.2" r="4.6" />
        <circle class="st3" cx="505" cy="375.5" r="4.6" />
    </g>
</svg>

Проблема заключается в том, что SVG масштабируется на основе исходного местоположения, а не текущего местоположения, поэтому при применении преобразования он перемещает элемент в дополнение к его масштабированию. Я пытаюсь исправить эту ситуацию, переводя по смещению BBox(), масштабируя, а затем переводя обратно, но это только помогло, а не полностью решило проблему.

var shield = document.getElementById("Layer_4"),
    dots = document.querySelectorAll("#Layer_8 .st3");

toggleTransform(false);

shield.onmouseover = function () { toggleTransform(true); }
shield.onmouseout = function () { toggleTransform(false); }

function toggleTransform(bool) {
    if (!bool) {
        for (var i = 0; i < dots.length; i++) {
            var box = dots[i].getBBox(),
                cx = box.x + box.width / 10,
                cy = box.y + box.height / 10;
            //dots[i].setAttribute("transform", "translate(" + cx + " " + cy + ") scale(0) translate(" + cx + " " + cy + ")");
            dots[i].style.WebkitTransform = "translate(" + cx + "px, " + cy + "px) scale(0) translate(" + -cx + "px, " + -cy + "px)";
        }
    } else {
        for (var i = 0; i < dots.length; i++) {
            var box = dots[i].getBBox(),
                cx = box.x + box.width / 2,
                cy = box.y + box.height / 2;
            //dots[i].setAttribute("transform", "translate(0 0) scale(1) translate(0 0)");
            dots[i].style.WebkitTransform = "translate(0, 0) scale(1) translate(0, 0)";
        }
    }
}

Я пытался использовать как setAttribute, так и преобразование CSS (я не смог заставить setAttribute переходить, предположительно потому, что CSS не анимировал его), но не смог добиться этого ни с одним из них. Я тестировал только в Chrome

Кто-нибудь знает, как я могу масштабировать, не двигаясь, красные точки?

Вот демо еще раз, если вы его пропустили

Изменить

Я создал функцию на основе ответа RashFlash, чтобы сделать ее довольно простой в использовании, а также учитывать смещения и различные источники преобразования.

function scaleMe(elem, scaleX, scaleY, newOffsetX, newOffsetY, originX, originY) {
    newOffsetX = null ? 0 : newOffsetX;
    newOffsetY = null ? 0 : newOffsetY;
    originX = null ? "center" : originX;
    originY = null ? "center" : originY;

    var bbox = elem.getBBox(),
        cx = bbox.x + (bbox.width / 2),
        cy = bbox.y + (bbox.height / 2),        
        tx = -cx * (scaleX - 1) + newOffsetX,
        ty = -cy * (scaleY - 1) + newOffsetY;        

    if(originX === "left" || originX === "right") {
        tx = newOffsetX;
    }
    if(originY === "top" || originY === "bottom") {
        ty = newOffsetY;
    }

    var scalestr = scaleX + ',' + scaleY,
        translatestr = tx + 'px,' + ty + 'px';

    elem.style.WebkitTransformOrigin = originX + " " + originY;
    elem.style.MozTransformOrigin = originX + " " + originY;
    elem.style.msTransformOrigin = originX + " " + originY;
    elem.style.transformOrigin = originX + " " + originY;

    elem.style.WebkitTransform = "translate(" + translatestr + ") scale(" + scalestr + ")";
    elem.style.MozTransform = "translate(" + translatestr + ") scale(" + scalestr + ")";
    elem.style.msTransform = "translate(" + translatestr + ") scale(" + scalestr + ")";
    elem.style.transform = "translate(" + translatestr + ") scale(" + scalestr + ")";
}

person Zach Saucier    schedule 11.06.2014    source источник
comment
Не совсем понятно, что вы хотите, чтобы произошло. Вы хотите, чтобы каждая из красных точек становилась больше при наведении курсора мыши?   -  person Paul LeBeau    schedule 12.06.2014
comment
@BigBadaboom Да, я хочу масштабировать красные точки при наведении курсора мыши, чтобы они не перемещались откуда-либо - просто увеличивайтесь   -  person Zach Saucier    schedule 12.06.2014


Ответы (3)


Обновлено для работы с современными браузерами, поддерживающими transform-box. Ранее этот подход работал только в Chrome. Но спецификация изменила то, как работает transform-origin, и добавление transform-box теперь означает, что это работает в большем количестве браузеров (в настоящее время Chrome, FF и Opera).

На самом деле вы можете добиться этого эффекта без JS.

.st3 {
    fill: red;
    -webkit-transform: scale(1);
    -webkit-transform-origin: 50% 50%;
    -webkit-transition:.3s;
    transform: scale(1);
    transform-origin: 50% 50%;
    transition:.3s;
    transform-box: fill-box;
}

#Layer_4:hover + g .st3 {
    -webkit-transform: scale(2);
    -webkit-transform-origin: 50% 50%;
    -webkit-transition:.3s;
    transform: scale(2);
    transform-origin: 50% 50%;
    transition:.3s;
}

Демо здесь

person Paul LeBeau    schedule 12.06.2014
comment
Обратите внимание, что transform-origin при использовании в SVG (в отличие от CSS), кажется, плохо работает с некоторыми браузерами, особенно мобильными. - person V. Rubinetti; 03.09.2018
comment
Chrome исправил ошибку, из-за которой это больше не работало. Я обновил пример, и теперь он снова работает, а также работает в других браузерах. - person Paul LeBeau; 03.09.2018
comment
У меня была похожая проблема для проекта, который я сейчас делаю - использование transform-box: fill-box сделало то, что я делал, намного проще! - person Zach Saucier; 03.01.2019
comment
Классная вещь, спасибо! К сожалению, он еще не полностью поддерживается: w3cschool.cn/doc_css/css-transform. -box.html - person nad_rom; 17.02.2019
comment
Информация на этой странице устарела. Я бы посоветовал вам использовать MDN как более надежный ресурс: developer. mozilla.org/en-US/docs/Web/CSS/transform-box - person Paul LeBeau; 18.02.2019

если я не ошибаюсь, вы хотите масштабировать точки по их центру, точки остаются в своем текущем положении и просто увеличиваются.

если вы этого хотите, то следующий код поможет вам

var bbox=elementNode.getBBox();
var cx=bbox.x+(bbox.width/2),
    cy=bbox.y+(bbox.height/2);   // finding center of element
var scalex=1.5, scaley=1.5;    // your desired scale
var saclestr=scalex+','+scaley;
var tx=-cx*(scalex-1);
var ty=-cy*(scaley-1);                        
var translatestr=tx+','+ty;

elementNode.setAttribute('transform','translate('+translatestr+') scale('+saclestr+')');

Итак, что я сделал, я сначала переместил точку, а затем масштабировал ее. я использую следующую формулу, как описано в Преобразование системы координат

translate(-centerX*(factor-1), -centerY*(factor-1))
scale(factor)
person RashFlash    schedule 12.06.2014
comment
Спасибо за вашу помощь! Использование этой математики и использование преобразования CSS вместо этого сохранило переход. Рабочая демонстрация (только webkit) - person Zach Saucier; 12.06.2014

Более простой способ сделать это, не связанный с кучей геометрии, — поместить элемент, который нужно масштабировать и перевести, в родительскую группу ('g').

Затем вы применяете перевод к родительской группе и масштаб к самому элементу.

var trasnstr = x + ',' + y;
var scalestr = scaleX + ',' + scaleY;

parentElement.setAttribute('transform', 'translate(' + trasnstr + ')');
element.setAttribute('transform', 'scale(' + scalestr + ')');
person David Johnson    schedule 27.05.2020
comment
Не знаю, почему вы получили отрицательный голос, поскольку этот подход сработал для меня. Однако я работаю с кодом xml и завернул свой путь в ‹g transform=scale(1.2, 1.0)›...‹/g› вместо того, чтобы вставлять команду преобразования в сам путь. Переезд ушел. Масштабирование по назначению. Спасибо ! - person 1813222; 31.01.2021