如何将 svg 图形组缩放到所需的大小(不是按因子)?

发布于 2024-11-28 05:48:16 字数 232 浏览 1 评论 0原文

我有 svg 图形组(我的意思是路径、重新排列等)。我想将组缩放到一定大小,例如 70,70。我知道 svg 提供了比例变换,但是比例变换需要我不知道的比例因子,因为组可以有很多形状,并且确定整个组的边界大小并不容易。
我使用了 svg element 的 viewBox 参数和preserveAspectRatio 参数,但无法获得所需的结果(缩放组为 70,70)。
也许可以使用 maxtrix 变换来缩放组或存在其他方式?

I have svg graphics group (I mean paths, recatangles etc.). I want to scale group to certain size, for examle to 70,70. I know that svg provides scale transformation, but scale transformation needs scale factor that I don't know because group can have many shapes and it's not easy to determine bounding size of whole group.
I played with viewBox parameter of svg elemenent and preserveAspectRatio parameter but I could not get desired result(scale group to 70,70).
Maybe is it possible to scale group with maxtrix transform or exists other way?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

浮世清欢 2024-12-05 05:48:16

您可以使用 getBBox() 函数检索图形的当前大小,然后使用宽度和高度来计算应应用于图形的比例因子,以使其适合所需的大小。

在下面的示例中,g1 组中的图形被缩放以适合宽度和高度设置为 70 的矩形。

<?xml version="1.0" encoding="UTF-8" ?>
<svg version="1.2" viewBox="0 0 480 240" width="480" height="240" xmlns="http://www.w3.org/2000/svg" >
 <g id="g1">
    <rect x="20" y="20" width="100" height="100" fill="red" />
    <rect x="40" y="60" width="100" height="100" fill="blue" />
 </g>
 <script type="application/ecmascript"> 

    var width=70, height=70;
    var node = document.getElementById("g1");
    var bb = node.getBBox();
    var matrix = "matrix("+width / bb.width+", 0, 0, "+height / bb.height+", 0,0)";
    node.setAttribute("transform", matrix);

 </script>
</svg>

You can use the getBBox() function to retrieve the current size of the graphics and then use the width and height to calculate the scale factor that should be applied to the graphics to fit it into the desired size.

In the example below the graphics in the group g1 is scaled to fit in a rectange with a width and height set to 70.

<?xml version="1.0" encoding="UTF-8" ?>
<svg version="1.2" viewBox="0 0 480 240" width="480" height="240" xmlns="http://www.w3.org/2000/svg" >
 <g id="g1">
    <rect x="20" y="20" width="100" height="100" fill="red" />
    <rect x="40" y="60" width="100" height="100" fill="blue" />
 </g>
 <script type="application/ecmascript"> 

    var width=70, height=70;
    var node = document.getElementById("g1");
    var bb = node.getBBox();
    var matrix = "matrix("+width / bb.width+", 0, 0, "+height / bb.height+", 0,0)";
    node.setAttribute("transform", matrix);

 </script>
</svg>
掩于岁月 2024-12-05 05:48:16

如果已经应用了路径\矩形转换,您可能会遇到一些将 SVG 元素放入矩形边界的问题。

下面的代码演示了如何适应经过分组、倾斜、缩放和转换到边界的转换后的反应输入。您必须保留现有的转换并应用新的转换。

代码正在做接下来的事情:

  • 计算屏幕坐标中的比例和位置偏移(在屏幕坐标中,在这种情况下所有变换都被计算在内)
  • 创建一个新矩阵,首先应用变换偏移(移动到目标的中心)。
  • 应用新的缩放,并将元素的中心作为变换的原点。
  • 虽然缩放应该在屏幕坐标中完成,但我们应该将当前元素变换(包括所有父变换)转换为屏幕坐标,将所有具有给定比例的元素变换并转换回元素坐标。

确切的行是:

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

此代码对于所有 svg 元素都是通用的,因此任何形状都可以适合给定的矩形:

    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()
            // 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.
            .translate(offsetX, offsetY)
            // Scale in screen coordinates around screen 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:
        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要点链接

You might face some issues to fit the SVG element into a rectangle bound if path\rect transformations are already applied.

The code below is demonstrating how to fit transformed react input that grouped, skewed, scaled and transformed into the bounds. You have to preserve existing transformations and apply new transformations.

Code is doing next things:

  • Calculate scale and position offset in screen coordinated (in the screen coordinates while all transformations are counted in this case)
  • Create a new matrix, apply transformation offset first (move to the center of the destination).
  • Apply new scaling with the center of the element as the origin of the transform.
  • While scaling should be done in screen coordinates, we should convert the current element transformation including all the parent transformations to the screen coordinates, transform all those with a given scale and convert back to the element coordinates.

Exact line is:

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

This code is generic for all the svg elements, so any shape can be fit into the given rect:

    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()
            // 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.
            .translate(offsetX, offsetY)
            // Scale in screen coordinates around screen 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:
        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 gist link

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文