So legen Sie den Transformationsursprung in SVG fest

104

Ich muss die Größe bestimmter Elemente im SVG-Dokument mithilfe von Javascript ändern und drehen. Das Problem ist, dass standardmäßig immer die Transformation um den Ursprung (0, 0)oben links angewendet wird.

Wie kann ich diesen Transformationsankerpunkt neu definieren?

Ich habe versucht, das transform-originAttribut zu verwenden, aber es hat keine Auswirkungen.

So habe ich es gemacht:

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

Es scheint nicht, dass der Drehpunkt auf den von mir angegebenen Punkt gesetzt wird, obwohl ich in Firefox sehen kann, dass das Attribut korrekt gesetzt ist. Ich habe Dinge wie center bottomund 50% 100%mit und ohne Klammern ausprobiert . Bisher hat nichts funktioniert.

Kann jemand helfen?

CTheDark
quelle
FWIW, angeblich behoben ab Firefox 19 Beta 3, obwohl ich immer noch Probleme in Firefox 22 habe. Mozilla Bugzilla-Liste: bugzilla.mozilla.org/show_bug.cgi?id=828286
Toph

Antworten:

147

Zum Drehen verwenden Sie transform="rotate(deg, cx, cy)", wobei deg der Grad ist, den Sie drehen möchten, und (cx, cy) den Drehpunkt definieren.

Zum Skalieren / Ändern der Größe müssen Sie mit (-cx, -cy) übersetzen, dann skalieren und dann zurück nach (cx, cy) übersetzen. Sie können dies mit einer Matrixtransformation tun :

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

Wobei sx der Skalierungsfaktor auf der x-Achse ist, sy auf der y-Achse.

Peter Collingridge
quelle
Ich danke dir sehr. Drehen funktioniert meiner Meinung nach perfekt, aber Skalieren / Ändern der Größe führt immer zu einem zufälligen Rückgang. Ich habe versucht, die Matrix zu verwenden und sie separat wie "translate (cx sx, cy sy) scale (sx, sy)" auszuführen. Das gleiche Ergebnis.
CTheDark
6
Wäre es nicht transform = "Matrix (sx, 0, 0, sy, cx-sx cx, cy-sy cy)"? Weil Sie nur durch den Unterschied übersetzen wollen. Ich denke, diese Logik ist richtig, aber sie gibt mir immer noch Positionen ab ...
CTheDark
Jetzt gehts! Ich habe nur falsche cx- und cy-Werte verwendet! Vielen Dank!
CTheDark
1
@ JayStevens Ja, die Matrixtransformation funktioniert für jedes System, es ist nur Mathematik. Der einzige Unterschied könnte die Notation sein. Soweit ich das beurteilen kann, verwendet die CSS-Matrixtransformation dieselbe Notation wie für SVGs, sodass die gleichen sechs Zahlen in dieser Reihenfolge funktionieren sollten.
Peter Collingridge
1
Nur um zu verdeutlichen, müssen cx und cy Offsets der Mitte der Box sein, in die Sie skalieren. Dies hat mich ein wenig gestolpert, wie ich in CSS-Boxmodellen gedacht habe. @forresto spielt in seiner Antwort unten darauf an.
Jason Farnsworth
22

Wenn Sie einen festen Wert verwenden können (nicht "Mitte" oder "50%"), können Sie stattdessen CSS verwenden:

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

Einige Browser (wie Firefox) verarbeiten relative Werte nicht korrekt.

Hafenkranich
quelle
1
Beeindruckend. Würde +10 wenn ich könnte. Das hat mir den Tag gerettet! Vielen Dank.
Cullub
Wie mache ich das und nur das in JavaScript?
Andrew S
Wie element.setAttribute('transform-origin', '25px 25px')?
Nikk Wong
13

Wenn Sie wie ich sind und mit transform-origin schwenken und dann zoomen möchten, benötigen Sie etwas mehr.

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

Hier funktioniert es: http://forresto.github.io/dataflow-prototyping/react/

forresto
quelle
Ihr Github Link 404s
NuclearPeon
Hallo @NuclearPeon, das ist großartig, aber ich kann es scheinbar nicht in meinen Code implementieren. Das SVG-Element, auf das es angewendet werden soll, ist bereits eine Variable namens "mainGrid". Ich habe versucht, die Instanz von "view" ohne Wirkung durch "mainGrid" zu ersetzen. Was vermisse ich? Vielen Dank!
Sakeferret
@sakeferret Die offensichtlichsten Dinge, auf die Sie achten müssen, sind die idÜbereinstimmungen im Attribut getElementByIdund im Dokumentattribut id="...". Es ist schwer sicher zu wissen, ohne den Code zu sehen. Wenn Sie es nicht als SO-Frage veröffentlichen möchten, können Sie versuchen, das einfachste Beispiel mit Ihren Attributnamen zu erstellen, bis es funktioniert / Sie das Problem finden, und den Rest wieder hinzufügen.
NuclearPeon
12

Zum Skalieren, ohne die matrixTransformation verwenden zu müssen:

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

Und hier ist es in CSS:

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

es sieht so aus, als hätten wir alle hier einen Trick verpasst: transform-box: fill-box

Mit using transform-box: fill-boxverhält sich ein Element in einer SVG wie ein normales HTML-Element. Dann können Sie sich wie gewohnt bewerben transform-origin: center(oder etwas anderes)

Das ist richtig, transform-box: fill-boxkeine Notwendigkeit für komplizierte Matrix-Sachen

George
quelle
-1

Ich hatte ein ähnliches Problem. Aber ich habe D3 verwendet, um meine Elemente zu positionieren, und wollte, dass die Transformation und der Übergang von CSS übernommen werden. Dies war mein ursprünglicher Code, den ich in Chrome 65 zum Laufen gebracht habe:

//...
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) ...

Das erlaubte mir , die zu setzen cx, cyund transform-originWerte in Javascript , die gleichen Daten.

ABER das hat in Firefox nicht funktioniert! Was ich tun musste, war das circlein das gTag zu wickeln und translatees mit der gleichen Positionierungsformel von oben zu verwenden. Ich habe dann das circleim gTag angehängt und seine cxund cyWerte auf gesetzt 0. Von dort transform: scale(2)würde wie erwartet vom Zentrum aus skaliert. Der endgültige Code sah so aus.

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

Nachdem ich diese Änderung vorgenommen hatte, änderte ich mein CSS so, dass es auf das Ziel circleanstatt auf das .dotabzielte, das hinzuzufügen transform: scale(2). Ich brauchte nicht einmal Gebrauch transform-origin.

ANMERKUNGEN:

  1. Ich benutze d3-selection-multiim zweiten Beispiel. Dadurch kann ich ein Objekt übergeben, .attrsanstatt es .attrfür jedes Attribut zu wiederholen .

  2. Beachten Sie bei der Verwendung eines Zeichenfolgenvorlagenliteral die Zeilenumbrüche, wie im ersten Beispiel dargestellt. Dies enthält eine neue Zeile in der Ausgabe und kann Ihren Code beschädigen.

Jamie S.
quelle
2
Vielleicht, wenn Sie transform-box setzen: fill-box; Firefox hätte so funktioniert, wie Sie es wollten.
Robert Longson
habe das versucht. schien nicht zu funktionieren. Es hat funktioniert, nachdem ich die svg:transform-origin.enabledEinstellungen auf der Firefox- about:configSeite geändert habe . Aber ... das schien keine adäquate Lösung zu sein. Ich fand auch eine Diskrepanz zwischen transform-origin: 50% 50%und transform-origin: center center.
Jamie S.
Die Voreinstellung svg.transform-origin.enabled wurde vor vielen Versionen entfernt. Sofern Sie keine sehr alte Version verwenden, sollte es keinen Unterschied zwischen 50% und Mitte geben. Fühlen Sie sich frei, einen Bugzilla-Bug auszulösen, wenn es einen Unterschied in Firefox 59 oder höher gibt.
Robert Longson