Passen Sie das SVG-transformierte Element mit JavaScript an die rechten Grenzen an
Ich kämpfe mit einem Problem, pragmatisch transformiertes SVG-Element in die gegebenen rechten Grenzen zu passen.
- Das Ziel-Rect wird angegeben und nicht transformiert.
- Der Eingang rect hat jede Art von Transformationen.
- Input Rect kann ein Kind jeder transformierten Gruppe sein.
- Transformationen sollten nur auf das Eingangsrecht angewendet werden.
- Diese Frage bezieht sich nur auf die JavaScript-Elementtransformationen.
Es ist eine einfache Aufgabe, wenn das Element nur Transformationen für sich hat:
In diesem Fall entspricht das Verhältnis zwischen Ziel und Eingabe getBoundingClientRect (Begrenzung der Rechtecke in Bildschirmkoordinaten) einem geeigneten Skalierungsfaktor.
Aber es funktioniert nicht, wenn übergeordnete Elemente ebenfalls transformiert werden:
var inputElement = document.getElementById("input");
var destinationElement = document.getElementById("destination");
var inputBB = inputElement.getBoundingClientRect();
var outputBB = destinationElement.getBoundingClientRect();
var scaleX = outputBB.width / inputBB.width;
var scaleY = outputBB.height / inputBB.height;
// get offsets between figure center and destination rect center:
var offsetX = outputBB.x + outputBB.width / 2 - (inputBB.x + inputBB.width / 2);
var offsetY =
outputBB.y + outputBB.height / 2 - (inputBB.y + inputBB.height / 2);
// get current figure transformation
let currentMatrix = (
inputElement.transform.baseVal.consolidate() ||
inputElement.ownerSVGElement.createSVGTransform()
).matrix;
// Get center of figure in element coordinates:
const inputBBox = inputElement.getBBox();
const centerTransform = inputElement.ownerSVGElement.createSVGPoint();
centerTransform.x = inputBBox.x + inputBBox.width / 2;
centerTransform.y = inputBBox.y + inputBBox.height / 2;
// create scale matrix:
const svgTransform = inputElement.ownerSVGElement.createSVGTransform();
svgTransform.setScale(scaleX, scaleY);
let scalingMatrix = inputElement.ownerSVGElement
.createSVGMatrix()
// move the figure to the center of the destination rect.
.translate(offsetX, offsetY)
// Apply current matrix, so old transformations are not lost
.multiply(currentMatrix)
.translate(centerTransform.x, centerTransform.y)
// multiply is used instead of the scale method while for some reasons matrix scale is giving proportional scaling...
// From a transforms proper matrix is generated.
.multiply(svgTransform.matrix)
.translate(-centerTransform.x, -centerTransform.y);
// Apply new created matrix to element back:
const newTransform = inputElement.ownerSVGElement.createSVGTransform();
newTransform.setMatrix(scalingMatrix);
inputElement.transform.baseVal.initialize(newTransform);
var bboundsTest= document.getElementById("bboundsTest");
const resultBBounds = inputElement.getBoundingClientRect();
bboundsTest.setAttribute('x', resultBBounds .x);
bboundsTest.setAttribute('y', resultBBounds .y);
bboundsTest.setAttribute('width', resultBBounds .width);
bboundsTest.setAttribute('height', resultBBounds .height);
document.getElementById('test2').innerHTML = 'expected: 100x100 . Results: ' + resultBBounds.width + 'x' + resultBBounds.height
<svg
version="1.2"
viewBox="0 0 480 150"
width="480"
height="150"
xmlns="http://www.w3.org/2000/svg"
>
<g transform="skewX(10) translate(95,1) rotate(30)">
<g transform="skewX(30) translate(-3,3) rotate(30)">
<g transform="skewX(10) translate(-3,4) rotate(10)">
<rect
id="input"
transform="translate(95,76.5) skewX(25) translate(50,50) scale(1.5) translate(-50,-50) translate(0,0) rotate(45)"
width="30"
height="30"
fill="red"
/>
</g>
</g>
</g>
<rect
id="destination"
x="20"
y="20"
width="100"
height="100"
fill="transparent"
stroke="blue"
/>
<rect
id="bboundsTest"
x="20"
y="20"
width="100"
height="100"
fill="transparent"
stroke="black"
/>
</svg>
<div id="test2"></div>
Irgendwelche Ideen, wie übergeordnete Transformationen in die Zählung einbezogen werden können, um die richtigen Skalierungsfaktoren zu finden?
Vielen Dank im Voraus für die Ideen!
Die gegebene Antwort von Dipen Shah konzentriert sich darauf, Transformationen auf das übergeordnete Element anzuwenden, und dies ist auch eine Option, aber mein Ziel ist es, das Element in die Zielgrenzen zu transformieren.
Antworten
Wie Sie festgestellt haben, ist dies ein heikles Problem. Es ist noch schwieriger als Sie denken (siehe später).
Sie haben Rechtecke in zwei verschiedenen entsprechenden Räumen. Einer von ihnen ist verwandelt. Sie versuchen also, ein transformiertes Rechteck einem anderen, möglicherweise transformierten Rechteck zuzuordnen. Da sie transformiert sind, ist eines oder beide dieser Rechtecke (wahrscheinlich) kein Rechteck mehr.
Da es Ihre Anforderung ist, die "Eingabe" in das "Ziel" umzuwandeln, besteht der Weg, um das Problem in den Griff zu bekommen, darin, Ihren Koordinatenraum auf den Standpunkt der "Eingabe" zu ändern. Wie sieht das "Ziel" aus Sicht der "Eingabe" aus? Um zu sehen, müssen wir "Ziel" mit der Umkehrung der Transformation transformieren, die "Eingabe" hat.
Wie das Ziel für die aussieht <rect id="input" transform=""/>
<svg
version="1.2"
viewBox="-50 -50 160 260"
height="500"
xmlns="http://www.w3.org/2000/svg"
>
<rect
id="input"
transform="translate(95,76.5) skewX(25) translate(50,50) scale(1.5) translate(-50,-50) translate(0,0) rotate(45)"
width="30"
height="30"
fill="red"
/>
<g transform="rotate(-10) translate(3,-4) skewX(-10)">
<g transform="rotate(-30) translate(3,-3) skewX(-30)">
<g transform="rotate(-30) translate(-95,-1) skewX(-10)">
<rect
id="destination"
x="20"
y="20"
width="100"
height="100"
fill="transparent"
stroke="blue"
/>
</g>
</g>
</g>
Wie das Ziel für die aussieht <rect id="input"/>
<svg
version="1.2"
viewBox="-80 -70 120 230"
height="500"
xmlns="http://www.w3.org/2000/svg"
>
<rect
id="input"
width="30"
height="30"
fill="red"
/>
<g transform="rotate(-45) translate(0,0) translate(50,50) scale(0.67) translate(-50,-50) skewX(-25) translate(-95,-76.5)">
<g transform="rotate(-10) translate(3,-4) skewX(-10)">
<g transform="rotate(-30) translate(3,-3) skewX(-30)">
<g transform="rotate(-30) translate(-95,-1) skewX(-10)">
<rect
id="destination"
x="20"
y="20"
width="100"
height="100"
fill="transparent"
stroke="blue"
/>
</g>
</g>
</g>
</g>
Sie können also sehen, warum es jetzt so schwierig ist. Wir müssen entweder die Transformation finden, die ein Parallelogramm einem anderen Parallelogramm oder ein Rechteck einem Parallelogramm zuordnet. Natürlich wollen wir Letzteres wählen. Sie würden erwarten, dass es die einfachere der beiden Optionen ist.
Uns wird auch geholfen, weil wir davon ausgehen können, dass die Transformationen affin sind . Gerade Linien bleiben gerade und parallele Linien bleiben parallel.
Unsere Aufgabe ist es also, unser Rechteck so zu vergrößern, dass es genau in unser Zielparallelogramm passt. Da das Parallelogramm eine 180 ° -Drehsymmetrie aufweist, wissen wir auch, dass die Mitte unseres angepassten Rechtecks mit der Mitte des Parallelogramms übereinstimmt.
Stellen wir uns also vor, das "Eingabe" -Rechteck befindet sich in der Mitte des "Ziel" -Parallelogramms und schießen dann imaginäre Strahlen aus dem Rechteck, bis sie die Seiten des Parallelogramms treffen. Welcher Strahl zuerst auf das Zielparallelogramm trifft, gibt uns die Skala, die wir auf das Rechteck anwenden sollten, damit es passt.
.ray {
stroke: lightgrey;
stroke-dasharray: 2 2;
}
<svg
version="1.2"
viewBox="0 0 120 230"
height="500"
xmlns="http://www.w3.org/2000/svg"
>
<g transform="translate(47.1,101.2)"><!-- positioning conveniently for our figure -->
<!-- scaling rays -->
<line class="ray" x1="-100" y1="0" x2="100" y2="0"/>
<line class="ray" x1="-100" y1="30" x2="100" y2="30"/>
<line class="ray" x1="0" y1="-100" x2="0" y2="100"/>
<line class="ray" x1="30" y1="-100" x2="30" y2="100"/>
<rect
id="input"
width="30"
height="30"
fill="red"
/>
</g>
<g transform="translate(80,70)"><!-- positioning conveniently for our figure -->
<g transform="rotate(-45) translate(0,0) translate(50,50) scale(0.67) translate(-50,-50) skewX(-25) translate(-95,-76.5)">
<g transform="rotate(-10) translate(3,-4) skewX(-10)">
<g transform="rotate(-30) translate(3,-3) skewX(-30)">
<g transform="rotate(-30) translate(-95,-1) skewX(-10)">
<rect
id="destination"
x="20"
y="20"
width="100"
height="100"
fill="transparent"
stroke="blue"
/>
</g>
</g>
</g>
</g>
</g>
var inputElement = document.getElementById("input");
var destinationElement = document.getElementById("destination");
var svg = inputElement.ownerSVGElement;
// Get the four corner points of rect "input"
var inX = inputElement.x.baseVal.value;
var inY = inputElement.y.baseVal.value;
var inW = inputElement.width.baseVal.value;
var inH = inputElement.height.baseVal.value;
// Get the four corner points of rect "destination"
var destX = destinationElement.x.baseVal.value;
var destY = destinationElement.y.baseVal.value;
var destW = destinationElement.width.baseVal.value;
var destH = destinationElement.height.baseVal.value;
var destPoints = [
createPoint(svg, destX, destY),
createPoint(svg, destX + destW, destY),
createPoint(svg, destX + destW, destY + destH),
createPoint(svg, destX, destY + destH)
];
// Get total transform applied to input rect
var el = inputElement;
var totalMatrix = el.transform.baseVal.consolidate().matrix;
// Step up ancestor tree till we get to the element before the root SVG element
while (el.parentElement.ownerSVGElement != null) {
el = el.parentElement;
if (el.transform) {
totalMatrix = el.transform.baseVal.consolidate().matrix.multiply( totalMatrix );
}
}
//console.log("totalMatrix = ",totalMatrix);
// Transform the four "destination" rect corner points by the inverse of the totalMatrix
// We will then have the corner points in the same coordinate space as the "input" rect
for (var i=0; i<4; i++) {
destPoints[i] = destPoints[i].matrixTransform(totalMatrix.inverse());
}
//console.log("transformed destPoints=",destPoints);
// Find the equation for the rays that start at the centre of the "input" rect & "destination" parallelogram
// and pass through the corner points of the "input" rect.
var destMinX = Math.min(destPoints[0].x, destPoints[1].x, destPoints[2].x, destPoints[3].x);
var destMaxX = Math.max(destPoints[0].x, destPoints[1].x, destPoints[2].x, destPoints[3].x);
var destMinY = Math.min(destPoints[0].y, destPoints[1].y, destPoints[2].y, destPoints[3].y);
var destMaxY = Math.max(destPoints[0].y, destPoints[1].y, destPoints[2].y, destPoints[3].y);
var destCentreX = (destMinX + destMaxX) / 2;
var destCentreY = (destMinY + destMaxY) / 2;
// Find the scale in the X direction by shooting rays horizontally from the top and bottom of the "input" rect
var scale1 = findDistanceToDestination(destCentreX, destCentreY - inH/2, inW/2, 0, // line equation of ray line 1
destPoints);
var scale2 = findDistanceToDestination(destCentreX, destCentreY + inH/2, inW/2, 0, // line equation of ray line 2
destPoints);
var scaleX = Math.min(scale1, scale2);
// Find the scale in the Y direction by shooting rays vertically from the left and right of the "input" rect
scale1 = findDistanceToDestination(destCentreX - inW/2, destCentreY, 0, inH/2, // line equation of ray line 1
destPoints);
scale2 = findDistanceToDestination(destCentreX + inW/2, destCentreY, 0, inH/2, // line equation of ray line 2
destPoints);
var scaleY = Math.min(scale1, scale2);
// Now we can position and scale the "input" element to fit the "destination" rect
inputElement.transform.baseVal.appendItem( makeTranslate(svg, destCentreX, destCentreY));
inputElement.transform.baseVal.appendItem( makeScale(svg, scaleX, scaleY));
inputElement.transform.baseVal.appendItem( makeTranslate(svg, -(inX + inW)/2, -(inY + inH)/2));
function createPoint(svg, x, y)
{
var pt = svg.createSVGPoint();
pt.x = x;
pt.y = y;
return pt;
}
function makeTranslate(svg, x, y)
{
var t = svg.createSVGTransform();
t.setTranslate(x, y);
return t;
}
function makeScale(svg, sx, sy)
{
var t = svg.createSVGTransform();
t.setScale(sx, sy);
return t;
}
function findDistanceToDestination(centreX, centreY, rayX, rayY, // line equation of ray
destPoints) // parallelogram points
{
// Test ray against each side of the dest parallelogram
for (var i=0; i<4; i++) {
var from = destPoints[i];
var to = destPoints[(i + 1) % 4];
var dx = to.x - from.x;
var dy = to.y - from.y;
var k = intersection(centreX, centreY, rayX, rayY, // line equation of ray
from.x, from.y, dx, dy); // line equation of parallogram side
if (k >= 0 && k <= 1) {
// Ray intersected with this side
var interceptX = from.x + k * dx;
var interceptY = from.y + k * dy;
var distanceX = interceptX - centreX;
var distanceY = interceptY - centreY;
if (rayX != 0)
return Math.abs(distanceX / rayX);
else if (rayY != 0)
return Math.abs(distanceY / rayY);
else
return 0; // How to handle case where "input" rect has zero width or height?
}
}
throw 'Should have intersected one of the sides!'; // Shouldn't happen
}
// Returns the position along the 'side' line, that the ray hits.
// If it intersects the line, thre return value will be between 0 and 1.
function intersection(rayX, rayY, rayDX, rayDY,
sideX, sideY, sideDX, sideDY)
{
// We want to find where:
// rayXY + t * rayDXDY = sideXY + k * sideDXDY
// Returning k.
// See: https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
var den = -rayDX * -sideDY - -rayDY * -sideDX;
return (den != 0) ? - (-rayDX * (rayY-sideY) - -rayDY * (rayX-sideX)) / den
: -9999; // Lines don't intersect. Return a value outside range 0..1.
}
<svg
version="1.2"
viewBox="0 0 480 150"
width="480"
height="150"
xmlns="http://www.w3.org/2000/svg"
>
<g transform="skewX(10) translate(95,1) rotate(30)">
<g transform="skewX(30) translate(-3,3) rotate(30)">
<g transform="skewX(10) translate(-3,4) rotate(10)">
<rect
id="input"
transform="translate(95,76.5) skewX(25) translate(50,50) scale(1.5) translate(-50,-50) translate(0,0) rotate(45)"
width="30"
height="30"
fill="red"
/>
</g>
</g>
</g>
<rect
id="destination"
x="20"
y="20"
width="100"
height="100"
fill="transparent"
stroke="blue"
/>
</svg>
<div id="test2"></div>
Wir sind nah dran, aber wir sind etwas übergroß. Was ist passiert?
Wenn wir es wie zuvor im "Eingangs" -Rektraum betrachten, können wir das Problem besser erkennen.
<svg width="500" height="500" viewBox="-40 -40 50 180">
<polygon points="-38.5008, 79.5321,
-32.7704, -35.2044,
3.5896, 12.3685,
-2.1406, 127.1050"
fill="none"
stroke="blue"
stroke-width="0.5"/>
<!-- input -->
<rect x="-32.4555" y="30.9503" width="30" height="30"
fill="red"/>
<!-- centre of dest -->
<circle cx="-17.4555" cy="45.9503" r="1"/>
<!-- intercepts X -->
<circle cx="-36.0744" cy="30.9503" r="1" fill="green"/>
<circle cx="-37.5727" cy="60.9503" r="1" fill="green"/>
<!-- intercepts Y -->
<circle cx="-32.4555" cy="-34.7923" r="1" fill="green"/>
<circle cx="-2.4555" cy="4.4590" r="1" fill="green"/>
<!-- scaled input -->
<rect x="-32.4555" y="30.9503" width="30" height="30"
fill="red" fill-opacity="0.2"
transform="translate(-17.4556 45.9503) scale(1.24126 2.76608) translate(17.4556 -45.9503)"/>
</svg>
Die grünen Punkte stellen die Schnittpunkte dar, die wir durch horizontales und vertikales Aufnehmen der Strahlen von unserem "Eingabe" -Rechteck erhalten haben. Das verblasste rote Rechteck stellt das "Eingabe" -Rechteck dar, das vergrößert wurde, um unsere Schnittpunkte zu berühren. Es überläuft unsere "Ziel" -Form. Deshalb läuft auch unsere Form aus dem vorherigen Snippet über.
Das habe ich ganz oben gemeint, als ich sagte, es sei schwieriger als Sie denken. Damit die "Eingabe" mit dem "Ziel" übereinstimmt, müssen Sie zwei voneinander abhängige X- und Y-Skalen anpassen. Wenn Sie die X-Skala anpassen, passt sie nicht mehr in die Y-Richtung. Und umgekehrt.
Das ist so weit ich will. Ich habe bereits ein paar Stunden mit dieser Antwort verbracht. Vielleicht ist dies eine mathematische Lösung, um ein Rechteck zu finden, das in ein Parallelogramm passt und alle vier Seiten berührt. Aber ich möchte nicht wirklich die Zeit verbringen, um es herauszufinden. Es tut uns leid. :) :)
Vielleicht können Sie oder jemand anderes dies weiterführen. Sie können auch eine iterative Lösung ausprobieren, bei der die X- und Y-Skalen iterativ verschoben werden, bis sie nahe genug kommen.
Schließlich, wenn Sie bereit sind , um den Zustand zu akzeptieren , dass Sie nicht tun die Eingangs strecken sowohl horizontal als auch vertikal, und wenn Sie in Ordnung sind mit nur Skalierung oben (oder unten) , um den Eingang zu passen (dh das Seitenverhältnis gleich bleibt), dann ist das einfacher zu lösen.
var inputElement = document.getElementById("input");
var destinationElement = document.getElementById("destination");
var svg = inputElement.ownerSVGElement;
// Get the four corner points of rect "input"
var inX = inputElement.x.baseVal.value;
var inY = inputElement.y.baseVal.value;
var inW = inputElement.width.baseVal.value;
var inH = inputElement.height.baseVal.value;
// Get the four corner points of rect "destination"
var destX = destinationElement.x.baseVal.value;
var destY = destinationElement.y.baseVal.value;
var destW = destinationElement.width.baseVal.value;
var destH = destinationElement.height.baseVal.value;
var destPoints = [
createPoint(svg, destX, destY),
createPoint(svg, destX + destW, destY),
createPoint(svg, destX + destW, destY + destH),
createPoint(svg, destX, destY + destH)
];
// Get total transform applied to input rect
var el = inputElement;
var totalMatrix = el.transform.baseVal.consolidate().matrix;
// Step up ancestor tree till we get to the element before the root SVG element
while (el.parentElement.ownerSVGElement != null) {
el = el.parentElement;
if (el.transform) {
totalMatrix = el.transform.baseVal.consolidate().matrix.multiply( totalMatrix );
}
}
//console.log("totalMatrix = ",totalMatrix);
// Transform the four "destination" rect corner points by the inverse of the totalMatrix
// We will then have the corner points in the same coordinate space as the "input" rect
for (var i=0; i<4; i++) {
destPoints[i] = destPoints[i].matrixTransform(totalMatrix.inverse());
}
//console.log("transformed destPoints=",destPoints);
// Find the equation for the rays that start at the centre of the "input" rect & "destination" parallelogram
// and pass through the corner points of the "input" rect.
var destMinX = Math.min(destPoints[0].x, destPoints[1].x, destPoints[2].x, destPoints[3].x);
var destMaxX = Math.max(destPoints[0].x, destPoints[1].x, destPoints[2].x, destPoints[3].x);
var destMinY = Math.min(destPoints[0].y, destPoints[1].y, destPoints[2].y, destPoints[3].y);
var destMaxY = Math.max(destPoints[0].y, destPoints[1].y, destPoints[2].y, destPoints[3].y);
var destCentreX = (destMinX + destMaxX) / 2;
var destCentreY = (destMinY + destMaxY) / 2;
// Shoot diagonal rays from the centre through two adjacent corners of the "input" rect.
// Whichever one hits the destination shape first, provides the scaling factor we need.
var scale1 = findDistanceToDestination(destCentreX, destCentreY, inW/2, inH/2, // line equation of ray line 1
destPoints);
var scale2 = findDistanceToDestination(destCentreX, destCentreY, -inW/2, inW/2, // line equation of ray line 2
destPoints);
var scale = Math.min(scale1, scale2);
// Now we can position and scale the "input" element to fit the "destination" rect
inputElement.transform.baseVal.appendItem( makeTranslate(svg, destCentreX, destCentreY));
inputElement.transform.baseVal.appendItem( makeScale(svg, scale, scale));
inputElement.transform.baseVal.appendItem( makeTranslate(svg, -(inX + inW)/2, -(inY + inH)/2));
function createPoint(svg, x, y)
{
var pt = svg.createSVGPoint();
pt.x = x;
pt.y = y;
return pt;
}
function makeTranslate(svg, x, y)
{
var t = svg.createSVGTransform();
t.setTranslate(x, y);
return t;
}
function makeScale(svg, sx, sy)
{
var t = svg.createSVGTransform();
t.setScale(sx, sy);
return t;
}
function findDistanceToDestination(centreX, centreY, rayX, rayY, // line equation of ray
destPoints) // parallelogram points
{
// Test ray against each side of the dest parallelogram
for (var i=0; i<4; i++) {
var from = destPoints[i];
var to = destPoints[(i + 1) % 4];
var dx = to.x - from.x;
var dy = to.y - from.y;
var k = intersection(centreX, centreY, rayX, rayY, // line equation of ray
from.x, from.y, dx, dy); // line equation of parallogram side
if (k >= 0 && k <= 1) {
// Ray intersected with this side
var interceptX = from.x + k * dx;
var interceptY = from.y + k * dy;
var distanceX = interceptX - centreX;
var distanceY = interceptY - centreY;
if (rayX != 0)
return Math.abs(distanceX / rayX);
else if (rayY != 0)
return Math.abs(distanceY / rayY);
else
return 0; // How to handle case where "input" rect has zero width or height?
}
}
throw 'Should have intersected one of the sides!'; // Shouldn't happen
}
// Returns the position along the 'side' line, that the ray hits.
// If it intersects the line, thre return value will be between 0 and 1.
function intersection(rayX, rayY, rayDX, rayDY,
sideX, sideY, sideDX, sideDY)
{
// We want to find where:
// rayXY + t * rayDXDY = sideXY + k * sideDXDY
// Returning k.
// See: https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
var den = -rayDX * -sideDY - -rayDY * -sideDX;
return (den != 0) ? - (-rayDX * (rayY-sideY) - -rayDY * (rayX-sideX)) / den
: -9999; // Lines don't intersect. Return a value outside range 0..1.
}
<svg
version="1.2"
viewBox="0 0 480 150"
width="480"
height="150"
xmlns="http://www.w3.org/2000/svg"
>
<g transform="skewX(10) translate(95,1) rotate(30)">
<g transform="skewX(30) translate(-3,3) rotate(30)">
<g transform="skewX(10) translate(-3,4) rotate(10)">
<rect
id="input"
transform="translate(95,76.5) skewX(25) translate(50,50) scale(1.5) translate(-50,-50) translate(0,0) rotate(45)"
width="30"
height="30"
fill="red"
/>
</g>
</g>
</g>
<rect
id="destination"
x="20"
y="20"
width="100"
height="100"
fill="transparent"
stroke="blue"
/>
</svg>
<div id="test2"></div>
Aktualisieren:
Ich konnte das Quellelement an das Zielelement anpassen. Die Art und Weise, wie ich dies erreichen konnte, bestand darin, den obersten Container des Quellelements relativ zum Zielelement zu übersetzen und den Container basierend auf dem Größenverhältnis zwischen Quell- und Zielelementen zu skalieren .
function applyTransformations(source, sourceContainer, target, includeMagicScaleMargin) {
var sourceBB = source.getBoundingClientRect();
var inputBB = sourceContainer.getBoundingClientRect();
var outputBB = target.getBoundingClientRect();
var scaleX = (outputBB.width - (includeMagicScaleMargin ? 10 : 0)) / sourceBB.width;
var scaleY = (outputBB.height - (includeMagicScaleMargin ? 10 : 0)) / sourceBB.height;
// get offsets between figure center and destination rect center:
var offsetX = outputBB.x + outputBB.width / 2 - (inputBB.x + inputBB.width / 2);
var offsetY =
outputBB.y + outputBB.height / 2 - (inputBB.y + inputBB.height / 2);
// get current figure transformation
let currentMatrix = (
sourceContainer.transform.baseVal.consolidate() ||
sourceContainer.ownerSVGElement.createSVGTransform()
).matrix;
// Get center of figure in element coordinates:
const inputBBox = sourceContainer.getBBox();
const centerTransform = sourceContainer.ownerSVGElement.createSVGPoint();
centerTransform.x = inputBBox.x + inputBBox.width / 2;
centerTransform.y = inputBBox.y + inputBBox.height / 2;
// create scale matrix:
const svgTransform = sourceContainer.ownerSVGElement.createSVGTransform();
svgTransform.setScale(scaleX, scaleY);
let scalingMatrix = sourceContainer.ownerSVGElement
.createSVGMatrix()
// move the figure to the center of the destination rect.
.translate(offsetX, offsetY)
// Apply current matrix, so old transformations are not lost
.multiply(currentMatrix)
.translate(centerTransform.x, centerTransform.y)
// multiply is used instead of the scale method while for some reasons matrix scale is giving proportional scaling...
// From a transforms proper matrix is generated.
.multiply(svgTransform.matrix)
.translate(-centerTransform.x, -centerTransform.y);
// Apply new created matrix to element back:
const newTransform = sourceContainer.ownerSVGElement.createSVGTransform();
newTransform.setMatrix(scalingMatrix);
sourceContainer.transform.baseVal.initialize(newTransform);
}
function isDescendant(parent, child) {
var node = child.parentNode;
while (node != null) {
if (node == parent) {
return true;
}
node = node.parentNode;
}
return false;
}
function transformSVG1() {
var destinationElem = document.getElementById("destination");
var inputElem = document.getElementById("input");
var inputContainerElem = inputElem;
while (inputContainerElem.parentNode != null) {
let candidateParent = inputContainerElem.parentNode;
if (isDescendant(candidateParent, destinationElem)) {
break;
}
inputContainerElem = candidateParent;
}
applyTransformations(inputElem, inputContainerElem, destinationElem);
}
function transformSVG2() {
var destinationElem = document.getElementById("destination2");
var inputElem = document.getElementById("input2");
var inputContainerElem = inputElem;
while (inputContainerElem.parentNode != null) {
let candidateParent = inputContainerElem.parentNode;
if (isDescendant(candidateParent, destinationElem)) {
break;
}
inputContainerElem = candidateParent;
}
applyTransformations(inputElem, inputContainerElem, destinationElem, true);
}
transformSVG1();
transformSVG2();
<svg version="1.2" viewBox="0 0 480 200" width="480" height="200" xmlns="http://www.w3.org/2000/svg">
<g>
<text x="0" y="20" font-size="20">No magic margins</text>
<g transform="skewX(10) translate(95,1) rotate(30)">
<g transform="skewX(30) translate(-3,3) rotate(30)">
<g transform="skewX(10) translate(-3,4) rotate(10)">
<rect id="input" transform="translate(95,76.5) skewX(25) translate(50,50) scale(1.5) translate(-50,-50) translate(0,0) rotate(45)" width="30" height="30" fill="red" />
</g>
</g>
</g>
<rect id="destination" x="40" y="40" width="100" height="100" fill="transparent" stroke="blue" />
</g>
</svg>
<svg version="1.2" viewBox="0 0 480 200" width="480" height="200" xmlns="http://www.w3.org/2000/svg">
<g>
<text x="0" y="20" font-size="20">Magic margins!</text>
<g transform="skewX(10) translate(95,1) rotate(30)">
<g transform="skewX(30) translate(-3,3) rotate(30)">
<g transform="skewX(10) translate(-3,4) rotate(10)">
<rect id="input2" transform="translate(95,76.5) skewX(25) translate(50,50) scale(1.5) translate(-50,-50) translate(0,0) rotate(45)" width="30" height="30" fill="red" />
</g>
</g>
</g>
<rect id="destination2" x="40" y="40" width="100" height="100" fill="transparent" stroke="blue" />
</g>
</svg>
Ursprüngliche Antwort: Ich denke nicht, dass dies eine genaue Antwort auf das ist, wonach Sie suchen, aber einfacher wäre es, entweder:
Ansatz 1: Wenden Sie weiterhin dieselbe Transformation wie das Eingabeelement und sein übergeordnetes Element an, bis ein gemeinsamer übergeordneter Knoten gefunden wird.
function applyTransformations(source, target) {
var inputBB = source.getBoundingClientRect();
var outputBB = target.getBoundingClientRect();
var scaleX = outputBB.width / inputBB.width;
var scaleY = outputBB.height / inputBB.height;
// get offsets between figure center and destination rect center:
var offsetX = outputBB.x + outputBB.width / 2 - (inputBB.x + inputBB.width / 2);
var offsetY =
outputBB.y + outputBB.height / 2 - (inputBB.y + inputBB.height / 2);
// get current figure transformation
let currentMatrix = (
source.transform.baseVal.consolidate() ||
source.ownerSVGElement.createSVGTransform()
).matrix;
// Get center of figure in element coordinates:
const inputBBox = source.getBBox();
const centerTransform = source.ownerSVGElement.createSVGPoint();
centerTransform.x = inputBBox.x + inputBBox.width / 2;
centerTransform.y = inputBBox.y + inputBBox.height / 2;
// create scale matrix:
const svgTransform = source.ownerSVGElement.createSVGTransform();
svgTransform.setScale(scaleX, scaleY);
let scalingMatrix = source.ownerSVGElement
.createSVGMatrix()
// move the figure to the center of the destination rect.
.translate(offsetX, offsetY)
// Apply current matrix, so old transformations are not lost
.multiply(currentMatrix)
.translate(centerTransform.x, centerTransform.y)
// multiply is used instead of the scale method while for some reasons matrix scale is giving proportional scaling...
// From a transforms proper matrix is generated.
.multiply(svgTransform.matrix)
.translate(-centerTransform.x, -centerTransform.y);
// Apply new created matrix to element back:
const newTransform = source.ownerSVGElement.createSVGTransform();
newTransform.setMatrix(scalingMatrix);
source.transform.baseVal.initialize(newTransform);
}
function isDescendant(parent, child) {
var node = child.parentNode;
while (node != null) {
if (node == parent) {
return true;
}
node = node.parentNode;
}
return false;
}
var destinationElement = document.getElementById("destination");
var inputElement = document.getElementById("input");
while (inputElement.parentNode != null) {
applyTransformations(inputElement, destinationElement);
let candidateParent = inputElement.parentNode;
if (isDescendant(candidateParent, destinationElement)) {
break;
}
inputElement = candidateParent;
}
// Test:
var bboundsTest= document.getElementById("bboundsTest");
const resultBBounds = document.getElementById("input").getBoundingClientRect();
bboundsTest.setAttribute('x', resultBBounds.x);
bboundsTest.setAttribute('y', resultBBounds.y);
bboundsTest.setAttribute('width', resultBBounds.width);
bboundsTest.setAttribute('height', resultBBounds.height);
<svg version="1.2" viewBox="0 0 480 240" width="480" height="240" xmlns="http://www.w3.org/2000/svg">
<g>
<g transform="skewX(10) translate(95,1) rotate(30)">
<g transform="skewX(30) translate(-3,3) rotate(30)">
<g transform="skewX(10) translate(-3,4) rotate(10)">
<rect
id="input"
transform="translate(95,76.5) skewX(25) translate(50,50) scale(1.5) translate(-50,-50) translate(0,0) rotate(45)"
width="30"
height="30"
fill="red"
/>
</g>
</g>
</g>
<rect
id="destination"
x="20"
y="20"
width="100"
height="100"
fill="transparent"
stroke="blue"
/>
<rect
id="bboundsTest"
fill="transparent"
stroke="black"
/>
</g>
</svg>
Ansatz 2: Oder suchen Sie zuerst das übergeordnete Element der Eingabe, das nicht das übergeordnete Element des Ziels ist, und wenden Sie dann dieselben Transformationen wie der übergeordnete Knoten an.
function applyTransformations(source, target) {
var inputBB = source.getBoundingClientRect();
var outputBB = target.getBoundingClientRect();
var scaleX = outputBB.width / inputBB.width;
var scaleY = outputBB.height / inputBB.height;
// get offsets between figure center and destination rect center:
var offsetX = outputBB.x + outputBB.width / 2 - (inputBB.x + inputBB.width / 2);
var offsetY =
outputBB.y + outputBB.height / 2 - (inputBB.y + inputBB.height / 2);
// get current figure transformation
let currentMatrix = (
source.transform.baseVal.consolidate() ||
source.ownerSVGElement.createSVGTransform()
).matrix;
// Get center of figure in element coordinates:
const inputBBox = source.getBBox();
const centerTransform = source.ownerSVGElement.createSVGPoint();
centerTransform.x = inputBBox.x + inputBBox.width / 2;
centerTransform.y = inputBBox.y + inputBBox.height / 2;
// create scale matrix:
const svgTransform = source.ownerSVGElement.createSVGTransform();
svgTransform.setScale(scaleX, scaleY);
let scalingMatrix = source.ownerSVGElement
.createSVGMatrix()
// move the figure to the center of the destination rect.
.translate(offsetX, offsetY)
// Apply current matrix, so old transformations are not lost
.multiply(currentMatrix)
.translate(centerTransform.x, centerTransform.y)
// multiply is used instead of the scale method while for some reasons matrix scale is giving proportional scaling...
// From a transforms proper matrix is generated.
.multiply(svgTransform.matrix)
.translate(-centerTransform.x, -centerTransform.y);
// Apply new created matrix to element back:
const newTransform = source.ownerSVGElement.createSVGTransform();
newTransform.setMatrix(scalingMatrix);
source.transform.baseVal.initialize(newTransform);
}
function isDescendant(parent, child) {
var node = child.parentNode;
while (node != null) {
if (node == parent) {
return true;
}
node = node.parentNode;
}
return false;
}
var destinationElement = document.getElementById("destination");
var inputElement = document.getElementById("input");
while (inputElement.parentNode != null) {
let candidateParent = inputElement.parentNode;
if (isDescendant(candidateParent, destinationElement)) {
break;
}
inputElement = candidateParent;
}
applyTransformations(inputElement, destinationElement);
// Test:
var bboundsTest= document.getElementById("bboundsTest");
const resultBBounds = document.getElementById("input").getBoundingClientRect();
bboundsTest.setAttribute('x', resultBBounds.x);
bboundsTest.setAttribute('y', resultBBounds.y);
bboundsTest.setAttribute('width', resultBBounds.width);
bboundsTest.setAttribute('height', resultBBounds.height);
<svg version="1.2" viewBox="0 0 480 240" width="480" height="240" xmlns="http://www.w3.org/2000/svg">
<g>
<g transform="skewX(10) translate(95,1) rotate(30)">
<g transform="skewX(30) translate(-3,3) rotate(30)">
<g transform="skewX(10) translate(-3,4) rotate(10)">
<rect
id="input"
transform="translate(95,76.5) skewX(25) translate(50,50) scale(1.5) translate(-50,-50) translate(0,0) rotate(45)"
width="30"
height="30"
fill="red"
/>
</g>
</g>
</g>
<rect
id="destination"
x="20"
y="20"
width="100"
height="100"
fill="transparent"
stroke="blue"
/>
<rect
id="bboundsTest"
fill="transparent"
stroke="black"
/>
</g>
</svg>
Hinweis: Beide Ansätze liefern unterschiedliche Ergebnisse basierend auf Transformationen, die an übergeordneten Elementen beteiligt sind, da der zweite Ansatz nicht alle Transformationen auf das Ziel anwendet, sondern dieselben Transformationen wie der übergeordnete Knoten der Eingabe, der nicht auch übergeordnet für das Ziel ist.
Ich habe einige Zeit gebraucht, um eine Antwort zu finden, aber schließlich habe ich sie bekommen und es ist ganz einfach!
- Holen Sie sich die Begrenzungsrahmen beider Rechtecke in die 'Bildschirm'-Koordinaten. Zum Beispiel: getBoundingClientRect.
- Durch Vergleichen der Rechteckfelder erhalten Sie die gewünschten Skalierungsfaktoren.
- Während die Skalierung in Bildschirmkoordinaten erfolgen sollte, sollten wir die aktuelle Elementtransformation einschließlich aller übergeordneten Transformationen in die Bildschirmkoordinaten konvertieren, alle mit der angegebenen Skalierung transformieren und zurück in die Elementkoordinaten konvertieren.
Die genaue Zeile lautet:
var toScreenMatrix = inputElement.getScreenCTM();
// Scale element by a matrix in screen coordinates and convert it back to the element coordinates:
currentMatrix = currentMatrix.multiply(toScreenMatrix.inverse().multiply(scaleAndTransform).multiply(toScreenMatrix));
Dieser Code ist generisch für alle SVG-Elemente, sodass jede Form in das angegebene Rechteck eingepasst werden kann:
function fitElement(from, to, changePosition) {
var inputElement = document.getElementById(from);
var destinationElement = document.getElementById(to);
// Get center of figure in element coordinates:
var inputScreenBBox = inputElement.getBoundingClientRect();
var destinationScreenBBox = destinationElement.getBoundingClientRect();
var scaleX = destinationScreenBBox.width / inputScreenBBox.width;
var scaleY = destinationScreenBBox.height / inputScreenBBox.height;
var inputCenter = getCenter(inputScreenBBox);
var offsetX = 0;
var offsetY = 0;
if (changePosition) {
var destCenter = getCenter(destinationScreenBBox);
offsetX = destCenter.x - inputCenter.x;
offsetY = destCenter.y - inputCenter.y;
}
// create scale matrix:
var scaleMatrix = getScaleMatrix(scaleX, scaleY, inputElement);
// get element self transformation matrix:
var currentMatrix = getElementMatrix(inputElement);
scaleAndTransform = inputElement.ownerSVGElement.createSVGMatrix()
.translate(offsetX, offsetY)
// Scale in screen coordinates around the element center:
.translate(inputCenter.x, inputCenter.y)
.multiply(scaleMatrix)
.translate(-inputCenter.x, -inputCenter.y)
var toScreenMatrix = inputElement.getScreenCTM();
// Scale element by a matrix in screen coordinates and convert it back to the element coordinates:
currentMatrix = currentMatrix.multiply(toScreenMatrix.inverse().multiply(scaleAndTransform).multiply(toScreenMatrix));
// Apply new created transform back to the element:
var newTransform = inputElement.ownerSVGElement.createSVGTransform();
newTransform.setMatrix(currentMatrix);
inputElement.transform.baseVal.initialize(newTransform);
}
function getElementMatrix(element) {
// Get consolidated element matrix:
var currentMatrix =
(element.transform.baseVal.consolidate() ||
element.ownerSVGElement.createSVGTransform()).matrix;
return currentMatrix;
}
function getScaleMatrix(scaleX, scaleY, el) {
// Return DOM matrix
var svgTransform = el.ownerSVGElement.createSVGTransform();
// Transform type is used because of the bug in chrome applying scale to the DOM matrix:
svgTransform.setScale(scaleX, scaleY);
var scaleMatrix = svgTransform.matrix;
return scaleMatrix
}
function getCenter(rect) {
return new DOMPoint((rect.x + rect.width / 2), (rect.y + rect.height / 2));
}
fitElement('source', 'destination', true);
<svg width="1380" height="1340" xmlns="http://www.w3.org/2000/svg">
<g transform="skewX(10) translate(-3,4) rotate(30)">
<g transform="skewX(30) translate(-3,4) rotate(30)">
<g transform="skewX(10) translate(-3,4) rotate(10)">
<g transform="translate(350,30) skewX(10) rotate(30)">
<rect id="source" transform="scale(2) rotate(30) skewX(10)" x="20" y="50" width="30" height="30"
fill="red" />
</g>
</g>
</g>
</g>
<rect id="destination" x="30" y="30" width="120" height="100" fill="transparent" stroke="blue" />
</svg>
GitHub-Link