Como definir a origem da transformação em SVG

104

Eu preciso redimensionar e girar certos elementos no documento SVG usando javascript. O problema é que, por padrão, ele sempre aplica a transformação em torno da origem em(0, 0) canto superior esquerdo.

Como posso redefinir este ponto de ancoragem de transformação?

Tentei usar o transform-originatributo, mas não afetou nada.

Foi assim que eu fiz:

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

Não parece definir o ponto principal para o ponto que especifiquei, embora possa ver no Firefox que o atributo está definido corretamente. Tentei coisas como center bottome 50% 100%com e sem parênteses. Nada funcionou até agora.

Alguém pode ajudar?

CTheDark
fonte
FWIW, supostamente corrigido no Firefox 19 beta 3, embora ainda esteja tendo problemas no Firefox 22. Listagem de bugzilla do Mozilla: bugzilla.mozilla.org/show_bug.cgi?id=828286
Toph

Respostas:

147

Para girar transform="rotate(deg, cx, cy)", use , onde deg é o grau que você deseja girar e (cx, cy) define o centro de rotação.

Para dimensionar / redimensionar, você deve traduzir por (-cx, -cy), então dimensionar e então traduzir de volta para (cx, cy). Você pode fazer isso com uma transformação de matriz :

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

Onde sx é o fator de escala no eixo x, sy no eixo y.

Peter Collingridge
fonte
Muito obrigado. Girar funciona perfeitamente eu acho, mas escalar / redimensionar sempre acaba saindo por algum valor aleatório. Tentei usar a matriz e fazê-los separadamente como "traduzir (cx sx, cy sy) escala (sx, sy)". O mesmo resultado.
CTheDark
6
Não seria transform = "matriz (sx, 0, 0, sy, cx-sx cx, cy-sy cy)"? Porque você quer traduzir apenas pela diferença. Acho que essa lógica está correta, mas ainda me dá posições
erradas
Agora funciona! Eu estava apenas usando valores de cx e cy errados! Muito obrigado!
CTheDark
1
@JayStevens Sim, a transformação da matriz funcionará para qualquer sistema, é apenas matemática. A única diferença pode ser a notação. Pelo que eu posso dizer, a transformação da matriz CSS usa a mesma notação que para SVGs, portanto, os mesmos seis números nessa ordem devem funcionar.
Peter Collingridge
1
Só para esclarecer, cx e cy precisam ser deslocados do centro da caixa em que você está escalando. Isso me confundiu um pouco, porque eu estava pensando em modelos de caixa CSS. @forresto alude a isso em sua resposta abaixo.
Jason Farnsworth
22

Se você pode usar um valor fixo (não "center" ou "50%"), você pode usar CSS em seu 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;

Alguns navegadores (como o Firefox) não lidam com valores relativos corretamente.

Hafenkranich
fonte
1
Uau. Teria +10 se eu pudesse. Isso salvou meu dia! Obrigado.
Cullub
Como fazer isso e apenas isso em JavaScript?
Andrew S
Gostou element.setAttribute('transform-origin', '25px 25px')?
nikk wong
13

Se você é como eu e deseja aplicar panorâmica e zoom com transform-origin, precisará de um pouco mais.

// <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);
};

Aqui está ele funcionando: http://forresto.github.io/dataflow-prototyping/react/

Forresto
fonte
Seu link do github 404s
NuclearPeon
Olá @NuclearPeon, isso é incrível, mas não consigo implementá-lo no meu código. O elemento SVG que eu gostaria de aplicar já é uma variável chamada "mainGrid". Tentei substituir a instância de "view" por "mainGrid" sem nenhum efeito. o que estou perdendo? Obrigado!
sakeferret
@sakeferret o mais óbvio a verificar é se as idcorrespondências no atributo getElementByIde no id="..."atributo dos documentos . É difícil saber com certeza sem ver o código. Se você não quiser postá-la como uma pergunta SO, você pode tentar criar o exemplo mais simples usando seus nomes de atributos até que funcione / você encontre o problema, então adicione o resto de volta.
NuclearPeon
12

Para escalar sem ter que usar a matrixtransformação:

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

E aqui está em CSS:

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

parece que todos nós perdemos um truque aqui: transform-box: fill-box

usar transform-box: fill-boxfará com que um elemento em um SVG se comporte como um elemento HTML normal. Em seguida, você pode aplicar transform-origin: center(ou outra coisa) como faria normalmente

isso mesmo, transform-box: fill-boxnão há necessidade de nenhum material complicado de matriz

George
fonte
-1

Eu tive uma questão semelhante. Mas eu estava usando o D3 para posicionar meus elementos e queria que a transformação e a transição fossem feitas por CSS. Este era meu código original, que comecei a trabalhar no 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) ...

Isso me permitiu definir as cx, cye transform-originvalores em JavaScript usando os mesmos dados.

MAS isso não funcionou no Firefox! O que eu tive que fazer foi embrulhar o circlena gtag e translateusando a mesma fórmula de posicionamento de cima. Em seguida, anexei o circlena gtag e defini seus valores cxe cycomo 0. A partir daí, transform: scale(2)escalaria do centro conforme o esperado. O código final ficou assim.

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,
  })

Depois de fazer essa alteração, mudei meu CSS para direcionar o em circlevez de .dot, para adicionar o transform: scale(2). Eu nem precisei usar transform-origin.

NOTAS:

  1. Estou usando d3-selection-multino segundo exemplo. Isso me permite passar um objeto para, em .attrsvez de repetir .attrpara cada atributo.

  2. Ao usar um literal de modelo de string, esteja ciente das quebras de linha, conforme ilustrado no primeiro exemplo. Isso incluirá uma nova linha na saída e pode quebrar seu código.

Jamie S
fonte
2
Talvez se você definir transform-box: fill-box; O Firefox teria funcionado da maneira que você queria.
Robert Longson
tentei isso. não pareceu funcionar. Ele fez o trabalho depois que eu mudei o svg:transform-origin.enabledpref na do firefox about:configpágina. Mas ... isso não parecia uma solução adequada. Também encontrei uma discrepância entre transform-origin: 50% 50%e transform-origin: center center.
Jamie S
a pref svg.transform-origin.enabled foi removida muitas versões atrás. A menos que você esteja usando uma versão muito antiga, não deve haver diferença entre 50% e o centro. Sinta-se à vontade para levantar um bug no bugzilla se houver alguma diferença no Firefox 59 ou posterior.
Robert Longson