Search code examples
javascriptsvgcss-transforms

SVG Scale group from his center


I'm trying to scale an <g></g> called #RectangleScaled by a 1.4 factor from the group center. I'm using this formula

translate(-centerX*(factor-1), -centerY*(factor-1)) scale(factor)

But the red rectangle doesn't seem to have the right translation

Here is my implementation

var rectangle = document.querySelector("#Rectangle")
var rectangleScaled = document.querySelector("#RectangleScaled")

var canvasBbox = document.querySelector("#Canvas").getBBox()

var x = -canvasBbox.width/2*(1.4-1)
var y = -canvasBbox.height/2*(1.4-1)

rectangleScaled.style.transform = "translate("+x+"px, "+y+"px) scale(1.4)"

Svg

<svg width="411" height="731" viewBox="0 0 411 731" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <g id="Canvas" transform="translate(721 384)">
    <clipPath id="clip-0" clip-rule="evenodd">
      <path d="M -721 -384L -310 -384L -310 347L -721 347L -721 -384Z" fill="#FFFFFF"/>
    </clipPath>
    <g id="Google Pixel" clip-path="url(#clip-0)">
      <path d="M -721 -384L -310 -384L -310 347L -721 347L -721 -384Z" fill="#FFFFFF"/>

      <g id="RectangleScaled">
        <use xlink:href="#path1_fill" transform="translate(-614 -152)" fill="red"/>
      </g>
      <g id="Rectangle">
        <use xlink:href="#path0_fill" transform="translate(-614 -152)" fill="#C4C4C4"/>
      </g>
    </g>
  </g>
  <defs>
    <path id="path0_fill" d="M 0 0L 196 0L 196 266L 0 266L 0 0Z"/>
    <path id="path1_fill" d="M 0 0L 196 0L 196 266L 0 266L 0 0Z"/>
  </defs>
</svg>

Here is the fiddle


Solution

  • You appear to have got your centre wrong. You are scaling around the wrong centre point.

    You are dividing canvasBBox width and height by two, but you are not taking into account the x and y values of the bbox.

    The centre of the #Canvas element is at

    var cx = canvasBbox.x + canvasBbox.width/2;
    var cy = canvasBbox.y + canvasBbox.height/2;
    

    If you update the calculation with these values, you get your expected result.

    var rectangle = document.querySelector("#Rectangle")
    var rectangleScaled = document.querySelector("#RectangleScaled")
    
    var canvasBbox = document.querySelector("#Canvas").getBBox()
    
    var cx = canvasBbox.x + canvasBbox.width/2;
    var cy = canvasBbox.y + canvasBbox.height/2;
    
    var x = -cx * (1.4 - 1)
    var y = -cy * (1.4 - 1)
    
    rectangleScaled.style.transform = "translate("+x+"px, "+y+"px) scale(1.4)"
    <svg width="411" height="731" viewBox="0 0 411 731" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
      <g id="Canvas" transform="translate(721 384)">
        <clipPath id="clip-0" clip-rule="evenodd">
          <path d="M -721 -384L -310 -384L -310 347L -721 347L -721 -384Z" fill="#FFFFFF"/>
        </clipPath>
        <g id="Google Pixel" clip-path="url(#clip-0)">
          <path d="M -721 -384L -310 -384L -310 347L -721 347L -721 -384Z" fill="#FFFFFF"/>
    
          <g id="RectangleScaled">
            <use xlink:href="#path1_fill" transform="translate(-614 -152)" fill="red"/>
          </g>
          <g id="Rectangle">
            <use xlink:href="#path0_fill" transform="translate(-614 -152)" fill="#C4C4C4"/>
          </g>
        </g>
      </g>
      <defs>
        <path id="path0_fill" d="M 0 0L 196 0L 196 266L 0 266L 0 0Z"/>
        <path id="path1_fill" d="M 0 0L 196 0L 196 266L 0 266L 0 0Z"/>
      </defs>
    </svg>