SVG obtiene el ancho del elemento de texto

105

Estoy trabajando en ECMAScript / JavaScript para un archivo SVG y necesito obtener el widthy heightde un textelemento para poder cambiar el tamaño de un rectángulo que lo rodea. En HTML, podría usar los atributos offsetWidthy offsetHeighten el elemento, pero parece que esas propiedades no están disponibles.

Aquí hay un fragmento con el que necesito trabajar. Necesito cambiar el ancho del rectángulo cada vez que cambio el texto, pero no sé cómo obtener el valor real width(en píxeles) del textelemento.

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

¿Algunas ideas?

Stephen Sorensen
fuente

Respuestas:

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

y luego configure los atributos del rect en consecuencia.

Enlace: getBBox()en el estándar SVG v1.1.

NickFitz
fuente
4
Gracias. También encontré otra función que podría haber ayudado con el ancho. textElement.getComputedTextLength (). Probaré ambos esta noche y veré cuál funciona mejor para mí.
Stephen Sorensen
5
Estaba jugando con estos la semana pasada; el método getComputedTextLength () devolvió el mismo resultado que getBBox (). width para las cosas en las que lo probé, así que simplemente opté por el cuadro delimitador, ya que necesitaba tanto el ancho como el alto. No estoy seguro de si hay alguna circunstancia en la que la longitud del texto sea diferente del ancho: creo que quizás ese método se proporciona para ayudar con el diseño del texto, como dividir el texto en varias líneas, mientras que el cuadro delimitador es un método genérico disponible en todos (?) elementos.
NickFitz
2
El problema es que si el texto sigue una ruta, entonces el ancho no es el ancho real del texto (por ejemplo, si el texto sigue una ruta circular, entonces el bbox es el que encierra el círculo, y obtener el ancho de ese no es 't el ancho del texto antes de dar la vuelta al círculo). Otra cosa es que bbox.width es mayor en un factor de 2, por lo que si el inspector de elementos muestra un ancho de 200, entonces bbox.width es 400. No estoy seguro de por qué.
trusktr
¿Cómo puedes calcular la longitud antes de que se dibuje?
Nikos hace
7

Con respecto a la longitud del texto, el enlace parece indicar que BBox y getComputedTextLength () pueden devolver valores ligeramente diferentes, pero que son bastante cercanos entre sí.

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

Andrew Paté
fuente
¡¡Gracias por ese link!! Tuve que revisar todos los escenarios por mí mismo, pero este es un resumen realmente bueno ... Mi problema fue usar getBBox () en un elemento Tspan en Firefox ... No podría ser un problema más específico y molesto ... . ¡gracias de nuevo!
Andres Elizondo
4
document.getElementById('yourTextId').getComputedTextLength();

trabajó para mí en

Zombi
fuente
Su solución devuelve la longitud real del elemento de texto que es la respuesta correcta. Algunas respuestas usan getBBox () que puede ser correcto si el texto no se rota.
Netsi1964
2

¿Qué tal algo como esto por compatibilidad:

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

Esto devuelve el promedio de los resultados válidos de los tres métodos. Puede mejorarlo para descartar valores atípicos o para favorecer getComputedTextLengthsi el elemento es un elemento de texto.

Advertencia: como dice el comentario, getBoundingClientRectes complicado. Elimínelo de los métodos o utilícelo solo en los elementos en los que se getBoundingClientRectobtendrán buenos resultados, por lo que no hay rotación y probablemente no hay escala (?)

HostedMetrics.com
fuente
1
getBoundingClientRect funciona en un sistema de coordenadas potencialmente diferente a los otros dos.
Robert Longson
2

No estoy seguro de por qué, pero ninguno de los métodos anteriores funciona para mí. Tuve cierto éxito con el método del lienzo, pero tuve que aplicar todo tipo de factores de escala. Incluso con los factores de escala, seguí obteniendo resultados inconsistentes entre Safari, Chrome y Firefox.

Entonces, probé lo siguiente:

            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;

Funcionó de manera increíble y súper precisa, pero solo en Firefox. Factores de escala al rescate para Chrome y Safari, pero sin alegría. Resulta que los errores de Safari y Chrome no son lineales ni con la longitud de la cadena ni con el tamaño de la fuente.

Entonces, acérquese al número dos. No me importa mucho el enfoque de fuerza bruta, pero después de luchar con esto de forma intermitente durante años, decidí intentarlo. Decidí generar valores constantes para cada carácter imprimible individual. Normalmente esto sería algo tedioso, pero afortunadamente Firefox resulta ser súper preciso. Aquí está mi solución de fuerza bruta de dos partes:

<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: El fragmento de código anterior debe ejecutarse en Firefox para generar una matriz precisa de valores. Además, debe reemplazar el elemento 32 de la matriz con el valor del ancho del espacio en el registro de la consola.

Simplemente copio el texto de Firefox en la pantalla y lo pego en mi código javascript. Ahora que tengo la matriz de longitudes de caracteres imprimibles, puedo implementar una función de obtener ancho. Aquí está el código:

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

Bueno, eso es todo. Fuerza bruta, pero es súper precisa en los tres navegadores, y mi nivel de frustración ha bajado a cero. No estoy seguro de cuánto durará a medida que evolucionen los navegadores, pero debería ser lo suficientemente largo para que pueda desarrollar una técnica sólida de métricas de fuentes para mi texto SVG.

agente-p
fuente
¡La mejor solución hasta ahora! ¡Gracias por compartir!
just_user
Esto no maneja el kerning
pat
Es cierto, pero no importa para las fuentes que uso. Voy a volver a visitar esto el próximo año cuando tenga tiempo. Tengo algunas ideas sobre cómo hacer que las métricas de texto sean más precisas para mis casos que no utilizan un enfoque de fuerza bruta.
agent-p
Excelente. Me permite alinear las etiquetas de los gráficos SVG de forma estática en lugar de tener que alinearlas sobre la marcha en un script local. Eso hace que la vida sea mucho más sencilla al ofrecer gráficos en Ruby desde RoR. ¡Gracias!
Lex Lindsey
0

La especificación SVG tiene un método específico para devolver esta información: getComputedTextLength()

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