Вращение SVG элемента вокруг центра

207
29 января 2020, 12:40

Проблема заключается в том, что если к элементу уже применена трансформация, в моем случае масштаб, при вращении наблюдается эффект растягивания. Но что конкретно я должен учитывать при создании матрицы вращения не знаю. Цель: вращать любой элемент вокруг центра не зависимо от примененной же к нему трансформации...

Обновлено:
Если установить порядок умножения матриц на более верный(как мне кажется)
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>

Answer 1

Вот так (при помощи дополнительной группы сверху в иерархии элементов) вращать 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>

Результат:

Answer 2

Для вращения элемента с трансформацией вокруг центра достаточно следующих действий:

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>

READ ALSO
JavaScript код не работает

JavaScript код не работает

При запуске кода программа не выводит результатВ консоли выдает ошибку "Uncaught SyntaxError: Unexpected number"

216
Как изменять элемент в зависимости от значения input с помощью js?

Как изменять элемент в зависимости от значения input с помощью js?

Возможно в заголовке не получилось передать суть вопросаДело вот в чем

236
Рекурсия с условиями, на основе массива данных

Рекурсия с условиями, на основе массива данных

У меня формируется некая схема, по которой я хочу пройтись циклом и выполнить опеределённые условия для определённых операторов

194