I have a simple transform class to apply translations, scales and rotations on a div in any arbitrary order:
class TransformDiv{ constructor(div) { this.div = div; this.translateX = 0; this.translateY = 0; this.scaleX = 1; this.scaleY = 1; this.shearX = 0; this.shearY = 0; } translate(x, y) { this.translateX += x; this.translateY += y; this.setTransform(); } scale(x, y, anchorX = 0, anchorY = 0) { this.scaleX *= x; this.shearX *= x; this.scaleY *= y; this.shearY *= y; this.translateX -= (this.translateX - anchorX) * (1 - x); this.translateY -= (this.translateY - anchorY) * (1 - y); this.setTransform(); } rotate(rad, anchorX = 0, anchorY = 0) { let cos = Math.cos(rad); let sin = Math.sin(rad); // the composition of two successive rotations are additive let newScaleX = this.scaleX * cos + this.shearX * sin; let newShearX = this.scaleX * (-sin) + this.shearX * cos; let newShearY = this.shearY * cos + this.scaleY * sin; let newScaleY = this.shearY * (-sin) + this.scaleY * cos; this.scaleX = newScaleX; this.shearX = newShearX; this.shearY = newShearY; this.scaleY = newScaleY; //rotation about an arbitrary point let originX = (this.translateX - anchorX); let originY = (this.translateY - anchorY); this.translateX -= (originY * sin - originX * (cos - 1)); this.translateY -= (-originY * (cos - 1) - originX * sin); this.setTransform(); } setTransform() { this.div.style.transform = `matrix(${this.scaleX}, ${this.shearY}, ${this.shearX}, ${this.scaleY}, ${this.translateX}, ${this.translateY})`; } }
A problem arises when I wish to rotate after a non-uniform scale has been made.
Edit – Newer interactive example: https://codepen.io/manstie/pen/RwGGOmB
Here is the example I made: https://jsfiddle.net/ft61q230/1/
In the example here:
div2.translate(100, 100); div2.scale(2, 1, 100, 100); div2.rotate(Math.PI / 2, 100, 100);
The expected result is for Test 1 Text
and Test 2 Text
to be the same length, as if you were rotating from the top left of the div clockwise 90 degrees; but as you can see the result is such that the rotation logic I am performing retains the scale on the world-space axis, so now Test 2 Text
is twice as tall rather than twice as long.
Current outcome:
Desired outcome:
The current rotation logic is based on multiplying the existing transformation matrix that makes up rotation by another transformation matrix containing an angle to rotate by, but I realize it is not as simple as that and I am missing something to retain local-axial scale.
Thank you for your assistance.
Edit:
Was recommended DOMMatrix
which does all this math for me, but it has the same problem, although there is some skew which I don’t think is accurate:
https://jsfiddle.net/heqo7vrt/1/
The skew is caused by the scale function scaling it’s local X axis while it is rotated, and then rotating after not keeping that local X axis scaling. Also, DOMMatrix
translate function has the translations apply on its local axis which is not desired in my situation but if its rotate function worked as expected I would be able to use it.
Advertisement
Answer
I managed to fix it here:
Regular: https://jsfiddle.net/sbca61k5/
let newScaleX = cos * this.scaleX + sin * this.shearY; let newShearX = cos * this.shearX + sin * this.scaleY; let newShearY = -sin * this.scaleX + cos * this.shearY; let newScaleY = -sin * this.shearX + cos * this.scaleY;
DOMMatrix version: https://jsfiddle.net/b36kqrsg/
this.matrix = new DOMMatrix([cos, sin, -sin, cos, 0, 0]).multiply(this.matrix); // or this.matrix = new DOMMatrix().rotate(deg).multiply(this.matrix);
The difference is to have the rotation matrix multiplied by the rest of the matrix to “add” it on, not the other way round:
[a c e] [cos -sin 0] [scx shy tx] [b d f] = [sin cos 0] . [shx scy ty] [0 0 1] [0 0 1] [0 0 1 ]
I’m unsure about the details of the anchor mathematics but the DOMMatrix version’s anchor is relative to its own top left whereas the other is relative to the top left of the document.
From my interactive example the anchor maths does not work as after a multitude of rotations the objects get further away from the anchor origin. https://codepen.io/manstie/pen/PoGXMed