SVG get text element width

前端 未结 6 1814
误落风尘
误落风尘 2020-12-02 07:40

I\'m working on some ECMAScript/JavaScript for an SVG file and need to get the width and height of a text element so I can resize a re

相关标签:
6条回答
  • 2020-12-02 08:11

    Regarding the length of text the link seems to indicate BBox and getComputedTextLength() may return slightly different values, but ones that are fairly close to each other.

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

    0 讨论(0)
  • 2020-12-02 08:13

    SVG spec has a specific method to return this info: getComputedTextLength()

    var width = textElement.getComputedTextLength(); // returns a pixel number
    
    0 讨论(0)
  • 2020-12-02 08:15

    Not sure why, but none of the above methods work for me. I had some success with the canvas method, but I had to apply all kinds of scale factors. Even with the scale factors I still had inconsistent results between Safari, Chrome, and Firefox.

    So, I tried the following:

                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;

    Worked awesome and super precise, but only in Firefox. Scale factors to the rescue for Chrome and Safari, but no joy. Turns out that Safari and Chrome errors are not linear with either string length or font size.

    So, approach number two. I don't much care for the brute force approach, but after struggling with this on and off for years I decided to give it a try. I decided to generate constant values for each individual printable character. Normally this would be kind of tedious, but luckily Firefox happens to be super accurate. Here is my two part brute force solution:

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

    Note: The above snippet has to executed in Firefox to generate an accurate array of values. Also, you do have to replace array item 32 with the space width value in the console log.

    I simply copy the Firefox on screen text, and paste it into my javascript code. Now that I have the array of printable character lengths, I can implement a get width function. Here is the 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;
        }

    Well, that is it. Brute force, but it is super accurate in all three browsers, and my frustration level has gone to zero. Not sure how long it will last as the browsers evolve, but it should be long enough for me to develop a robust font metrics technique for my SVG text.

    0 讨论(0)
  • 2020-12-02 08:16
    var bbox = textElement.getBBox();
    var width = bbox.width;
    var height = bbox.height;
    

    and then set the rect's attributes accordingly.

    Link: getBBox() in the SVG v1.1 standard.

    0 讨论(0)
  • 2020-12-02 08:17

    How about something like this for compatibility:

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

    This returns the average of any valid results of the three methods. You could improve it to cast out outliers or to favor getComputedTextLength if the element is a text element.

    Warning: As the comment says, getBoundingClientRect is tricky. Either remove it from the methods or use this only on elements where getBoundingClientRect will return good results, so no rotation and probably no scaling(?)

    0 讨论(0)
  • 2020-12-02 08:19
    document.getElementById('yourTextId').getComputedTextLength();
    

    worked for me in

    0 讨论(0)
提交回复
热议问题