Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SVG Scale without moving location

Tags:

What I'm trying to do is simple: scale some SVG dots from scale(0) to scale(1) when a sibling element is hovered using vanilla js. They are the red ones in the demo

Here's the basic SVG setup

<?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> 

The issue is that SVG scales based on the origin location, not the current location, thus when a transform is applied it moves the element in addition to scaling it. I am attempting to fix this situation by translating by the BBox() offset, scaling, then translating back but that only seemed to help and not entirely fix the issue.

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)";         }     } } 

I tried using both setAttribute and CSS's transform (I couldn't get setAttribute to transition, presumably because it's not animatable by CSS) but couldn't get it with either. I've only been testing in Chrome

Anyone have an idea how I can scale, while not moving, red dots?

Here's the demo again if you missed it

Edit

I made a function based on RashFlash's answer to make it quite simple to use and also takes into account offsets and different transform origins

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 + ")"; } 
like image 734
Zach Saucier Avatar asked Jun 11 '14 22:06

Zach Saucier


People also ask

How do I scale an SVG image?

Just set the viewBox on your <svg> , and set one of height or width to auto . The browser will adjust it so that the overall aspect ratio matches the viewBox .

How do I make SVG not scale?

If you want it to not scale, then you need give either the <svg> , or the <object> , a specific width. For example, if you want it to be rendered at 1:1, you would need to give it the same width and height as the viewBox: <svg width="1191" height="971" ...>

How do I change SVG size in HTML?

Solution with HTML attributes In this snippet, you can see how to resize SVG in HTML. For that, you need to change the width and height attributes. Here, you can see the height and width attributes that can be replaced with the values you need.


1 Answers

Updated to work with modern browsers that support transform-box Previously, this approach worked only in Chrome. But spec changes to how transform-origin works, and the addition of transform-box now means that this works in more browsers (currently Chrome, FF, and Opera).

You can actually achieve this effect without 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; } 

Demo here

like image 138
Paul LeBeau Avatar answered Oct 19 '22 05:10

Paul LeBeau