SVG缩放但位置不变

27
我想做的很简单:使用纯JS,当兄弟元素被悬停时,将一些SVG点从scale(0)缩放到scale(1)。它们是演示中的红点。
以下是基本的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的变换(无法通过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 + ")";
}

你想要发生什么并不是很清楚。你是想让每个红点在鼠标悬停时变大吗? - Paul LeBeau
@BigBadaboom 是的,我想在鼠标悬停时缩放红点,而不需要它们从任何地方移动 - 只需变大即可。 - Zach Saucier
4个回答

36

更新以适应支持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;
}

这里有演示


请注意,当在SVG中使用transform-origin时(不是CSS),特别是移动设备浏览器上可能会出现问题。 - V. Rubinetti
Chrome修复了一个“漏洞”,这意味着它不再起作用了。我现在已经更新了示例,使其再次正常工作,并且在其他浏览器中也可以正常工作。 - Paul LeBeau
我在做一个项目时遇到了类似的问题 - 使用 transform-box: fill-box 让我的工作变得更加容易了! - Zach Saucier
太棒了,谢谢!不幸的是,它目前还没有完全支持:https://www.w3cschool.cn/doc_css/css-transform-box.html - nad_rom
那个页面上的信息已经过时了。我建议您使用 MDN 作为更可靠的资源:https://developer.mozilla.org/en-US/docs/Web/CSS/transform-box - Paul LeBeau

11

如果我没错的话,您想沿着点的中心缩放,点保持当前位置并变大。

如果是这样,那么以下代码将帮助您:

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)

1
感谢您的帮助!使用数学计算并使用CSS变换代替后,保留了过渡效果。[工作演示](http://jsbin.com/vefahide/2/edit)(仅限webkit)。 - Zach Saucier

3

一种更简单的方法,不涉及大量几何计算,是将需要缩放和平移的元素放入一个父级组(“g”)中。

然后,您将平移应用到父级组上,将缩放应用到元素本身上。

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

parentElement.setAttribute('transform', 'translate(' + trasnstr + ')');
element.setAttribute('transform', 'scale(' + scalestr + ')');

不知道为什么你会被点踩,因为这种方法对我是有效的。然而,我正在处理 XML 代码,并将我的路径包裹在 <g transform="scale(1.2, 1.0)">...</g> 中,而不是将变换命令插入到路径本身中。移动已经消失,缩放效果如预期一样。谢谢! - 1813222

0
这将自动计算并设置任何SVG元素的变换原点。
// mainSvgElement is SVG element itself
// svgChildElement is any path, rect, circle etc. inside SVG element
var setTransformOrigin = function(mainSvgElement, svgChildElement) {
  var mainRect = mainSvgElement.getBoundingClientRect();
  var childRect = svgChildElement.getBoundingClientRect();
  var originX = (((childRect.left - mainRect.left) + (childRect.width * 0.5)) / mainRect.width) * 100;
  var originY = (((childRect.top - mainRect.top) + (childRect.height * 0.5)) / mainRect.height) * 100;
  svgChildElement.style.transformOrigin = originX + "% " + originY + "%";
};

setTransformOrigin(mainSvgElement, svgChildElement);
// set scale now / or you can set in css too
svgChildElement.style.transform = "scale(1.5)";

(function() {
  var mainSvgElement = document.querySelector("svg");
  var svgChildElement = mainSvgElement.querySelector("path");
  // mainSvgElement is SVG element itself
  // svgChildElement is any path, rect, circle etc. inside SVG element
  var setTransformOrigin = function(mainSvgElement, svgChildElement) {
    var mainRect = mainSvgElement.getBoundingClientRect();
    var childRect = svgChildElement.getBoundingClientRect();
    var originX = (((childRect.left - mainRect.left) + (childRect.width * 0.5)) / mainRect.width) * 100;
    var originY = (((childRect.top - mainRect.top) + (childRect.height * 0.5)) / mainRect.height) * 100;
    svgChildElement.style.transformOrigin = originX + "% " + originY + "%";
  };
  
  setTransformOrigin(mainSvgElement, svgChildElement);
  // set scale now / or you can set in css too
  svgChildElement.addEventListener("mouseenter", function() {
    svgChildElement.style.transform = "scale(1.5)";
  });
  svgChildElement.addEventListener("mouseleave", function() {
    svgChildElement.style.transform = "scale(1)";
  });
})();
svg {
  width: 100%;
  border: 2px solid red;
}
path {
  cursor: pointer;
  transition: transform 1s;
}
Bring your mouse over on the shape:
<svg class="tb-graph" viewBox="0 0 1006 684" fill="none" xmlns="http://www.w3.org/2000/svg">
  <path d="M574.618 66.3322C575.377 63.1066 573.378 59.87 570.137 59.1784C512.776 46.9376 452.991 52.4821 398.793 75.1494C344.594 97.8167 298.663 136.486 267.096 185.919C265.312 188.711 266.213 192.407 269.042 194.132L357.225 247.895C360.055 249.62 363.738 248.718 365.56 245.95C384.451 217.254 411.493 194.793 443.273 181.502C475.052 168.211 510.032 164.732 543.728 171.435C546.978 172.082 550.207 170.093 550.966 166.867L574.618 66.3322Z" fill="#005453" stroke="#012020" stroke-width="2" />

https://codepen.io/animatedcreativity/pen/qBKXQKZ


嗯,这个似乎对我不起作用。你知道出了什么问题吗? - Zach Saucier
@ZachSaucier 我已经添加了一个例子。 - Rehmat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接