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:
- Calculate the current angle of
#sample
using its matrix - Output the angle before the end of
body
rotate by 15°
By clicking on "rotate by 15°" the script does the following steps:
- Calculate the current angle of
#sample
using its matrix - Rotate
#sample
by 15° about its center - 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 anotherg
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.