SVG obtenir la largeur de l'élément de texte

106

Je travaille sur un ECMAScript / JavaScript pour un fichier SVG et j'ai besoin d'obtenir le widthet heightd'un textélément afin de pouvoir redimensionner un rectangle qui l'entoure. En HTML, je pourrais utiliser les attributs offsetWidthet offsetHeightsur l'élément, mais il semble que ces propriétés ne soient pas disponibles.

Voici un fragment avec lequel je dois travailler. J'ai besoin de changer la largeur du rectangle chaque fois que je change le texte mais je ne sais pas comment obtenir le réel width(en pixels) de l' textélément.

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

Des idées?

Stephen Sorensen
la source

Réponses:

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

puis définissez les attributs du rect en conséquence.

Lien: getBBox()dans le standard SVG v1.1.

NickFitz
la source
4
Merci. J'ai également trouvé une autre fonction qui aurait pu aider avec la largeur. textElement.getComputedTextLength (). Je vais essayer les deux ce soir et voir ce qui fonctionne le mieux pour moi.
Stephen Sorensen
5
Je jouais avec ces derniers la semaine dernière; la méthode getComputedTextLength () a renvoyé le même résultat que getBBox (). width pour les choses sur lesquelles je l'ai essayé, alors je suis simplement allé avec la boîte englobante, car j'avais besoin de largeur et de hauteur. Je ne sais pas s'il existe des circonstances dans lesquelles la longueur du texte serait différente de la largeur: je pense que cette méthode est peut-être fournie pour aider à la mise en page du texte telle que le fractionnement du texte sur plusieurs lignes, alors que le cadre de sélection est une méthode générique disponible sur tous les éléments (?).
NickFitz
2
Le problème est que si le texte suit un chemin, alors la largeur n'est pas la largeur réelle du texte (par exemple, si le texte suit un chemin circulaire, la bbox est celle qui entoure le cercle, et obtenir la largeur de ce n'est 't la largeur du texte avant de faire le tour du cercle). Une autre chose est que bbox.width est plus grand d'un facteur de 2, donc si l'inspecteur d'élément affiche une largeur de 200, alors bbox.width est de 400. Je ne sais pas pourquoi.
trusktr
Comment pouvez-vous calculer la longueur avant qu'elle ne soit dessinée?
Nikos
7

En ce qui concerne la longueur du texte, le lien semble indiquer que BBox et getComputedTextLength () peuvent renvoyer des valeurs légèrement différentes, mais qui sont assez proches les unes des autres.

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

andrew pate
la source
Merci pour ce lien!! J'ai dû parcourir tous les scénarios par moi-même, mais c'est un très bon résumé ... Mon problème était d'utiliser getBBox () sur un élément Tspan sur Firefox ... Cela ne pouvait pas être un problème plus spécifique et plus ennuyeux ... . Merci encore!
Andres Elizondo
4
document.getElementById('yourTextId').getComputedTextLength();

travaillé pour moi dans

Zombi
la source
Votre solution renvoie la longueur réelle de l'élément de texte qui est la bonne réponse. Certaines réponses utilisent le getBBox () qui peut être correct si le texte n'est pas pivoté.
Netsi1964
2

Que diriez-vous de quelque chose comme ça pour la 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;
}

Cela renvoie la moyenne de tous les résultats valides des trois méthodes. Vous pouvez l'améliorer pour éliminer les valeurs aberrantes ou pour favoriser getComputedTextLengthsi l'élément est un élément de texte.

Attention: comme le dit le commentaire, getBoundingClientRectc'est délicat. Supprimez-le des méthodes ou utilisez-le uniquement sur les éléments où getBoundingClientRectretournera de bons résultats, donc pas de rotation et probablement pas de mise à l'échelle (?)

HostedMetrics.com
la source
1
getBoundingClientRect fonctionne dans un système de coordonnées potentiellement différent des deux autres.
Robert Longson
2

Je ne sais pas pourquoi, mais aucune des méthodes ci-dessus ne fonctionne pour moi. J'ai eu un certain succès avec la méthode du canevas, mais j'ai dû appliquer toutes sortes de facteurs d'échelle. Même avec les facteurs d'échelle, j'avais toujours des résultats incohérents entre Safari, Chrome et Firefox.

Alors, j'ai essayé ce qui suit:

            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;

A fonctionné génial et super précis, mais seulement dans Firefox. Échelle des facteurs à la rescousse pour Chrome et Safari, mais pas de joie. Il s'avère que les erreurs Safari et Chrome ne sont pas linéaires avec la longueur de la chaîne ou la taille de la police.

Alors, approchez-vous du numéro deux. Je n'aime pas beaucoup l'approche de la force brute, mais après avoir lutté avec cela de temps en temps pendant des années, j'ai décidé de l'essayer. J'ai décidé de générer des valeurs constantes pour chaque caractère imprimable individuel. Normalement, ce serait un peu fastidieux, mais heureusement, Firefox est extrêmement précis. Voici ma solution de force brute en deux parties:

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

Remarque: L'extrait ci-dessus doit être exécuté dans Firefox pour générer un tableau précis de valeurs. Vous devez également remplacer l'élément de tableau 32 par la valeur de largeur d'espace dans le journal de la console.

Je copie simplement le texte de Firefox à l'écran et je le colle dans mon code javascript. Maintenant que j'ai le tableau des longueurs de caractères imprimables, je peux implémenter une fonction get width. Voici le 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;
    }

Eh bien, c'est ça. Force brute, mais elle est super précise dans les trois navigateurs, et mon niveau de frustration est tombé à zéro. Je ne sais pas combien de temps cela va durer au fur et à mesure que les navigateurs évoluent, mais cela devrait être assez long pour que je développe une technique de métrique de police robuste pour mon texte SVG.

agent-p
la source
La meilleure solution à ce jour! Merci d'avoir partagé!
just_user
Cela ne gère pas le crénage
Pat
C'est vrai, mais cela n'a pas d'importance pour les polices que j'utilise. Je vais y revenir l'année prochaine quand j'aurai du temps. J'ai quelques idées pour rendre les métriques de texte plus précises pour mes cas qui n'utilisent pas une approche de force brute.
agent-p
Génial. Cela me permet d'aligner les étiquettes de graphiques SVG de manière statique plutôt que de les aligner à la volée dans un script local. Cela rend la vie beaucoup plus simple lors de la diffusion de graphiques dans Ruby de RoR. Merci!
Lex Lindsey
0

La spécification SVG a une méthode spécifique pour renvoyer ces informations: getComputedTextLength()

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