Cómo configurar el origen de la transformación en SVG

104

Necesito cambiar el tamaño y rotar ciertos elementos en un documento SVG usando javascript. El problema es que, por defecto, siempre aplica la transformación alrededor del origen en la (0, 0)parte superior izquierda.

¿Cómo puedo volver a definir este punto de ancla de transformación?

Intenté usar el transform-originatributo, pero no afecta nada.

Así es como lo hice:

svg.getDocumentById('someId').setAttribute('transform-origin', '75 240');

No parece establecer el punto de pivote en el punto que especifiqué, aunque puedo ver en Firefox que el atributo está configurado correctamente. Probé cosas como center bottomy 50% 100%con y sin paréntesis. Nada funcionó hasta ahora.

¿Alguien puede ayudar?

CTheDark
fuente
FWIW, supuestamente corregido a partir de Firefox 19 beta 3, aunque todavía tengo problemas en Firefox 22. Listado de errores de Mozilla: bugzilla.mozilla.org/show_bug.cgi?id=828286
Toph

Respuestas:

147

Para rotar use transform="rotate(deg, cx, cy)", donde deg es el grado que desea rotar y (cx, cy) define el centro de rotación.

Para escalar / cambiar el tamaño, debe traducir por (-cx, -cy), luego escalar y luego traducir de nuevo a (cx, cy). Puede hacer esto con una transformación de matriz :

transform="matrix(sx, 0, 0, sy, cx-sx*cx, cy-sy*cy)"

Donde sx es el factor de escala en el eje x, sy en el eje y.

Peter Collingridge
fuente
Muchas gracias. Rotar funciona perfectamente, creo, pero escalar / cambiar el tamaño siempre termina teniendo una cantidad aleatoria. Intenté usar la matriz y hacerlos por separado como "traducir (cx sx, cy sy) scale (sx, sy)". El mismo resultado.
CTheDark
6
¿No sería transform = "matriz (sx, 0, 0, sy, cx-sx cx, cy-sy cy)"? Porque solo quieres traducir por la diferencia. Creo que esta lógica es correcta, pero aún me da posiciones ...
CTheDark
¡Ahora funciona! ¡Estaba usando valores incorrectos de cx y cy! ¡Muchas gracias!
CTheDark
1
@JayStevens Sí, la transformación de matriz funcionará para cualquier sistema, son solo matemáticas. La única diferencia puede ser la notación. Por lo que puedo decir, la transformación de la matriz CSS usa la misma notación que para los SVG, por lo que los mismos seis números en ese orden deberían funcionar.
Peter Collingridge
1
Solo para aclarar, cx y cy deben ser compensaciones del centro de la caja en su escala. Esto me hizo tropezar un poco, ya que estaba pensando en los modelos de caja CSS. @forresto alude a esto en su respuesta a continuación.
Jason Farnsworth
22

Si puede usar un valor fijo (no "centro" o "50%"), puede usar CSS en su lugar:

-moz-transform-origin: 25px 25px;
-ms-transform-origin:  25px 25px;
-o-transform-origin: 25px 25px;
-webkit-transform-origin:  25px 25px;
transform-origin: 25px 25px;

Algunos navegadores (como Firefox) no manejarán los valores relativos correctamente.

Hafenkranich
fuente
1
Guau. Haría +10 si pudiera. ¡Esto me salvó el día! Gracias.
Cullub
¿Cómo hacer esto y solo esto en JavaScript?
Andrew S
Al igual que element.setAttribute('transform-origin', '25px 25px')?
nikk wong
13

Si eres como yo y quieres desplazarte y luego hacer zoom con transform-origin, necesitarás un poco más.

// <g id="view"></g>
var view = document.getElementById("view");

var state = {
  x: 0,
  y: 0,
  scale: 1
};

// Origin of transform, set to mouse position or pinch center
var oX = window.innerWidth/2;
var oY = window.innerHeight/2;

var changeScale = function (scale) {
  // Limit the scale here if you want
  // Zoom and pan transform-origin equivalent
  var scaleD = scale / state.scale;
  var currentX = state.x;
  var currentY = state.y;
  // The magic
  var x = scaleD * (currentX - oX) + oX;
  var y = scaleD * (currentY - oY) + oY;

  state.scale = scale;
  state.x = x;
  state.y = y;

  var transform = "matrix("+scale+",0,0,"+scale+","+x+","+y+")";
  //var transform = "translate("+x+","+y+") scale("+scale+")"; //same
  view.setAttributeNS(null, "transform", transform);
};

Aquí está funcionando: http://forresto.github.io/dataflow-prototyping/react/

forresto
fuente
Su enlace de github 404s
NuclearPeon
Hola @NuclearPeon, esto es increíble, pero parece que no puedo implementarlo en mi código. El elemento SVG al que me gustaría aplicarlo ya es una variable llamada "mainGrid". Intenté reemplazar la instancia de "view" con "mainGrid" sin ningún efecto. ¿Qué me estoy perdiendo? ¡Gracias!
sakeferret
@sakeferret, las cosas más obvias para verificar es que las idcoincidencias en el atributo getElementByIdy los documentos id="...". Es difícil saberlo con certeza sin ver el código. Si no desea publicarlo como una pregunta SO, puede intentar crear el ejemplo más simple usando los nombres de sus atributos hasta que funcione / encuentre el problema, luego agregue el resto nuevamente.
NuclearPeon
12

Para escalar sin tener que usar la matrixtransformación:

transform="translate(cx, cy) scale(sx sy) translate(-cx, -cy)"

Y aquí está en CSS:

transform: translate(cxpx, cypx) scale(sx, sy) translate(-cxpx, -cypx)
cmititiuc
fuente
0

parece que todos nos hemos perdido un truco aquí: transform-box: fill-box

el uso transform-box: fill-boxhará que un elemento dentro de un SVG se comporte como un elemento HTML normal. Luego puede aplicar transform-origin: center(o algo más) como lo haría normalmente

eso es correcto, transform-box: fill-boxno hay necesidad de cosas complicadas de matriz

Jorge
fuente
-1

Tuve un problema similar. Pero estaba usando D3 para colocar mis elementos y quería que la transformación y la transición fueran manejadas por CSS. Este era mi código original, que conseguí trabajar en Chrome 65:

//...
this.layerGroups.selectAll('.dot')
  .data(data)
  .enter()
  .append('circle')
  .attr('transform-origin', (d,i)=> `${valueScale(d.value) * Math.sin( sliceSize * i)} 
                                     ${valueScale(d.value) * Math.cos( sliceSize * i + Math.PI)}`)
//... etc (set the cx, cy and r below) ...

Esto permitió que fije las cx, cyy transform-originlos valores en JavaScript utilizando los mismos datos.

¡PERO esto no funcionó en Firefox! Lo que tenía que hacer era envolver el circleen la getiqueta y translateusando la misma fórmula de posicionamiento desde arriba. Luego agregué el circleen la getiqueta y establecí sus valores cxy cyen 0. A partir de ahí, transform: scale(2)escalaría desde el centro como se esperaba. El código final se ve así.

this.layerGroups.selectAll('.dot')
  .data(data)
  .enter()
  .append('g')
  .attrs({
    class: d => `dot ${d.metric}`,
    transform: (d,i) => `translate(${valueScale(d.value) * Math.sin( sliceSize * i)} ${valueScale(d.value) * Math.cos( sliceSize * i + Math.PI)})`
  })
  .append('circle')
  .attrs({
    r: this.opts.dotRadius,
    cx: 0,
    cy: 0,
  })

Después de realizar este cambio, cambié mi CSS para apuntar al en circlelugar de .dot, para agregar el transform: scale(2). Ni siquiera necesitaba uso transform-origin.

NOTAS:

  1. Estoy usando d3-selection-multien el segundo ejemplo. Esto me permite pasar un objeto a en .attrslugar de repetir .attrpara cada atributo.

  2. Cuando utilice un literal de plantilla de cadena, tenga en cuenta los saltos de línea como se ilustra en el primer ejemplo. Esto incluirá una nueva línea en la salida y puede romper su código.

Jamie S
fuente
2
Quizás si establece transform-box: fill-box; Firefox habría funcionado de la manera que usted quería.
Robert Longson
intenté eso. no pareció funcionar. Se hizo el trabajo después de cambiar el svg:transform-origin.enabledpref en el firefox about:configpágina. Pero ... esa no parecía una solución adecuada. También encontré una discrepancia entre transform-origin: 50% 50%y transform-origin: center center.
Jamie S
la preferencia svg.transform-origin.enabled se eliminó hace muchas versiones. A menos que esté utilizando una versión muy antigua, no debería haber diferencia entre el 50% y el centro. Siéntase libre de generar un error de bugzilla si hay una diferencia en Firefox 59 o posterior.
Robert Longson