Проблема заключается в том, что если к элементу уже применена трансформация, в моем случае масштаб, при вращении наблюдается эффект растягивания. Но что конкретно я должен учитывать при создании матрицы вращения не знаю. Цель: вращать любой элемент вокруг центра не зависимо от примененной же к нему трансформации...
Обновлено:
Если установить порядок умножения матриц на более верный(как мне кажется)
translate(x y) X rotate(deg) X translate(-x -y) X CTM
то проблема с масштабом уходит. Но если элемент до вращения был уже повернут, то
вращать вокруг центра его уже не удается...
const scaled = document.getElementById('scaled');
const sMatrix = scaled.getCTM();
function rotate(el, matrix, radians) {
const {
x,
y,
width,
height
} = el.getBBox();
const translateMatrix = createSVGMatrix(),
rotateMatrix = createSVGMatrix();
const cos = Math.cos(radians),
sin = Math.sin(radians);
//scale(0.3, 0.6)
translateMatrix.e = (x + width / 2) * 0.3;
translateMatrix.f = (y + height / 2) * 0.6;
rotateMatrix.a = cos;
rotateMatrix.b = sin;
rotateMatrix.c = -sin;
rotateMatrix.d = cos;
// Порядок умножения матриц:
// translate(x y) X rotate(deg) X translate(-x -y) X CTM
const elMatrix = translateMatrix
.multiply(rotateMatrix)
.multiply(translateMatrix.inverse())
.multiply(matrix);
el.setAttribute(
'transform',
matrixToString(elMatrix)
);
}
function matrixToString(m) {
return `matrix(${m.a},${m.b},${m.c},${m.d},${m.e},${m.f})`;
}
function createSVGMatrix() {
return document
.createElementNS('http://www.w3.org/2000/svg', 'svg')
.createSVGMatrix();
}
let r = 0;
setInterval(() => {
rotate(scaled, sMatrix, r);
r += 0.1;
}, 50)
<svg width="100%" height="100%">
<g id="scaled" transform="rotate(30) scale(0.3, 0.6)">
<path id="path" d="m99.04713000000004,80.80642999999998 c-31.21513,0,-56.34375,26.03359,-56.34375,58.375 l0,27.71875 c0,32.34141,25.12862,58.40625,56.34375,58.40625 l393,0 c31.21513,0,56.34375,-26.06484,56.34375,-58.40625 l0,-27.71875 c0,-32.34141,-25.12862,-58.375,-56.34375,-58.375 l-393,0 z m36.90625,21.65625,6.78125,0 c13.61323,0,24.5625,8.42733,24.5625,18.90625 l0,63.96875 c0,10.47892,-10.94927,18.90625,-24.5625,18.90625 l-6.78125,0 c-13.61323,0,-24.5625,-8.42733,-24.5625,-18.90625 l0,-63.96875 c0,-10.47892,10.94927,-18.90625,24.5625,-18.90625 z " id="path3.130" inkscape:connector-curvature="0" style="opacity: 0.95492; fill: rgb(204, 204, 204);" transform="matrix(1,0,0,1,0,0)"></path>
</g>
</svg>
Вот так (при помощи дополнительной группы сверху в иерархии элементов) вращать svg элемент намного проще:
Для этого необходимо лишь определить точку, вокруг которой нужно вращать.
Сделать это можно при помощи .getBBox()
let gr1 = document.querySelector('#rotateGroup1');
let gr2 = document.querySelector('#rotateGroup2');
let gr3 = document.querySelector('#rotateGroup3');
requestAnimationFrame(draw)
function draw(dt){
rotate(gr1, dt/10);
rotate(gr2, -dt/5);
rotate(gr3, dt/5);
requestAnimationFrame(draw)
}
function rotate(el, value) {
let bb = el.getBBox();
let cx = bb.x + bb.width/2;
let cy = bb.y + bb.height/2;
el.setAttribute('transform', `rotate(${value} ${cx},${cy})`)
}
<svg viewbox="0 0 200 100" height="100%">
<g id="rotateGroup1">
<g transform='scale(1.3, 1.6)'>
<rect x="10" y="10" width="20" height="20"></rect>
</g>
</g>
<g id="rotateGroup2">
<g transform='scale(1.7, 1.2)'>
<circle cx="40" cy="20" r="10"></circle>
</g>
</g>
<g id="rotateGroup3">
<g transform='translate(100,0) scale(0.3)'>
<path d="M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80"></path>
</g>
</g>
</svg>
Результат:
Для вращения элемента с трансформацией вокруг центра достаточно следующих действий:
const root = document.getElementById('root');
const scaled = document.getElementById('scaled');
const sMatrix = scaled.getCTM();
const {
x,
y,
width,
height
} = scaled.getBBox();
const pt = root.createSVGPoint();
pt.x = x + width / 2;
pt.y = y + height / 2;
// применяем трансформацию элемента к координатам центра
// и находим координаты центра относительно документа
const elCenter = pt.matrixTransform(sMatrix);
const centerPoint = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
centerPoint.cx.baseVal.value = elCenter.x;
centerPoint.cy.baseVal.value = elCenter.y;
centerPoint.r.baseVal.value = 2;
root.appendChild(centerPoint);
function rotate(el, matrix, radians, center) {
const translateMatrix = createSVGMatrix(),
rotateMatrix = createSVGMatrix();
const cos = Math.cos(radians),
sin = Math.sin(radians);
rotateMatrix.a = cos;
rotateMatrix.b = sin;
rotateMatrix.c = -sin;
rotateMatrix.d = cos;
translateMatrix.e = center.x;
translateMatrix.f = center.y;
// составляем матрицу вращения вокруг центра фигуры
// translate(cx cy) X rotate(deg) X translate(-cx -cy)
const rMatrix = translateMatrix
.multiply(rotateMatrix)
.multiply(translateMatrix.inverse());
// применяем к текущей матрице
// rotate(deg, cx, cy) X CTM
const elMatrix = rMatrix.multiply(matrix);
el.setAttribute(
'transform',
matrixToString(elMatrix)
);
}
function matrixToString(m) {
return `matrix(${m.a},${m.b},${m.c},${m.d},${m.e},${m.f})`;
}
function createSVGMatrix() {
return document
.createElementNS('http://www.w3.org/2000/svg', 'svg')
.createSVGMatrix();
}
let r = 0;
setInterval(() => {
rotate(scaled, sMatrix, r, elCenter);
r += 0.1;
}, 50)
<svg id="root" width="100%" height="100%">
<g id="scaled" transform="skewX(10) translate(10, 10) rotate(10) scale(0.4, 0.3)">
<path id="path" d="m99.04713000000004,80.80642999999998 c-31.21513,0,-56.34375,26.03359,-56.34375,58.375 l0,27.71875 c0,32.34141,25.12862,58.40625,56.34375,58.40625 l393,0 c31.21513,0,56.34375,-26.06484,56.34375,-58.40625 l0,-27.71875 c0,-32.34141,-25.12862,-58.375,-56.34375,-58.375 l-393,0 z m36.90625,21.65625,6.78125,0 c13.61323,0,24.5625,8.42733,24.5625,18.90625 l0,63.96875 c0,10.47892,-10.94927,18.90625,-24.5625,18.90625 l-6.78125,0 c-13.61323,0,-24.5625,-8.42733,-24.5625,-18.90625 l0,-63.96875 c0,-10.47892,10.94927,-18.90625,24.5625,-18.90625 z " id="path3.130" inkscape:connector-curvature="0" style="opacity: 0.95492; fill: rgb(204, 204, 204);"></path>
</g>
</svg>
Продвижение своими сайтами как стратегия роста и независимости