OiO.lk Community platform!

Oio.lk is an excellent forum for developers, providing a wide range of resources, discussions, and support for those in the developer community. Join oio.lk today to connect with like-minded professionals, share insights, and stay updated on the latest trends and technologies in the development field.
  You need to log in or register to access the solved answers to this problem.
  • You have reached the maximum number of guest views allowed
  • Please register below to remove this limitation

SVG Scale without moving location

  • Thread starter Thread starter Zach Saucier
  • Start date Start date
Z

Zach Saucier

Guest
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

Code:
<?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.

Code:
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

Code:
function scaleMe(elem, scaleX = 1, scaleY = 1, newOffsetX = 0, newOffsetY = 0, originX = "center", originY = "center") {
    const bbox = elem.getBBox();
    const cx = bbox.x + (bbox.width / 2);
    const cy = bbox.y + (bbox.height / 2);
    let tx = -cx * (scaleX - 1) + newOffsetX;
    let ty = -cy * (scaleY - 1) + newOffsetY;        
    
    if (originX === "left" || originX === "right") {
        tx = newOffsetX;
    }
    if (originY === "top" || originY === "bottom") {
        ty = newOffsetY;
    }
    
    const scaleStr = `${scaleX},${scaleY}`;
    const translateStr = `${tx}px,${ty}px`;
    
    elem.style.transformOrigin = `${originX} ${originY}`;
    elem.style.transform = `translate(${translateStr}) scale(${scaleStr})`;
}

<p>What I'm trying to do is simple: scale some SVG dots from <code>scale(0)</code> to <code>scale(1)</code> when a sibling element is hovered using vanilla js. They are the red ones in <a href="http://jsfiddle.net/Zeaklous/ST59M/1/" rel="nofollow noreferrer"><strong>the demo</strong></a></p>
<p>Here's the basic SVG setup</p>
<pre><code><?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>
</code></pre>
<p><strong>The issue</strong> 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 <code>BBox()</code> offset, scaling, then translating back but that only seemed to help and not entirely fix the issue.</p>
<pre><code>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.getBBox(),
cx = box.x + box.width / 10,
cy = box.y + box.height / 10;
//dots.setAttribute("transform", "translate(" + cx + " " + cy + ") scale(0) translate(" + cx + " " + cy + ")");
dots.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.getBBox(),
cx = box.x + box.width / 2,
cy = box.y + box.height / 2;
//dots.setAttribute("transform", "translate(0 0) scale(1) translate(0 0)");
dots.style.WebkitTransform = "translate(0, 0) scale(1) translate(0, 0)";
}
}
}
</code></pre>
<p>I tried using both <code>setAttribute</code> and CSS's transform (I couldn't get <code>setAttribute</code> to transition, presumably because it's not animatable by CSS) but couldn't get it with either. I've only been testing in Chrome</p>
<p>Anyone have an idea how I can scale, while not moving, red dots?</p>
<p><a href="http://jsfiddle.net/Zeaklous/ST59M/1/" rel="nofollow noreferrer"><strong>Here's the demo</strong></a> again if you missed it</p>
<p><strong>Edit</strong></p>
<p>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</p>
<pre><code>function scaleMe(elem, scaleX = 1, scaleY = 1, newOffsetX = 0, newOffsetY = 0, originX = "center", originY = "center") {
const bbox = elem.getBBox();
const cx = bbox.x + (bbox.width / 2);
const cy = bbox.y + (bbox.height / 2);
let tx = -cx * (scaleX - 1) + newOffsetX;
let ty = -cy * (scaleY - 1) + newOffsetY;

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

const scaleStr = `${scaleX},${scaleY}`;
const translateStr = `${tx}px,${ty}px`;

elem.style.transformOrigin = `${originX} ${originY}`;
elem.style.transform = `translate(${translateStr}) scale(${scaleStr})`;
}
</code></pre>
 

Latest posts

A
Replies
0
Views
1
AgencyAnalytics
A
S
Replies
0
Views
1
Stacker Media
S
C
Replies
0
Views
1
CC.Talent
C
Top