SVGはテキスト要素の幅を取得します


105

私はSVGファイルのECMAScript / JavaScriptに取り組んでいて、要素のwidthand を取得して、それを囲む長方形のサイズを変更できるようにする必要があります。HTMLでは要素のおよび属性を使用できますが、これらのプロパティは使用できないようです。heighttextoffsetWidthoffsetHeight

これが私が作業する必要があるフラグメントです。テキストを変更するたびに長方形の幅を変更する必要がありますがwidthtext要素の実際のサイズ(ピクセル単位)を取得する方法がわかりません。

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

何か案は?

回答:


155
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 2009年

2
問題は、テキストがパスをたどる場合、幅はテキストの実際の幅ではない(たとえば、テキストが円のパスをたどる場合、bboxは円を囲むものであり、その幅を取得することです。円を一周する前のテキストの幅ではありません)。もう1つは、bbox.widthが2倍大きいことです。そのため、要素インスペクターが200の幅を示している場合、bbox.widthは400です。理由はわかりません。
trusktr 2016年

それが描かれる前に、どのように長さを計算できますか?
ニコス

7

テキストの長さに関して、リンクはBBoxを示しているようで、getComputedTextLength()はわずかに異なる値を返す可能性がありますが、互いにかなり近い値です。

http://bl.ocks.org/MSCAU/58bba77cdcae42fc2f44


リンクありがとうございます!! 私は自分ですべてのシナリオを実行する必要がありましたが、これは本当に良い要約です...私の問題は、FirefoxのTspan要素でgetBBox()を使用していたことです...これは、より具体的で厄介な問題ではありませんでした。 。 再度、感謝します!
Andres Elizondo 2017年

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

私のために働いた


ソリューションは、正解であるテキスト要素の実際の長さを返します。一部の回答はgetBBox()を使用していますが、これはテキストが回転していない場合は正しい場合があります。
Netsi1964

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

これは、3つのメソッドの有効な結果の平均を返します。これを改善して、外れ値をキャストしたりgetComputedTextLength、要素がテキスト要素である場合は優先したりできます。

警告:コメントが言うように、getBoundingClientRectトリッキーです。メソッドから削除するか、getBoundingClientRect良好な結果が返される要素でのみ使用してください。回転せず、おそらくスケーリングなし(?)


1
getBoundingClientRectは、他の2つとは異なる可能性のある座標系で機能します。
Robert Longson、2015年

2

理由はわかりませんが、上記の方法はどれもうまくいきません。canvasメソッドではある程度成功しましたが、あらゆる種類のスケールファクターを適用する必要がありました。スケール係数を使用しても、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のエラーは、文字列の長さやフォントサイズに比例しないことがわかります。

それでは、2番目にアプローチします。私はブルートフォースアプローチをあまり気にしませんが、これを何年もオンとオフで苦労した後、試してみることにしました。印刷可能な文字ごとに定数値を生成することにしました。通常、これは退屈な作業ですが、幸いにもFirefoxは非常に正確です。これが私の2部構成のブルートフォースソリューションです。

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

まあ、それはそれです。ブルートフォースですが、3つのブラウザーすべてで非常に正確であり、私の不満のレベルはゼロになりました。ブラウザーの進化に伴ってどれだけ続くかはわかりませんが、SVGテキスト用の堅牢なフォントメトリックテクニックを開発するには十分な長さでなければなりません。


これまでのところ最高の解決策!共有してくれてありがとう!
just_user 2018年

これはカーニングを処理しません
pat '26

確かに、使用するフォントには関係ありません。時間が来たら、来年もまた訪れたいと思います。ブルートフォースアプローチを使用しない私のケースに対してテキストメトリックをより正確にすることについていくつかのアイデアがあります。
agent-p

すごい。ローカルスクリプトでSVGチャートラベルをその場で配置する必要なく、静的に配置できます。これにより、RoRからRubyでチャートを提供する際の作業が大幅に簡素化されます。ありがとう!
Lex Lindsey

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.