Jak ustawić pochodzenie transformacji w SVG

105

Muszę zmienić rozmiar i obrócić niektóre elementy w dokumencie SVG za pomocą javascript. Problem polega na tym, że domyślnie zawsze stosuje transformację wokół początku w (0, 0)lewym górnym rogu.

Jak mogę ponownie zdefiniować ten punkt zakotwiczenia transformacji?

Próbowałem użyć transform-originatrybutu, ale na nic to nie wpływa.

Oto jak to zrobiłem:

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

Nie wydaje się, aby ustawić punkt zwrotny w punkcie, który określiłem, chociaż widzę w przeglądarce Firefox, że atrybut jest poprawnie ustawiony. Próbowałem takich rzeczy, jak center bottomiz 50% 100%nawiasami lub bez. Jak dotąd nic nie działało.

Czy ktoś może pomóc?

CTheDark
źródło
FWIW, podobno naprawione w Firefoksie 19 beta 3, chociaż nadal mam problemy z Firefoksem 22. Lista błędów Mozilli: bugzilla.mozilla.org/show_bug.cgi?id=828286
Toph

Odpowiedzi:

148

Aby obrócić użyj transform="rotate(deg, cx, cy)", gdzie deg jest stopniem, który chcesz obrócić, a (cx, cy) definiuje środek obrotu.

Aby skalować / zmienić rozmiar, musisz przetłumaczyć przez (-cx, -cy), następnie skalować, a następnie przetłumaczyć z powrotem na (cx, cy). Możesz to zrobić za pomocą transformacji macierzowej :

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

Gdzie sx jest współczynnikiem skalowania na osi x, sy na osi y.

Peter Collingridge
źródło
Dziękuję bardzo. Myślę, że obracanie działa idealnie, ale skalowanie / zmiana rozmiaru zawsze kończy się o jakąś losową wartość. Próbowałem użyć macierzy i zrobić je osobno, np. „Translate (cx sx, cy sy) scale (sx, sy)”. Ten sam wynik.
CTheDark,
6
Czy nie byłoby to transform = "matrix (sx, 0, 0, sy, cx-sx cx, cy-sy cy)"? Ponieważ chcesz tłumaczyć tylko przez różnicę. Myślę, że ta logika jest prawidłowa, ale wciąż daje mi od pozycji ...
CTheDark
Teraz działa! Po prostu używałem złych wartości cx i cy! Wielkie dzięki!
CTheDark,
1
@JayStevens Tak, transformacja macierzy będzie działać dla każdego systemu, to tylko matematyka. Jedyną różnicą może być notacja. O ile wiem, transformacja macierzy CSS używa tej samej notacji, co w przypadku SVG, więc te same sześć liczb w tej kolejności powinno działać.
Peter Collingridge
1
Dla wyjaśnienia, cx i cy muszą być przesunięte w stosunku do środka pola, w którym skalujesz się. To mnie trochę zaskoczyło, jak myślałem w modelach CSS. @forresto nawiązuje do tego w swojej odpowiedzi poniżej.
Jason Farnsworth,
22

Jeśli możesz użyć stałej wartości (nie „wyśrodkuj” ani „50%”), możesz zamiast tego użyć 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;

Niektóre przeglądarki (takie jak Firefox) nie obsługują poprawnie wartości względnych.

Hafenkranich
źródło
1
Łał. Zrobiłbym +10, gdybym mógł. To uratowało mi dzień! Dzięki.
Cullub
Jak to zrobić i tylko to w JavaScript?
Andrew S
Lubisz element.setAttribute('transform-origin', '25px 25px')?
nikk wong
13

Jeśli jesteś podobny do mnie i chcesz przesuwać, a następnie powiększać przy użyciu źródła transformacji, potrzebujesz trochę więcej.

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

Tutaj to działa: http://forresto.github.io/dataflow-prototyping/react/

forresto
źródło
Twój link do github 404s
NuclearPeon
Witaj @NuclearPeon, to jest niesamowite, ale nie mogę tego zaimplementować w swoim kodzie. Element SVG, do którego chciałbym zastosować, jest już zmienną o nazwie „mainGrid”. Próbowałem zamienić wystąpienie „view” na „mainGrid” bez żadnego efektu. czego mi brakuje? Dzięki!
sakeferret
@sakeferret najbardziej oczywistych rzeczy, aby sprawdzić na to, że idmecze w getElementByIdoraz dokumenty id="..."atrybut. Trudno wiedzieć na pewno bez zapoznania się z kodem. Jeśli nie chcesz publikować tego jako pytania SO, możesz spróbować utworzyć najprostszy przykład, używając nazw atrybutów, dopóki nie zadziała / nie znajdziesz problemu, a następnie dodaj resztę z powrotem.
NuclearPeon
13

Do skalowania bez użycia matrixtransformacji:

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

A oto w CSS:

transform: translate(cxpx, cypx) scale(sx, sy) translate(-cxpx, -cypx)
cmititiuc
źródło
3

wygląda na to, że wszyscy przegapiliśmy tutaj sztuczkę: transform-box: fill-box

użycie transform-box: fill-boxspowoduje, że element w SVG będzie zachowywał się jak zwykły element HTML. Następnie możesz złożyć wniosek transform-origin: center(lub coś innego) tak, jak zwykle

to prawda, transform-box: fill-boxnie ma potrzeby stosowania skomplikowanej matrycy

Jerzy
źródło
2
BARDZO przydatne rozwiązanie, to powinno być wyżej przegłosowane. to jest dokładnie to, czego potrzebowałem; Dziękuję Ci! Elementy svg i html mają subtelne różnice podczas przekształcania i przenoszenia. Byłem zdumiony błędem animacji przez wiele godzin, dopóki nie znalazłem tej odpowiedzi ... dzięki transform-box: fill-box;tobie możesz sprawić, że elementy svg szanują się transform-originw rozsądny sposób / tak, jak można się spodziewać!
zfogg
-1

Miałem podobny problem. Ale używałem D3 do pozycjonowania moich elementów i chciałem, aby transformacja i przejście były obsługiwane przez CSS. To był mój oryginalny kod, który otrzymałem pracując w 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) ...

Pozwoliło mi to ustawić wartości cx, cyi transform-originw javascript przy użyciu tych samych danych.

ALE to nie działało w Firefoksie! Co miałem robić zawinąć circlew gznaczniku i translateże przy użyciu tego samego wzoru pozycjonowania z góry. Następnie dołączyłem circlew gtagu i ustawiłem jego cxi cywartości na 0. Stamtąd transform: scale(2)będzie skalowany od środka zgodnie z oczekiwaniami. Ostateczny kod wyglądał następująco.

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

Po wprowadzeniu tej zmiany zmieniłem mój CSS, aby kierował się na circlezamiast .dot, aby dodać transform: scale(2). Nawet nie potrzebowałem użycia transform-origin.

UWAGI:

  1. Używam d3-selection-multiw drugim przykładzie. To pozwala mi przekazać obiekt .attrszamiast powtarzać go .attrdla każdego atrybutu.

  2. Korzystając z literału szablonu ciągu, należy pamiętać o podziałach wiersza, jak pokazano w pierwszym przykładzie. Spowoduje to wyświetlenie nowej linii w danych wyjściowych i może spowodować uszkodzenie kodu.

Jamie S.
źródło
2
Być może, jeśli ustawisz transform-box: fill-box; Firefox działałby tak, jak chciałeś.
Robert Longson,
próbowałem tego. nie wydawało się działać. To zrobił pracę po tym, jak zmienił svg:transform-origin.enabledpref w Firefoksa about:configstronie. Ale ... to nie wydawało się odpowiednim rozwiązaniem. Znalazłem również rozbieżność między transform-origin: 50% 50%a transform-origin: center center.
Jamie S
prefiks svg.transform-origin.enabled został usunięty wiele wersji temu. O ile nie używasz bardzo starej wersji, nie powinno być różnicy między 50% a środkiem. Zapraszam do zgłaszania błędu bugzilli, jeśli istnieje różnica w Firefoksie 59 lub nowszym.
Robert Longson