Home > Mobile >  SVGTransform.setRotate distorts my square shape
SVGTransform.setRotate distorts my square shape

Time:08-27

By trying to rotate a square shaped element #sample about its center using SVGTransform.setRotate its shape gets distorted.

//REM: inserts return of getCurrentRotationOfSVGElementInDegrees(#sample) before the end of </body>
function addCurrentDegreesToBody(){
  const
    tElement = document.getElementById('sample'),
    tDegrees = getCurrentRotationOfSVGElementInDegrees(tElement).toString();

  document.body.insertAdjacentHTML('beforeEnd', `${tDegrees}<br />`)
};

//https://stackoverflow.com/questions/27557040/svg-find-rotation-angle-of-a-path
//REM: returns the angle of passed element in degrees
function getCurrentRotationOfSVGElementInDegrees(element){
  const
    tMatrix = element.transform.baseVal.getItem(0).matrix,
    tDegrees = (180 / Math.PI) * Math.atan2(tMatrix.d, tMatrix.c) - 90;

  return tDegrees < 0 ? tDegrees   360 : tDegrees
};

//REM: Rotates #sample by  15° about its center
function rotateBy15Degrees(){
  const
    tElement = document.getElementById('sample'),
    tAngle = getCurrentRotationOfSVGElementInDegrees(tElement),
    tMatrix = tElement.transform.baseVal.getItem(0).matrix,
    tBBox = tElement.getBBox();

  setRotationSVG(
    tElement,
    tAngle   15,
    tBBox.x   tBBox.width / 2,
    tBBox.y   tBBox.height / 2
  )
};

//REM: sets the rotation of passed element to the passed angle about point (cx,cy)
//REM: and outputs the degrees in the <body>
function setRotationSVG(element, angle, cx, cy){
  const
    tTransform = element.ownerSVGElement.createSVGTransform(),
    tAngle = getCurrentRotationOfSVGElementInDegrees(element);

  //https://developer.mozilla.org/en-US/docs/Web/API/SVGTransform
  //https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform#rotate
  //REM: Rotate to passed angle (opposite of current angle plus passed angle)
  tTransform.setRotate(tAngle * -1   angle, cx, cy);
  element.transform.baseVal.appendItem(tTransform);
  element.transform.baseVal.consolidate();

  addCurrentDegreesToBody();

  return element
};
<svg xmlns="http://www.w3.org/2000/svg" viewBox="1322 122 45 45" width="200px">
  <!--REM: Expected result -->
  <g id="sample" transform="matrix(0.208522,0.0256033,-0.0546508,0.445095,1023.54,-358.288)">
    <g stroke-width="0.1" transform="matrix(1 0 0 1 0 0)">
      <path d="m1902.15 1045.16v-46.74h-99.72v46.74z" fill="#1e90ff" fill-rule="evenodd"></path>
      <path d="m1902.15 998.42v46.74h-99.72v-46.74z" fill="none" stroke="#fff"></path>
    </g>
  </g>
</svg>

<!--REM: Inline events for sample case-->
<button onclick="addCurrentDegreesToBody()">show degrees</button>
<button onclick="rotateBy15Degrees()">rotate by 15°</button>
<br /><br />

show degrees

By clicking on "show degrees" the script does the following steps:

  1. Calculate the current angle of #sample using its matrix
  2. Output the angle before the end of body

rotate by 15°

By clicking on "rotate by 15°" the script does the following steps:

  1. Calculate the current angle of #sample using its matrix
  2. Rotate #sample by 15° about its center
  3. Output the new angle before the end of body

By doing that the shape of #sample distorts => is not a square anymore. The outputed angle does not show an increase of 15° which I assume is due to the distorting issue.


How am I to use SVGTransform.setRotate correctly, so that the shape of #sample remains a square?


Update 26/08/2022

The issue seems to be the shape.

  • If I use another shape like the one from enxaneta it works fine
  • If I wrap the whole #sample in another g and rotate that it works fine

I can showcase this in another fiddle here.

CodePudding user response:

As I've commented: in order to rotate with matrix you may try

transform = `matrix( ${Math.cos( t )}, ${Math.sin( t )}, ${-Math.sin( t )}, ${Math.cos( t )}, 0, 0)

where t is the angle in radians. This will rotate the shape around the point x:0,y:0.

In order to rotate with matrix around it's center, or around the point you want, you need to calculate also the values for the e and f components (translate in x and y) of the matrix like in the folowing example:

let x = 120,y=60;//the center of rotation
let shape = document.getElementById("matrixRotate");
let t = (15*Math.PI)/180;//angle
let a = Math.cos( t );
let b = Math.sin( t );
let c = -Math.sin( t );
let d = Math.cos( t );
let e = -x * Math.cos( t )   y * Math.sin( t )   x;
let f = -x * Math.sin( t ) - y * Math.cos( t )   y;

let transform=`matrix( ${a}, ${b}, ${c}, ${d}, ${e}, ${f})`;
shape.setAttribute("transform",transform);
<svg width="250" height="200" viewBox="0 0 250 200">
    <rect x="70" y="20" height="80" width="100" stroke="#003300" fill="#6ab150" fill-opacity="0.5">
    </rect>
    <rect x="70" y="20" height="80" width="100" id="matrixRotate" stroke="#003300" fill="#6ab150" fill-opacity="0.9" transform="">
    </rect>
</svg>

CodePudding user response:

The trick to make SVGTransform.setRotate work corretly on #shape is to not append the new transformation yet to prepend it by using element.transform.baseVal.insertItemBefore() instead of element.transform.baseVal.appendItem().

Here is the working code.

//REM: inserts return of getCurrentRotationOfSVGElementInDegrees(#sample) before the end of </body>
function addCurrentDegreesToBody(){
  const
    tElement = document.getElementById('sample'),
    tDegrees = getCurrentRotationOfSVGElementInDegrees(tElement).toString();

  document.body.insertAdjacentHTML('beforeEnd', `${tDegrees}<br />`)
};

//https://stackoverflow.com/questions/27557040/svg-find-rotation-angle-of-a-path
//REM: returns the angle of passed element in degrees
function getCurrentRotationOfSVGElementInDegrees(element){
  const
    tMatrix = element.transform.baseVal.getItem(0).matrix,
    tDegrees = (180 / Math.PI) * Math.atan2(tMatrix.d, tMatrix.c) - 90;

  return tDegrees < 0 ? tDegrees   360 : tDegrees
};

//REM: Rotates #sample by  15° about its center
//REM: and outputs the degrees in the <body>
function rotateBy15Degrees(){
  const
    tElement = document.getElementById('sample'),
    tAngle = getCurrentRotationOfSVGElementInDegrees(tElement),
    tMatrix = tElement.transform.baseVal.getItem(0).matrix,
    tBBox = tElement.getBBox();

  //REM: Center of the shape including matrix, since we insert the new rotation first we have to apply it
  const
    tOX = tBBox.x   tBBox.width / 2,
    tOY = tBBox.y   tBBox.height / 2,
    tCX = (tOX * tMatrix.a   tOY * tMatrix.c)   tMatrix.e,
    tCY = (tOX * tMatrix.b   tOY * tMatrix.d)   tMatrix.f;

  setRotationSVG(
    tElement,
    tAngle   15,
    //tBBox.x   tBBox.width / 2,
    //tBBox.y   tBBox.height / 2
    tCX,
    tCY
  );

  addCurrentDegreesToBody()
};

//REM: sets the rotation of passed element to the passed angle about point (cx,cy)
function setRotationSVG(element, angle, cx, cy){
  const
    tTransform = element.ownerSVGElement.createSVGTransform(),
    tAngle = getCurrentRotationOfSVGElementInDegrees(element);

  //https://developer.mozilla.org/en-US/docs/Web/API/SVGTransform
  //https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform#rotate
  //REM: Rotate to passed angle (opposite of current angle plus passed angle)
  tTransform.setRotate(tAngle * -1   angle, cx, cy);
  //element.transform.baseVal.appendItem(tTransform);
  element.transform.baseVal.insertItemBefore(tTransform, 0);
  element.transform.baseVal.consolidate();

  return element
};
<svg xmlns="http://www.w3.org/2000/svg" viewBox="1322 122 45 45" width="200px">
  <!--REM: Sample shape -->
  <g id="sample" transform="matrix(0.208522,0.0256033,-0.0546508,0.445095,1023.54,-358.288)">
    <g stroke-width="0.1" transform="matrix(1 0 0 1 0 0)">
      <path d="m1902.15 1045.16v-46.74h-99.72v46.74z" fill="#1e90ff" fill-rule="evenodd"></path>
      <path d="m1902.15 998.42v46.74h-99.72v-46.74z" fill="none" stroke="#fff"></path>
    </g>
  </g>
</svg>

<!--REM: Inline events for sample case-->
<button onclick="addCurrentDegreesToBody()">show degrees</button>
<button onclick="rotateBy15Degrees()">rotate by 15° (setRotate)</button>
<br /><br />

In my environment this seems to fix already rotated or skewed shapes which have a value unequal to zero in either matrix b or c. The other shapes were not negatively affected by this change so far.

  • Related