SVG получить ширину текстового элемента


Я работаю над некоторыми ECMAScript / JavaScript для SVG-файла и должен получить width и height на text элемент, так что я могу изменить размер прямоугольника, который его окружает. В HTML я мог бы использовать offsetWidth и offsetHeight атрибуты на элементе, но кажется, что эти свойства недоступны.

вот фрагмент, с которым мне нужно работать. Мне нужно изменить ширину прямоугольника всякий раз, когда я меняю текст, но я не знаю, как получить фактический width (в пикселях)text элемент.

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

какие идеи?

6 88

6 ответов:

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

и затем установите атрибуты rect соответственно.

ссылки: getBBox() в стандарте SVG v1.1.

Что касается длины текста, то ссылка, по-видимому, указывает на то, что BBox и getComputedTextLength() могут возвращать несколько разные значения, но те, которые довольно близки друг к другу.

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

как насчет чего-то вроде этого для совместимости:

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

это возвращает среднее значение всех допустимых результатов трех методов. Вы можете улучшить его, чтобы изгнать выбросы или в пользу getComputedTextLength, если элемент является текстовым элементом.

внимание: как говорится в комментарии,getBoundingClientRect сложно. Либо удалите его из методов, либо используйте это только на элементах, где getBoundingClientRect вернет хорошие результаты, так что без вращения и, вероятно, без масштабирования(?)

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

У меня работал в

не знаю, почему, но ни один из вышеперечисленных методов не работает для меня. У меня был некоторый успех с методом холста, но мне пришлось применить все виды масштабных факторов. Даже с масштабными коэффициентами у меня все еще были противоречивые результаты между Safari, Chrome и Firefox.

Итак, я попробовал следующий:

            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;

работал потрясающе и супер точно, но только в Firefox. Масштабные коэффициенты спасают Chrome и Safari, но радости нет. Оказывается, что сафари и Ошибки в Chrome не линейный с длиной строки или размер шрифта.

Итак, подход номер два. Мне не очень нравится подход грубой силы, но после борьбы с этим в течение многих лет я решил попробовать. Я решил создать постоянные значения для каждого печатного символа. Обычно это было бы довольно утомительно, но, к счастью, Firefox оказался очень точным. Вот мои две части грубой силы решение:

    <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);
            console.log("space width: " + div.clientWidth - space_string.charCodeAt(0)*2);
            document.body.removeChild(div);

            string = string + "]";
            div.innerHTML = string;
            document.body.appendChild(div);
            </script>
    </body>

Примечание: приведенный выше фрагмент должен выполняться в Firefox для создания точного массива значений. Кроме того, необходимо заменить элемент массива 32 значением ширины пробела в журнале консоли.

Я просто копирую текст Firefox на экране и вставляю его в свой код javascript. Теперь, когда у меня есть массив печатаемых длин символов, я могу реализовать функцию get width. Вот код:

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

Ну вот и все. Грубая сила, но она супер точна во всех трех браузерах, и мой уровень разочарования упал до нуля. Не уверен, как долго это будет продолжаться, поскольку браузеры развиваются, но это должно быть достаточно долго для меня, чтобы разработать надежный метод метрики шрифтов для моего текста SVG.

SVG spec имеет определенный метод для возврата этой информации:getComputedTextLength()

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