SVG ottiene la larghezza dell'elemento di testo


106

Sto lavorando su alcuni ECMAScript / JavaScript per un file SVG e ho bisogno di ottenere il widthe heightdi un filetext ho elemento in modo da poter ridimensionare un rettangolo che lo circonda. In HTML sarei in grado di utilizzare gli attributi offsetWidthe offsetHeightsull'elemento ma sembra che quelle proprietà non siano disponibili.

Ecco un frammento con cui devo lavorare. Devo modificare la larghezza del rettangolo ogni volta che cambio il testo ma non so come ottenere l'effettivo width(in pixel) textdell'elemento.

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

Qualche idea?

Risposte:


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

e quindi impostare gli attributi del rect di conseguenza.

Link: getBBox()nello standard SVG v1.1.


4
Grazie. Ho anche trovato un'altra funzione che avrebbe potuto aiutare con la larghezza. textElement.getComputedTextLength (). Proverò entrambi stasera e vedrò quale funziona meglio per me.
Stephen Sorensen,

5
Stavo giocando con questi la scorsa settimana; il metodo getComputedTextLength () ha restituito lo stesso risultato di getBBox (). width per le cose su cui l'ho provato, quindi ho scelto il riquadro di delimitazione, poiché avevo bisogno sia della larghezza che dell'altezza. Non sono sicuro che ci siano circostanze in cui la lunghezza del testo sarebbe diversa dalla larghezza: penso che forse questo metodo sia fornito per aiutare con il layout del testo come la divisione del testo su più righe, mentre il riquadro di delimitazione è un metodo generico disponibile su tutti (?) gli elementi.
NickFitz

2
Il problema è che se il testo segue un percorso, la larghezza non è la larghezza effettiva del testo (ad esempio, se il testo segue un percorso circolare, la casella b è quella che racchiude il cerchio e la larghezza di quella non è 't la larghezza del testo prima che gira intorno al cerchio). Un'altra cosa è che bbox.width è maggiore di un fattore 2, quindi se l'ispettore dell'elemento mostra una larghezza di 200, allora bbox.width è 400. Non sono sicuro del perché.
trusktr

Come puoi calcolare la lunghezza prima che venga disegnata?
Nikos

7

Per quanto riguarda la lunghezza del testo, il collegamento sembra indicare BBox e getComputedTextLength () possono restituire valori leggermente diversi, ma abbastanza vicini tra loro.

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


Grazie per quel link!! Ho dovuto esaminare tutti gli scenari da solo, ma questo è davvero un buon riassunto ... Il mio problema era usare getBBox () su un elemento Tspan su Firefox ... Non potrebbe essere un problema più specifico e fastidioso .. . grazie ancora!
Andres Elizondo

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

ha lavorato per me in


La tua soluzione restituisce la lunghezza effettiva dell'elemento di testo che è la risposta corretta. Alcune risposte usano getBBox () che può essere corretto se il testo non viene ruotato.
Netsi1964

2

Che ne dici di qualcosa di simile per compatibilità:

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

Ciò restituisce la media di tutti i risultati validi dei tre metodi. Potresti migliorarlo per eliminare i valori anomali o per favorire getComputedTextLengthse l'elemento è un elemento di testo.

Avvertenza: come dice il commento, getBoundingClientRectè complicato. Rimuovilo dai metodi o usalo solo su elementi in cui getBoundingClientRectrestituirà buoni risultati, quindi nessuna rotazione e probabilmente nessun ridimensionamento (?)


1
getBoundingClientRect funziona in un sistema di coordinate potenzialmente diverso dagli altri due.
Robert Longson,

2

Non sono sicuro del perché, ma nessuno dei metodi precedenti funziona per me. Ho avuto un certo successo con il metodo canvas, ma ho dovuto applicare tutti i tipi di fattori di scala. Anche con i fattori di scala ho ancora avuto risultati incoerenti tra Safari, Chrome e Firefox.

Quindi, ho provato quanto segue:

            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;

Ha funzionato in modo fantastico e super preciso, ma solo in Firefox. Fattori di scala in soccorso per Chrome e Safari, ma nessuna gioia. Si scopre che gli errori di Safari e Chrome non sono lineari né con la lunghezza della stringa né con la dimensione del carattere.

Quindi, avvicinati al numero due. Non mi interessa molto l'approccio della forza bruta, ma dopo aver lottato con questo per anni ho deciso di provarlo. Ho deciso di generare valori costanti per ogni singolo carattere stampabile. Normalmente questo sarebbe un po 'noioso, ma fortunatamente Firefox sembra essere estremamente preciso. Ecco la mia soluzione di forza bruta in due parti:

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

Nota: lo snippet di cui sopra deve essere eseguito in Firefox per generare un array di valori accurato. Inoltre, è necessario sostituire l'elemento dell'array 32 con il valore della larghezza dello spazio nel registro della console.

Copio semplicemente il testo di Firefox sullo schermo e lo incollo nel mio codice javascript. Ora che ho l'array di lunghezze di caratteri stampabili, posso implementare una funzione get width. Ecco il codice:

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

Bene, questo è tutto. Forza bruta, ma è super preciso in tutti e tre i browser e il mio livello di frustrazione è arrivato a zero. Non sono sicuro di quanto durerà con l'evolversi dei browser, ma dovrebbe essere abbastanza lungo per me per sviluppare una robusta tecnica di metrica dei caratteri per il mio testo SVG.


La migliore soluzione finora! Grazie per la condivisione!
just_user

Questo non gestisce la crenatura
picchietta

Vero, ma non importa per i caratteri che uso. Tornerò l'anno prossimo quando avrò un po 'di tempo. Ho alcune idee su come rendere le metriche di testo più accurate per i miei casi che non utilizzano un approccio di forza bruta.
agente-p

Grande. Mi consente di allineare staticamente le etichette dei grafici SVG anziché doverle allineare al volo in uno script locale. Ciò rende la vita molto più semplice quando si servono le classifiche in Ruby di RoR. Grazie!
Lex Lindsey

0

Le specifiche SVG hanno un metodo specifico per restituire queste informazioni: getComputedTextLength()

var width = textElement.getComputedTextLength(); // returns a pixel number
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.