SVG 获取文本元素宽度

125

我正在编写一些用于SVG文件的ECMAScript/JavaScript代码,并需要获取text元素的widthheight,以便调整其周围的矩形大小。在HTML中,我可以使用元素上的offsetWidthoffsetHeight属性,但似乎这些属性不可用。

这是我需要处理的片段。每当更改文本时,我需要更改矩形的宽度,但我不知道如何获取text元素的实际width(以像素为单位)。

<rect x="100" y="100" width="100" height="100" />
<text>Some Text</text>

有什么想法吗?

7个回答

183
var bbox = textElement.getBBox();
var width = bbox.width;
var height = bbox.height;
然后相应地设置矩形的属性。 链接:getBBox()在SVG v1.1标准中。

4
谢谢。我还发现了另一个函数,可能有助于调整宽度。textElement.getComputedTextLength()。今晚我会尝试两种方法,看哪种对我更有效。 - Stephen Sorensen
5
上周我在使用这些方法;对于我尝试过的内容,getComputedTextLength() 方法返回的结果与 getBBox().width 相同,因此我选择了包围盒,因为我需要宽度和高度。我不确定是否存在任何情况下文本长度会与宽度不同:我认为该方法或许是用于帮助文本布局,如将文本拆分成多行,而包围盒则是通用方法,可用于所有(?)元素。 - NickFitz
2
问题是,如果文本沿着路径走,则宽度不是文本的实际宽度(例如,如果文本沿着圆形路径走,则bbox是包围圆形的那个,获取其宽度并不是文本在绕圆之前的宽度)。另一件事是,bbox.width大了2倍,因此如果元素检查器显示宽度为200,则bbox.width为400。我不确定为什么。 - trusktr
1
getBBox() 不被 IE11 支持。(是的,仍在检查中。) - j4v1
5
除非文本已经添加到DOM中,否则这些方法似乎都不起作用。 - a2f0

17
document.getElementById('yourTextId').getComputedTextLength();

在我这里起作用了


1
你的解决方案返回文本元素的实际长度,这是正确的答案。有些答案使用getBBox(),如果文本没有旋转可能是正确的。 - Netsi1964

8

谢谢你提供的链接!!我不得不自己经历所有的情况,但这是一个非常好的总结...我的问题是在Firefox上使用getBBox()在Tspan元素上...它不能更具体和恼人了...再次感谢! - Andres Elizondo

2
试试这样的兼容性方案:
function svgElemWidth(elem) {
    var methods = [ // name of function and how to process its result
        { fn: 'getBBox', w: function(x) { return x.width; }, },
        { fn: 'getBoundingClientRect', w: function(x) { return x.width; }, },
        { fn: 'getComputedTextLength', w: function(x) { return x; }, }, // text elements only
    ];
    var widths = [];
    var width, i, method;
    for (i = 0; i < methods.length; i++) {
        method = methods[i];
        if (typeof elem[method.fn] === 'function') {
            width = method.w(elem[method.fn]());
            if (width !== 0) {
                widths.push(width);
            }
        }
    }
    var result;
    if (widths.length) {
        result = 0;
        for (i = 0; i < widths.length; i++) {
            result += widths[i];
        }
        result /= widths.length;
    }
    return result;
}

这将返回三种方法中任何有效结果的平均值。您可以改进它以排除异常值,或者如果元素是文本元素,则更偏爱getComputedTextLength

警告:正如注释所说,getBoundingClientRect很棘手。要么从方法中删除它,要么仅在getBoundingClientRect将返回良好结果的元素上使用它,因此没有旋转并且可能没有缩放(?)


1
getBoundingClientRect 在潜在情况下使用与其他两个不同的坐标系。 - Robert Longson

2

不确定为什么,但以上方法都对我不起作用。我尝试使用画布方法,但必须应用各种比例因素才能取得一些成功。即使加了比例因素,Safari、Chrome和Firefox之间的结果仍然不一致。

因此,我尝试了以下方法:

            var div = document.createElement('div');
            div.style.position = 'absolute';
            div.style.visibility = 'hidden';
            div.style.height = 'auto';
            div.style.width = 'auto';
            div.style.whiteSpace = 'nowrap';
            div.style.fontFamily = 'YOUR_FONT_GOES_HERE';
            div.style.fontSize = '100';
            div.style.border = "1px solid blue"; // for convenience when visible

            div.innerHTML = "YOUR STRING";
            document.body.appendChild(div);
            
            var offsetWidth = div.offsetWidth;
            var clientWidth = div.clientWidth;
            
            document.body.removeChild(div);
            
            return clientWidth;

工作非常出色且精确,但仅限于Firefox浏览器。对于Chrome和Safari浏览器来说,缩放因素是解决问题的关键,但并没有成功。事实证明,Safari和Chrome浏览器的错误不是线性的,无论是字符串长度还是字体大小。
因此,采用第二种方法。我并不太喜欢采用蛮力法,但在这个问题上苦苦挣扎了多年后,我决定尝试一下。我决定为每个可打印字符生成常量值。通常,这会有点乏味,但幸运的是,Firefox浏览器非常准确。以下是我的两部分蛮力解决方案:

<body>
        <script>
            
            var div = document.createElement('div');
            div.style.position = 'absolute';
            div.style.height = 'auto';
            div.style.width = 'auto';
            div.style.whiteSpace = 'nowrap';
            div.style.fontFamily = 'YOUR_FONT';
            div.style.fontSize = '100';          // large enough for good resolution
            div.style.border = "1px solid blue"; // for visible convenience
            
            var character = "";
            var string = "array = [";
            for(var i=0; i<127; i++) {
                character = String.fromCharCode(i);
                div.innerHTML = character;
                document.body.appendChild(div);
                
                var offsetWidth = div.offsetWidth;
                var clientWidth = div.clientWidth;
                console.log("ASCII: " + i + ", " + character + ", client width: " + div.clientWidth);
                
                string = string + div.clientWidth;
                if(i<126) {
                    string = string + ", ";
                }

                document.body.removeChild(div);
                
            }
        
            var space_string = "! !";
            div.innerHTML = space_string;
            document.body.appendChild(div);
            var space_string_width = div.clientWidth;
            document.body.removeChild(div);
            var no_space_string = "!!";
            div.innerHTML = no_space_string;
            document.body.appendChild(div);
            var no_space_string_width = div.clientWidth;
            console.log("space width: " + (space_string_width - no_space_string_width));
            document.body.removeChild(div);


            string = string + "]";
            div.innerHTML = string;
            document.body.appendChild(div);
            </script>
    </body>

注意:以上代码片段必须在Firefox中执行,以生成准确的值数组。此外,在控制台日志中,您必须将数组项32替换为空格宽度值。

我只需复制Firefox屏幕上的文本,并将其粘贴到我的JavaScript代码中。现在,我已经获得了可打印字符长度的数组,我可以实现一个获取宽度的函数。以下是代码:

const LCARS_CHAR_SIZE_ARRAY = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 26, 46, 63, 42, 105, 45, 20, 25, 25, 47, 39, 21, 34, 26, 36, 36, 28, 36, 36, 36, 36, 36, 36, 36, 36, 27, 27, 36, 35, 36, 35, 65, 42, 43, 42, 44, 35, 34, 43, 46, 25, 39, 40, 31, 59, 47, 43, 41, 43, 44, 39, 28, 44, 43, 65, 37, 39, 34, 37, 42, 37, 50, 37, 32, 43, 43, 39, 43, 40, 30, 42, 45, 23, 25, 39, 23, 67, 45, 41, 43, 42, 30, 40, 28, 45, 33, 52, 33, 36, 31, 39, 26, 39, 55];


    static getTextWidth3(text, fontSize) {
        let width = 0;
        let scaleFactor = fontSize/100;
        
        for(let i=0; i<text.length; i++) {
            width = width + LCARS_CHAR_SIZE_ARRAY[text.charCodeAt(i)];
        }
        
        return width * scaleFactor;
    }

好的,就是这样。这种方法虽然有些粗暴,但在所有三个浏览器中都非常准确,我的挫败感已经降到了零。不确定随着浏览器的发展它能持续多久,但应该足够我开发出一种强大的用于SVG文本的字体度量技术。


目前最佳解决方案!感谢分享! - just_user
2
这个不处理字距。 - pat
没错,但对我使用的字体无关紧要。等我有时间时,我会在明年重新审视这个问题。我有一些想法,可以使文本度量更准确,而不需要采用蛮力方法。 - agent-p
太好了。这使我能够静态地对齐SVG图表标签,而不必在本地脚本中动态对齐它们。当从RoR中提供图表时,这使生活变得简单得多。谢谢! - Lex Lindsey

1

SVG规范有一个特定的方法来返回此信息:getComputedTextLength()


var width = textElement.getComputedTextLength(); // returns a pixel number

0

如果您正在使用NodeJS并希望保持应用程序轻量级(例如不使用无头浏览器),则可以使用一种解决方案,即预先计算字体大小。

  1. 使用:https://github.com/nicktaras/getFontCharWidth 您可以确定字体中每个字符的宽度。

  2. 然后在例如node js应用程序中,您可以遍历每个字符以计算实际宽度。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接