Come impostare l'origine della trasformazione in SVG


104

Ho bisogno di ridimensionare e ruotare alcuni elementi nel documento SVG usando javascript. Il problema è che, per impostazione predefinita, applica sempre la trasformazione attorno all'origine in (0, 0)alto a sinistra.

Come posso ridefinire questo punto di ancoraggio della trasformazione?

Ho provato a utilizzare l' transform-originattributo, ma non ha alcun effetto.

Ecco come l'ho fatto:

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

Non sembra impostare il punto cardine sul punto che ho specificato anche se posso vedere in Firefox che l'attributo è impostato correttamente. Ho provato cose come center bottome 50% 100%con e senza parentesi. Finora niente ha funzionato.

Qualcuno può aiutare?


FWIW, presumibilmente corretto a partire da Firefox 19 beta 3, anche se ho ancora problemi con Firefox 22. Elenco bugzilla di Mozilla: bugzilla.mozilla.org/show_bug.cgi?id=828286
Toph

Risposte:


147

Per ruotare usa transform="rotate(deg, cx, cy)", dove deg è il grado che vuoi ruotare e (cx, cy) definisce il centro di rotazione.

Per il ridimensionamento / ridimensionamento, devi tradurre con (-cx, -cy), quindi ridimensionare e quindi tradurre nuovamente in (cx, cy). Puoi farlo con una trasformazione di matrice :

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

Dove sx è il fattore di scala nell'asse x, sy nell'asse y.


Grazie mille. Ruota funziona perfettamente, penso, ma il ridimensionamento / ridimensionamento finisce sempre per avere un valore casuale. Ho provato a utilizzare la matrice e ad eseguirli separatamente come "translate (cx sx, cy sy) scale (sx, sy)". Lo stesso risultato.
CTheDark

6
Non sarebbe transform = "matrix (sx, 0, 0, sy, cx-sx cx, cy-sy cy)"? Perché vuoi tradurre solo in base alla differenza. Penso che questa logica sia corretta, ma mi dà comunque delle posizioni fuori ...
CTheDark

Ora funziona! Stavo solo usando valori cx e cy sbagliati! Molte grazie!
CTheDark

1
@JayStevens Sì, la trasformazione della matrice funzionerà per qualsiasi sistema, è solo matematica. L'unica differenza potrebbe essere la notazione. Per quanto ne so, la trasformazione della matrice CSS utilizza la stessa notazione degli SVG, quindi dovrebbero funzionare gli stessi sei numeri in quell'ordine.
Peter Collingridge

1
Giusto per chiarire, cx e cy devono essere offset del centro della scatola in cui si ridimensiona. Questo mi ha fatto inciampare per un po ', come stavo pensando nei modelli di box CSS. @forresto allude a questo nella sua risposta qui sotto.
Jason Farnsworth

22

Se puoi utilizzare un valore fisso (non "center" o "50%"), puoi invece utilizzare CSS:

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

Alcuni browser (come Firefox) non gestiranno correttamente i valori relativi.


1
Wow. Avrei +10 se potessi. Questo mi ha salvato la giornata! Grazie.
Cullub

Come fare questo e solo questo in JavaScript?
Andrew S

Come element.setAttribute('transform-origin', '25px 25px')?
nikk wong

13

Se sei come me e vuoi fare una panoramica e poi zoomare con Transform-Origin, avrai bisogno di un po 'di più.

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

Qui funziona: http://forresto.github.io/dataflow-prototyping/react/


Il tuo link GitHub 404s
NuclearPeon

Ciao @NuclearPeon, è fantastico, ma non riesco a implementarlo nel mio codice. L'elemento SVG a cui vorrei che si applicasse è già una variabile chiamata "mainGrid". Ho provato a sostituire l'istanza di "view" con "mainGrid" senza alcun effetto. Cosa mi sto perdendo? Grazie!
sakeferret

@sakeferret la cosa più ovvia da controllare è che le idcorrispondenze nell'attributo getElementByIde nei documenti id="...". È difficile sapere con certezza senza vedere il codice. Se non vuoi pubblicarla come domanda SO, puoi provare a creare l'esempio più semplice usando i nomi degli attributi finché non funziona / trovi il problema, quindi aggiungi di nuovo il resto.
NuclearPeon

12

Per il ridimensionamento senza dover utilizzare la matrixtrasformazione:

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

Ed eccolo qui in CSS:

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

0

sembra che tutti abbiamo perso un trucco qui: transform-box: fill-box

l'utilizzo transform-box: fill-boxfarà sì che un elemento all'interno di un SVG si comporti come un normale elemento HTML. Quindi puoi applicare transform-origin: center(o qualcos'altro) come faresti normalmente

è vero, transform-box: fill-boxnon c'è bisogno di complicate cose sulla matrice


-1

Ho avuto un problema simile. Ma stavo usando D3 per posizionare i miei elementi e volevo che la trasformazione e la transizione fossero gestite dai CSS. Questo era il mio codice originale, che ho ottenuto lavorando in 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) ...

Questo mi ha permesso di impostare la cx, cyei transform-originvalori in JavaScript utilizzando gli stessi dati.

MA questo non ha funzionato in Firefox! Quello che ho dovuto fare è stato avvolgere il circlenel gtag e translateutilizzando la stessa formula posizionamento dall'alto. Ho quindi aggiunto il circlenel gtag e ho impostato i suoi valori cxe cysu 0. Da lì, transform: scale(2)scalerebbe dal centro come previsto. Il codice finale era così.

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

Dopo aver apportato questa modifica, ho cambiato il mio CSS per scegliere come target circleinvece di .dot, per aggiungere il file transform: scale(2). Non ne avevo nemmeno bisogno transform-origin.

APPUNTI:

  1. Sto usando d3-selection-multinel secondo esempio. Questo mi permette di passare un oggetto a .attrsinvece di ripetere .attrper ogni attributo.

  2. Quando si utilizza un modello di stringa letterale, prestare attenzione alle interruzioni di riga come illustrato nel primo esempio. Ciò includerà una nuova riga nell'output e potrebbe interrompere il codice.


2
Forse se imposti la casella di trasformazione: casella di riempimento; Firefox avrebbe funzionato come volevi.
Robert Longson

provato quello. non sembra funzionare. E ha fatto il lavoro dopo la modifica delle svg:transform-origin.enabledpref in di Firefox about:configpagina. Ma ... non sembrava una soluzione adeguata. Ho anche riscontrato una discrepanza tra transform-origin: 50% 50%e transform-origin: center center.
Jamie S

il pref svg.transform-origin.enabled è stato rimosso molte versioni fa. A meno che tu non stia utilizzando una versione molto vecchia, non dovrebbe esserci alcuna differenza tra il 50% e il centro. Sentiti libero di segnalare un bug di bugzilla se c'è una differenza in Firefox 59 o versioni successive.
Robert Longson
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.