วิธีตั้งค่าจุดเริ่มต้นการแปลงใน SVG


105

ฉันต้องการปรับขนาดและหมุนองค์ประกอบบางอย่างในเอกสาร SVG โดยใช้จาวาสคริปต์ ปัญหาคือโดยค่าเริ่มต้นจะใช้การแปลงรอบจุดเริ่มต้นที่(0, 0)- ซ้ายบนเสมอ

ฉันจะกำหนดจุดยึดการแปลงนี้ใหม่ได้อย่างไร

ฉันพยายามใช้transform-originแอตทริบิวต์ แต่ไม่ส่งผลใด ๆ

นี่คือวิธีที่ฉันทำ:

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

ดูเหมือนจะไม่ได้กำหนดจุดสำคัญเป็นจุดที่ฉันระบุแม้ว่าฉันจะเห็นใน Firefox ว่ามีการตั้งค่าแอตทริบิวต์อย่างถูกต้อง ฉันพยายามสิ่งที่ชอบcenter bottomและ50% 100%มีและไม่มีวงเล็บ จนถึงขณะนี้ไม่มีอะไรทำงาน

ใครสามารถช่วย?


FWIW ซึ่งคาดว่าจะได้รับการแก้ไขเมื่อ Firefox 19 เบต้า 3 แม้ว่าฉันจะยังคงมีปัญหาใน Firefox 22 ก็ตามรายการ Mozilla bugzilla: bugzilla.mozilla.org/show_bug.cgi?id=828286
Toph

คำตอบ:


148

เพื่อหมุนใช้ transform="rotate(deg, cx, cy)"โดยที่ deg คือองศาที่คุณต้องการหมุนและ (cx, cy) กำหนดจุดศูนย์กลางของการหมุน

สำหรับการปรับขนาด / การปรับขนาดคุณต้องแปลโดย (-cx, -cy) จากนั้นปรับขนาดแล้วแปลกลับเป็น (cx, cy) คุณสามารถทำได้ด้วยการแปลงเมทริกซ์ :

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

โดยที่ sx คือตัวคูณมาตราส่วนในแกน x, sy ในแกน y


ขอบคุณมาก. ฉันคิดว่าการหมุนทำงานได้อย่างสมบูรณ์แบบ แต่การปรับขนาด / การปรับขนาดมักจะจบลงด้วยการสุ่มจำนวน ฉันลองใช้เมทริกซ์และแยกมันออกเช่น "translate (cx sx, cy sy) scale (sx, sy)" ผลลัพธ์เดียวกัน
CTheDark

6
จะไม่แปลง = "เมทริกซ์ (sx, 0, 0, sy, cx-sx cx, cy-sy cy)"? เพราะคุณต้องการแปลตามความแตกต่างเท่านั้น ฉันคิดว่าตรรกะนี้ถูกต้อง แต่ก็ยังทำให้ฉันออกจากตำแหน่งได้ ...
CTheDark

ตอนนี้ใช้งานได้แล้ว! ฉันแค่ใช้ค่า cx และ cy ผิด! ขอบคุณมาก!
CTheDark

1
@JayStevens ใช่การแปลงเมทริกซ์จะใช้ได้กับทุกระบบมันเป็นแค่คณิตศาสตร์ ความแตกต่างเพียงอย่างเดียวอาจเป็นสัญกรณ์ เท่าที่ฉันสามารถบอกได้การแปลงเมทริกซ์ CSS ใช้สัญกรณ์เดียวกันกับ SVG ดังนั้นตัวเลขหกตัวเดียวกันในลำดับนั้นควรใช้งานได้
Peter Collingridge

1
เพื่อให้ชัดเจนว่า cx และ cy จะต้องมีการชดเชยตรงกลางของกล่องที่คุณกำลังสเกลอยู่สิ่งนี้ทำให้ฉันสะดุดขึ้นเล็กน้อยอย่างที่ฉันคิดในโมเดลกล่อง CSS @forresto พูดถึงสิ่งนี้ในคำตอบของเขาด้านล่าง
Jason Farnsworth

22

หากคุณสามารถใช้ค่าคงที่ (ไม่ใช่ "center" หรือ "50%") คุณสามารถใช้ 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;

เบราว์เซอร์บางตัว (เช่น Firefox) จะจัดการค่าสัมพัทธ์ไม่ถูกต้อง


1
ว้าว. จะ +10 ถ้าฉันทำได้ วันนี้ช่วยฉันไว้! ขอบคุณ.
Cullub

วิธีการทำและสิ่งนี้ใน JavaScript?
Andrew S

ชอบelement.setAttribute('transform-origin', '25px 25px')?
nikk wong

13

ถ้าคุณชอบฉันและต้องการแพนแล้วซูมด้วยการเปลี่ยนแปลงที่มาคุณจะต้องมีอีกเล็กน้อย

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

นี่คือการทำงาน: http://forresto.github.io/dataflow-prototyping/react/


ลิงก์ github
404s

สวัสดี @NuclearPeon นี้ยอดเยี่ยมมาก แต่ดูเหมือนจะใช้ในโค้ดไม่ได้ องค์ประกอบ SVG ที่ฉันต้องการใช้เป็นตัวแปรที่เรียกว่า "mainGrid" อยู่แล้ว ฉันพยายามแทนที่อินสแตนซ์ของ "view" ด้วย "mainGrid" โดยไม่มีผลใด ๆ ฉันขาดอะไรไป? ขอบคุณ!
สาเกเฟอร์เร็ต

@sakeferret สิ่งที่ชัดเจนที่สุดในการตรวจสอบคือการidจับคู่ในแอตทริบิวต์getElementByIdและเอกสาร id="..."ยากที่จะทราบได้อย่างแน่นอนโดยไม่ต้องเห็นรหัส หากคุณไม่ต้องการโพสต์เป็นคำถาม SO คุณสามารถลองสร้างตัวอย่างที่ง่ายที่สุดโดยใช้ชื่อแอตทริบิวต์ของคุณจนกว่าจะใช้งานได้ / คุณพบปัญหาจากนั้นเพิ่มส่วนที่เหลือกลับเข้าไป
NuclearPeon

13

สำหรับการปรับขนาดโดยไม่ต้องใช้การmatrixแปลง:

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

และนี่คือ CSS:

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

3

ดูเหมือนว่าเราจะพลาดเคล็ดลับที่นี่: transform-box: fill-box

การใช้transform-box: fill-boxจะทำให้องค์ประกอบภายใน SVG ทำงานเป็นองค์ประกอบ HTML ปกติ จากนั้นคุณสามารถสมัครtransform-origin: center(หรืออย่างอื่น) ได้ตามปกติ

ถูกtransform-box: fill-boxต้องไม่จำเป็นต้องมีเมทริกซ์ที่ซับซ้อน


2
ทางออกที่มีประโยชน์มากสิ่งนี้จะได้รับการโหวตให้สูงขึ้น นี่คือสิ่งที่ฉันต้องการ ขอบคุณ! องค์ประกอบ svg และ html มีความแตกต่างเล็กน้อยเมื่อเปลี่ยนรูปและเปลี่ยน ฉันงงงวยกับข้อผิดพลาดของแอนิเมชั่นเป็นเวลาหลายชั่วโมงจนกระทั่งฉันพบคำตอบนี้ ... transform-box: fill-box;คุณสามารถทำให้องค์ประกอบ svg เคารพtransform-originในลักษณะที่มีสติ / อย่างที่คุณคาดหวัง!
zfogg

-1

ฉันมีปัญหาที่คล้ายกัน แต่ฉันใช้D3เพื่อวางตำแหน่งองค์ประกอบของฉันและต้องการให้ CSS จัดการการแปลงและการเปลี่ยนแปลง นี่เป็นรหัสเดิมของฉันซึ่งฉันใช้งานได้ใน 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) ...

นี้ได้รับอนุญาตให้ฉันไปตั้งค่าcx, cyและtransform-originค่าในจาวาสคริปต์โดยใช้ข้อมูลเดียวกัน

แต่สิ่งนี้ใช้ไม่ได้ใน Firefox! สิ่งที่ฉันต้องทำคือห่อcircleในgแท็กและtranslateใช้สูตรการวางตำแหน่งเดียวกันจากด้านบน จากนั้นผมก็ต่อท้ายcircleในgแท็กและการตั้งค่าของcxและค่าcy 0จากนั้นtransform: scale(2)จะปรับขนาดจากจุดศูนย์กลางตามที่คาดไว้ รหัสสุดท้ายมีลักษณะเช่นนี้

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

หลังจากทำการเปลี่ยนแปลงนี้ฉันเปลี่ยน CSS ของฉันเพื่อกำหนดเป้าหมายcircleแทนการ.dotเพิ่มไฟล์transform: scale(2). ฉันไม่จำเป็นต้องใช้transform-originด้วยซ้ำ

หมายเหตุ:

  1. ฉันใช้d3-selection-multiในตัวอย่างที่สอง สิ่งนี้ช่วยให้ฉันส่งผ่านวัตถุไป.attrsแทนที่จะทำซ้ำ.attrสำหรับทุกแอตทริบิวต์

  2. เมื่อใช้สตริงเท็มเพลตลิเทอรัลโปรดระวังการแบ่งบรรทัดตามที่แสดงในตัวอย่างแรก ซึ่งจะรวมถึงการขึ้นบรรทัดใหม่ในผลลัพธ์และอาจทำให้โค้ดของคุณเสียหาย


2
บางทีถ้าคุณตั้งค่า transform-box: fill-box; Firefox จะทำงานในแบบที่คุณต้องการ
Robert Longson

พยายามแล้ว ดูเหมือนจะไม่ได้ผล มันไม่ทำงานหลังจากที่ผมเปลี่ยนsvg:transform-origin.enabledคำนำใน Firefox ของabout:configหน้า แต่ ... นั่นดูเหมือนจะไม่ใช่วิธีแก้ปัญหาที่เพียงพอ ฉันยังพบความแตกต่างระหว่างและtransform-origin: 50% 50% transform-origin: center center
Jamie S

คำนำหน้า svg.transform-origin.enabled ถูกลบออกหลายเวอร์ชันที่ผ่านมา เว้นแต่คุณจะใช้เวอร์ชันเก่ามากไม่ควรมีความแตกต่างระหว่าง 50% และศูนย์ อย่าลังเลที่จะเพิ่มข้อผิดพลาด bugzilla หากมีความแตกต่างใน Firefox 59 หรือใหม่กว่า
Robert Longson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.