【问题标题】:How to determine size of Raphael object after scaling & rotating it?缩放和旋转后如何确定 Raphael 对象的大小?
【发布时间】:2012-10-14 08:25:44
【问题描述】:

如果我要创建一个初始大小为 [w1,h1] 的图像作为 Raphael.js 对象,然后使用 transform() 方法对其进行缩放,我将如何检索它的新尺寸 [w2,h2]?因为调用image.attr("width")返回的是w1,即初始宽度,而不是变换后的宽度。

我知道你可能会说我应该简单地将 w1 和 h1 与缩放因子相乘,但大多数情况下,还会应用旋转变换,这会使事情变得更麻烦。

简而言之,Raphael.js 中是否有一种方法可以检索对象的正确大小,而不考虑可能对其应用的任何转换?

【问题讨论】:

    标签: javascript svg raphael


    【解决方案1】:

    Matt Esch 的示例可能仅适用于一些(矩形)元素,但要获得所有可能元素的度量(以及在元素受父组的嵌套转换影响的情况下),我们必须使用其他方法。为了获得转换后的 SVG 元素的各种指标(宽度、高度、bbox 宽度、bbox 高度、角坐标、边长等),我创建了一个 get_metrics() 函数:

    完整功能示例:http://output.jsbin.com/zuvuborehe/

    下图显示了 get_metrics() 的一种可能用例:

    函数get_metrics() 使用纯javascript,没有库。但它可以与像 Raphaël 这样的库一起使用 OC。它基于原生element1.getTransformToElement(element2),它可以获得两个元素之间的关系矩阵,在这种情况下是转换后的元素(例如<path>)和SVG根元素(<svg>)。当然你也可以使用其他元素,比如图像、多边形、矩形等。顺便说一下,getTransformToElement 非常强大且用途广泛,例如可以使用它。以这种方式展平由任何路径命令组成的路径变换(如果它们首先使用 Raphaël 的 path2curve 转换为 Cubics,则甚至是弧线):http://jsbin.com/atidoh/9

    SVGElement.prototype.getTransformToElement = 
    SVGElement.prototype.getTransformToElement || function(elem)
    { 
      return elem.getScreenCTM().inverse().multiply(this.getScreenCTM()); 
    };
    
    function get_metrics(el) {
        function pointToLineDist(A, B, P) {
            var nL = Math.sqrt((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y));
            return Math.abs((P.x - A.x) * (B.y - A.y) - (P.y - A.y) * (B.x - A.x)) / nL;
        }
    
        function dist(point1, point2) {
            var xs = 0,
                ys = 0;
            xs = point2.x - point1.x;
            xs = xs * xs;
            ys = point2.y - point1.y;
            ys = ys * ys;
            return Math.sqrt(xs + ys);
        }
        var b = el.getBBox(),
            objDOM = el,
            svgDOM = objDOM.ownerSVGElement;
        // Get the local to global matrix
        var matrix = svgDOM.getTransformToElement(objDOM).inverse(),
            oldp = [[b.x, b.y], [b.x + b.width, b.y], [b.x + b.width, b.y + b.height], [b.x, b.y + b.height]],
            pt, newp = [],
            obj = {},
            i, pos = Number.POSITIVE_INFINITY,
            neg = Number.NEGATIVE_INFINITY,
            minX = pos,
            minY = pos,
            maxX = neg,
            maxY = neg;
    
        for (i = 0; i < 4; i++) {
            pt = svgDOM.createSVGPoint();
            pt.x = oldp[i][0];
            pt.y = oldp[i][1];
            newp[i] = pt.matrixTransform(matrix);
            if (newp[i].x < minX) minX = newp[i].x;
            if (newp[i].y < minY) minY = newp[i].y;
            if (newp[i].x > maxX) maxX = newp[i].x;
            if (newp[i].y > maxY) maxY = newp[i].y;
        }
        // The next refers to the transformed object itself, not bbox
        // newp[0] - newp[3] are the transformed object's corner
        // points in clockwise order starting from top left corner
        obj.newp = newp; // array of corner points
        obj.width = pointToLineDist(newp[1], newp[2], newp[0]) || 0;
        obj.height = pointToLineDist(newp[2], newp[3], newp[0]) || 0;
        obj.toplen = dist(newp[0], newp[1]);
        obj.rightlen = dist(newp[1], newp[2]);
        obj.bottomlen = dist(newp[2], newp[3]);
        obj.leftlen = dist(newp[3], newp[0]);
        // The next refers to the transformed object's bounding box
        obj.BBx = minX;
        obj.BBy = minY;
        obj.BBx2 = maxX;
        obj.BBy2 = maxY;
        obj.BBwidth = maxX - minX;
        obj.BBheight = maxY - minY;
        return obj;
    }
    

    【讨论】:

    • get_metrics 函数似乎是一个相当强大的工具,当我有复杂的 SVG 时,我一定会研究它。谢谢!
    • 不幸的是,由于删除了 getTransformToElement github.com/cpettitt/dagre-d3/issues/202,这在最新版本的 google chrome 中不再有效
    • 有一个 polyfill。也许就够了:SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(elem) { return elem.getScreenCTM().inverse().multiply(this.getScreenCTM()); };
    • 有史以来最被低估的答案。
    【解决方案2】:

    有一种方法可以检索带有和不带有变换的元素的边界框。

    经过改造:

    var bbox = image.getBBox(),
        width = bbox.width,
        height = bbox.height;
    

    没有转换:

    var bbox = image.getBBoxWOTransform(),
        width = bbox.width,
        height = bbox.height;
    

    您可以使用帮助方法(如果您小心的话)扩展 Raphael.el,如果这对您有帮助,这些方法可以直接提供宽度和高度。您可以只使用边界框方法并返回您感兴趣的部分,但为了更高效,我只使用元素上的矩阵属性和属性的位置/宽度/高度计算了请求的属性。

    (function (r) {
        function getX() {
            var posX = this.attr("x") || 0,
                posY = this.attr("y") || 0;
            return this.matrix.x(posX, posY);
        }
    
        function getY() {
            var posX = this.attr("x") || 0,
                posY = this.attr("y") || 0;
            return this.matrix.y(posX, posY);
        }
    
        function getWidth() {
            var posX = this.attr("x") || 0,
                posY = this.attr("y") || 0,
                maxX = posX + (this.attr("width") || 0),
                maxY = posY + (this.attr("height") || 0),
                m = this.matrix,
                x = [
                    m.x(posX, posY),
                    m.x(maxX, posY),
                    m.x(maxX, maxY),
                    m.x(posX, maxY)
                ];
    
            return Math.max.apply(Math, x) - Math.min.apply(Math, x);
        }
    
        function getHeight() {
            var posX = this.attr("x") || 0,
                posY = this.attr("y") || 0,
                maxX = posX + (this.attr("width") || 0),
                maxY = posY + (this.attr("height") || 0),
                m = this.matrix,
                y = [
                    m.y(posX, posY),
                    m.y(maxX, posY),
                    m.y(maxX, maxY),
                    m.y(posX, maxY)
                ];
    
            return Math.max.apply(Math, y) - Math.min.apply(Math, y);
        }
    
        r.getX = getX;
        r.getY = getY;
        r.getWidth = getWidth;
        r.getHeight = getHeight;
    }(Raphael.el))
    

    有用法:

    var x = image.getX();
    var y = image.getY();
    var width = image.getWidth();
    var height = image.getHeight();
    

    只需在包含 Raphael 后包含脚本即可。请注意,它仅适用于具有宽度、高度、x 和 y 属性的元素,适用于图像。 Raphael 实际上是根据路径数据计算边界框,对路径中的所有点进行变换,并在变换后得到最小/最大 x/y 值。

    【讨论】:

    • 我担心你会提到getBBox :( 换句话说,我担心没有直接的对象方法来检索宽度和高度,我不得不“手动”计算它们
    • 为什么会出现这个问题?你能解释一下你正在尝试做什么以及为什么计算的边界框是一个问题吗?当然没有实际包含值的值,它必须被计算。在原始 svg 中,您也只能将边界框作为对象。无论哪种方式,您都无法避免调用一个函数,并且编写一个帮助程序以将宽度和高度作为数字提供给您是微不足道的。
    • 在旋转和缩放后计算图像的大小不是问题,而是令人讨厌。我已经根据 bbox 宽度+高度和旋转角度(一些难看的表达式)计算了图像的宽度和高度值,但想知道是否有办法直接获得这些结果。
    • 没有直接的方法(你没有在你的问题中要求这个),但你总是可以向 Raphael.el 添加一个扩展,以节省你对函数的内联调用。矩阵对象image.matrix可以为你做变换
    • @AndreiOniga 从我的示例中获取灵感,为您省去用丑陋的表达式计算边界框的麻烦 :)
    猜你喜欢
    • 1970-01-01
    • 2019-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多