SVG erhält Textelementbreite


105

Ich arbeite an ECMAScript / JavaScript für eine SVG-Datei und muss das widthund heighteines textElements abrufen, damit ich die Größe eines Rechtecks ​​ändern kann, das es umgibt. In HTML könnte ich die Attribute offsetWidthund offsetHeightfür das Element verwenden, aber es scheint, dass diese Eigenschaften nicht verfügbar sind.

Hier ist ein Fragment, mit dem ich arbeiten muss. Ich muss die Breite des Rechtecks ​​ändern, wenn ich den Text ändere, aber ich weiß nicht, wie ich den tatsächlichen Wert width(in Pixel) des textElements ermitteln kann.

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

Irgendwelche Ideen?

Antworten:


154
var bbox = textElement.getBBox();
var width = bbox.width;
var height = bbox.height;

und stellen Sie dann die Attribute des Rect entsprechend ein.

Link: getBBox()im SVG v1.1 Standard.


4
Vielen Dank. Ich habe auch eine andere Funktion gefunden, die bei der Breite hätte helfen können. textElement.getComputedTextLength (). Ich werde heute Abend beide ausprobieren und sehen, was für mich besser funktioniert.
Stephen Sorensen

5
Ich habe letzte Woche mit diesen gespielt; Die Methode getComputedTextLength () hat für die Dinge, an denen ich sie ausprobiert habe, das gleiche Ergebnis wie getBBox () zurückgegeben. width habe ich mich also für den Begrenzungsrahmen entschieden, da ich sowohl Breite als auch Höhe benötigte. Ich bin mir nicht sicher, ob es Umstände gibt, unter denen sich die Textlänge von der Breite unterscheidet: Ich denke, diese Methode wird möglicherweise bereitgestellt, um das Textlayout zu erleichtern, z. B. das Aufteilen von Text über mehrere Zeilen, während der Begrenzungsrahmen eine generische Methode ist auf alle (?) Elemente.
NickFitz

2
Das Problem ist, wenn der Text einem Pfad folgt, dann ist die Breite nicht die tatsächliche Breite des Textes (wenn der Text beispielsweise einem Kreispfad folgt, ist die bbox diejenige, die den Kreis umschließt, und die Breite dieses Pfads wird ermittelt 't die Breite des Textes, bevor er um den Kreis herumgeht). Eine andere Sache ist, dass bbox.width um den Faktor 2 größer ist. Wenn also der Elementinspektor eine Breite von 200 anzeigt, beträgt bbox.width 400. Ich bin mir nicht sicher, warum.
Trusktr

7

In Bezug auf die Textlänge scheint der Link darauf hinzudeuten, dass BBox und getComputedTextLength () leicht unterschiedliche Werte zurückgeben, die jedoch ziemlich nahe beieinander liegen.

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


Danke für diesen Link !! Ich musste alle Szenarien selbst durchgehen, aber dies ist eine wirklich gute Zusammenfassung ... Mein Problem war die Verwendung von getBBox () für ein Tspan-Element in Firefox ... Es könnte kein spezifischeres und ärgerlicheres Problem sein. . Danke noch einmal!
Andres Elizondo

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

arbeitete für mich in


Ihre Lösung gibt die tatsächliche Länge des Textelements zurück, was die richtige Antwort ist. Einige Antworten verwenden die getBBox (), die korrekt sein kann, wenn der Text nicht gedreht wird.
Netsi1964

2

Wie wäre es mit so etwas aus Kompatibilitätsgründen:

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

Dies gibt den Durchschnitt aller gültigen Ergebnisse der drei Methoden zurück. Sie können es verbessern, um Ausreißer auszutreiben oder zu bevorzugen, getComputedTextLengthwenn das Element ein Textelement ist.

Warnung: Wie der Kommentar sagt, getBoundingClientRectist schwierig. Entfernen Sie es entweder aus den Methoden oder verwenden Sie es nur für Elemente, bei denen getBoundingClientRectgute Ergebnisse erzielt werden, also keine Drehung und wahrscheinlich keine Skalierung (?)


1
getBoundingClientRect arbeitet in einem möglicherweise anderen Koordinatensystem als die beiden anderen.
Robert Longson

2

Ich weiß nicht warum, aber keine der oben genannten Methoden funktioniert bei mir. Ich hatte einige Erfolge mit der Canvas-Methode, musste aber alle möglichen Skalierungsfaktoren anwenden. Trotz der Skalierungsfaktoren hatte ich immer noch inkonsistente Ergebnisse zwischen Safari, Chrome und Firefox.

Also habe ich folgendes versucht:

            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;

Hat super und super präzise funktioniert, aber nur in Firefox. Skalierungsfaktoren zur Rettung von Chrome und Safari, aber keine Freude. Es stellt sich heraus, dass Safari- und Chrome-Fehler weder mit der Zeichenfolgenlänge noch mit der Schriftgröße linear sind.

Gehen Sie also Nummer zwei an. Ich mag den Brute-Force-Ansatz nicht besonders, aber nachdem ich jahrelang damit zu kämpfen hatte, beschloss ich, es auszuprobieren. Ich habe beschlossen, für jedes einzelne druckbare Zeichen konstante Werte zu generieren. Normalerweise wäre das etwas langweilig, aber zum Glück ist Firefox sehr genau. Hier ist meine zweiteilige Brute-Force-Lösung:

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

Hinweis: Das obige Snippet muss in Firefox ausgeführt werden, um ein genaues Array von Werten zu generieren. Außerdem müssen Sie das Array-Element 32 durch den Wert für die Leerzeichenbreite im Konsolenprotokoll ersetzen.

Ich kopiere einfach den Firefox-Bildschirmtext und füge ihn in meinen Javascript-Code ein. Nachdem ich nun das Array druckbarer Zeichenlängen habe, kann ich eine Funktion zum Abrufen der Breite implementieren. Hier ist der Code:

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

Nun, das ist es. Brute Force, aber es ist in allen drei Browsern sehr genau und meine Frustration ist auf Null gesunken. Ich bin mir nicht sicher, wie lange es dauern wird, wenn sich die Browser weiterentwickeln, aber es sollte lang genug sein, damit ich eine robuste Technik für Schriftmetriken für meinen SVG-Text entwickeln kann.


Beste Lösung bisher! Danke für das Teilen!
just_user

Dies behandelt nicht Kerning
Pat

Stimmt, spielt aber für die von mir verwendeten Schriftarten keine Rolle. Ich werde nächstes Jahr wiederkommen, wenn ich etwas Zeit habe. Ich habe einige Ideen, wie ich Textmetriken für meine Fälle genauer machen kann, die keinen Brute-Force-Ansatz verwenden.
Agent-p

Toll. Ermöglicht es mir, SVG-Diagrammbeschriftungen statisch auszurichten, anstatt sie im lokalen Skript im laufenden Betrieb ausrichten zu müssen. Das macht das Leben viel einfacher, wenn Diagramme in Ruby von RoR bereitgestellt werden. Vielen Dank!
Lex Lindsey

0

Die SVG-Spezifikation verfügt über eine bestimmte Methode, um diese Informationen zurückzugeben: getComputedTextLength()

var width = textElement.getComputedTextLength(); // returns a pixel number
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.