Comment définir l'origine de la transformation dans SVG

104

J'ai besoin de redimensionner et de faire pivoter certains éléments d'un document SVG à l'aide de javascript. Le problème est que, par défaut, il applique toujours la transformation autour de l'origine en (0, 0)haut à gauche.

Comment redéfinir ce point d'ancrage de transformation?

J'ai essayé d'utiliser l' transform-originattribut, mais cela n'affecte rien.

Voici comment je l'ai fait:

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

Il ne semble pas définir le point pivot au point que j'ai spécifié bien que je puisse voir dans Firefox que l'attribut est correctement défini. J'ai essayé des choses comme center bottomet 50% 100%avec et sans parenthèses. Rien n'a fonctionné jusqu'à présent.

Quelqu'un peut-il aider?

CTheDark
la source
FWIW, censé être corrigé à partir de Firefox 19 beta 3, bien que j'aie toujours des problèmes dans Firefox 22. Liste de bugzilla de Mozilla: bugzilla.mozilla.org/show_bug.cgi?id=828286
Toph

Réponses:

147

Pour faire pivoter, utilisez transform="rotate(deg, cx, cy)", où deg est le degré que vous souhaitez faire pivoter et (cx, cy) définit le centre de rotation.

Pour la mise à l'échelle / redimensionnement, vous devez traduire par (-cx, -cy), puis mettre à l'échelle et ensuite traduire en (cx, cy). Vous pouvez le faire avec une transformation matricielle :

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

Où sx est le facteur d'échelle sur l'axe des x, sy sur l'axe des y.

Peter Collingridge
la source
Merci beaucoup. La rotation fonctionne parfaitement, je pense, mais la mise à l'échelle / le redimensionnement finissent toujours par avoir un décalage aléatoire. J'ai essayé d'utiliser la matrice et de les faire séparément comme "translate (cx sx, cy sy) scale (sx, sy)". Le même résultat.
CTheDark
6
Ne serait-ce pas transform = "matrix (sx, 0, 0, sy, cx-sx cx, cy-sy cy)"? Parce que vous ne voulez traduire que par la différence. Je pense que cette logique est correcte, mais cela me donne toujours des positions ...
CTheDark
Maintenant ça marche! J'utilisais juste de mauvaises valeurs cx et cy! Merci beaucoup!
CTheDark
1
@JayStevens Oui, la transformation matricielle fonctionnera pour n'importe quel système, ce ne sont que des mathématiques. La seule différence pourrait être la notation. Pour autant que je sache, la transformation matricielle CSS utilise la même notation que pour les SVG, donc les mêmes six nombres dans cet ordre devraient fonctionner.
Peter Collingridge
1
Juste pour clarifier, cx et cy doivent être des décalages du centre de la boîte dans votre mise à l'échelle. Cela m'a fait trébucher un peu, comme je le pensais dans les modèles de boîtes CSS. @forresto y fait allusion dans sa réponse ci-dessous.
Jason Farnsworth
22

Si vous pouvez utiliser une valeur fixe (pas "center" ou "50%"), vous pouvez utiliser CSS à la place:

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

Certains navigateurs (comme Firefox) ne gèrent pas correctement les valeurs relatives.

Hafenkranich
la source
1
Sensationnel. Serait +10 si je pouvais. Cela m'a sauvé la journée! Merci.
Cullub
Comment faire cela et juste cela en JavaScript?
Andrew S
Comme element.setAttribute('transform-origin', '25px 25px')?
nikk wong
13

Si vous êtes comme moi et que vous souhaitez effectuer un panoramique et un zoom avec l'origine de la transformation, vous aurez besoin d'un peu plus.

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

Ici, cela fonctionne: http://forresto.github.io/dataflow-prototyping/react/

Forresto
la source
Votre lien github 404s
NuclearPeon
Bonjour @NuclearPeon, c'est génial, mais je n'arrive pas à l'implémenter dans mon code. L'élément SVG auquel je voudrais qu'il s'applique est déjà une variable appelée "mainGrid". J'ai essayé de remplacer l'instance de "view" par "mainGrid" sans effet. Qu'est-ce que je rate? Merci!
sakeferret
@sakeferret la chose la plus évidente à vérifier est que les idcorrespondances dans l' attribut getElementByIdet les documents id="...". Il est difficile de savoir avec certitude sans voir le code. Si vous ne voulez pas le publier en tant que question SO, vous pouvez essayer de créer l'exemple le plus simple en utilisant vos noms d'attributs jusqu'à ce que cela fonctionne / que vous trouviez le problème, puis ajoutez le reste.
NuclearPeon
12

Pour une mise à l'échelle sans avoir à utiliser la matrixtransformation:

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

Et le voici en CSS:

transform: translate(cxpx, cypx) scale(sx, sy) translate(-cxpx, -cypx)
cmititiuc
la source
1

il semble que nous ayons tous manqué un truc ici: transform-box: fill-box

en utilisant transform-box: fill-box, un élément dans un SVG se comportera comme un élément HTML normal. Ensuite, vous pouvez postuler transform-origin: center(ou autre chose) comme vous le feriez normalement

c'est vrai, transform-box: fill-boxpas besoin de trucs matriciels compliqués

George
la source
Solution TRÈS utile, cela devrait être voté plus haut. c'est exactement ce dont j'avais besoin; Merci! Les éléments svg et html présentent des différences subtiles lors de la transformation et de la transition. J'ai été perplexe avec un bug d'animation pendant des heures jusqu'à ce que je trouve cette réponse ... avec transform-box: fill-box;vous pouvez faire respecter les éléments svg d' transform-originune manière saine / comme vous vous en doutez !
zfogg
-1

J'ai eu un problème similaire. Mais j'utilisais D3 pour positionner mes éléments, et je voulais que la transformation et la transition soient gérées par CSS. C'était mon code d'origine, que j'ai fait travailler dans 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) ...

Cela m'a permis de définir les cx, cyet les transform-originvaleurs en javascript en utilisant les mêmes données.

MAIS cela n'a pas fonctionné dans Firefox! Ce que je devais faire était envelopper la circledans l' gétiquette et translateen utilisant la même formule de positionnement d' en haut. J'ai ensuite ajouté le circledans la gbalise et défini ses valeurs cxet cysur 0. À partir de là, transform: scale(2)serait mis à l'échelle du centre comme prévu. Le code final ressemblait à ceci.

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

Après avoir apporté ce changement, j'ai changé mon CSS pour cibler le circleplutôt que le .dot, pour ajouter le transform: scale(2). Je n'avais même pas besoin d'être utilisé transform-origin.

REMARQUES:

  1. J'utilise d3-selection-multidans le deuxième exemple. Cela me permet de passer un objet au .attrslieu de le répéter .attrpour chaque attribut.

  2. Lorsque vous utilisez un littéral de modèle de chaîne, tenez compte des sauts de ligne, comme illustré dans le premier exemple. Cela inclura une nouvelle ligne dans la sortie et peut casser votre code.

Jamie S
la source
2
Peut-être si vous définissez transform-box: fill-box; Firefox aurait fonctionné comme vous le souhaitiez.
Robert Longson
essayé ça. ne semblait pas fonctionner. Cela a fonctionné après avoir changé la svg:transform-origin.enabledpréférence dans la about:configpage de Firefox . Mais ... cela ne semblait pas être une solution adéquate. J'ai également trouvé une différence entre transform-origin: 50% 50%et transform-origin: center center.
Jamie S
la préférence svg.transform-origin.enabled a été supprimée il y a de nombreuses versions. Sauf si vous utilisez une très ancienne version, il ne devrait y avoir aucune différence entre 50% et le centre. N'hésitez pas à signaler un bug bugzilla s'il y a une différence dans Firefox 59 ou version ultérieure.
Robert Longson