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

106

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

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

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

Любые идеи?

Стивен Соренсен
источник

Ответы:

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

а затем соответствующим образом установите атрибуты прямоугольника.

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

НикФитц
источник
4
Спасибо. Я также нашел другую функцию, которая могла бы помочь с шириной. textElement.getComputedTextLength (). Я попробую оба сегодня вечером и посмотрю, что мне больше подходит.
Стивен Соренсен
5
Я играл с ними на прошлой неделе; метод getComputedTextLength () вернул тот же результат, что и getBBox (). width для вещей, на которых я его пробовал, поэтому я просто использовал ограничивающую рамку, поскольку мне нужны были и ширина, и высота. Я не уверен, есть ли какие-либо обстоятельства, при которых длина текста будет отличаться от ширины: я думаю, что, возможно, этот метод предоставляется, чтобы помочь с макетом текста, таким как разделение текста на несколько строк, тогда как ограничивающая рамка - это общий доступный метод по всем (?) элементам.
NickFitz
2
Проблема в том, что если текст следует по пути, то ширина не является фактической шириной текста (например, если текст следует по кругу, тогда bbox - это тот, который охватывает круг, и получение ширины этого isn 't ширина текста перед тем, как он обходит круг). Другое дело, что bbox.width больше в 2 раза, поэтому, если инспектор элементов показывает ширину 200, то bbox.width - 400. Я не уверен, почему.
trusktr
Как определить длину до того, как она нарисована?
Никос
7

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

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

Эндрю Пэйт
источник
Спасибо за ссылку !! Мне пришлось самому пройти все сценарии, но это действительно хорошее резюме ... Моя проблема заключалась в использовании getBBox () в элементе Tspan в firefox ... Это не могло быть более конкретной и раздражающей проблемой .. . Спасибо еще раз!
Андрес Элизондо
4
document.getElementById('yourTextId').getComputedTextLength();

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

Зомби
источник
Ваше решение возвращает фактическую длину текстового элемента, что является правильным ответом. В некоторых ответах используется getBBox (), который может быть правильным, если текст не повернут.
Netsi1964
2

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

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которые вернут хорошие результаты, поэтому без вращения и, вероятно, без масштабирования (?)

HostedMetrics.com
источник
1
getBoundingClientRect работает в системе координат, потенциально отличающейся от двух других.
Роберт Лонгсон,
2

Не знаю почему, но ни один из вышеперечисленных методов мне не подходит. У меня был некоторый успех с методом холста, но мне пришлось применять всевозможные масштабные коэффициенты. Даже с учетом масштабных коэффициентов у меня все еще были противоречивые результаты между 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, но без радости. Оказывается, ошибки 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);
            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>

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

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

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.

агент-п
источник
Лучшее решение на данный момент! Спасибо, что поделился!
just_user
Это не
pat
Верно, но не имеет значения для шрифтов, которые я использую. Я собираюсь вернуться к этому в следующем году, когда у меня будет время. У меня есть некоторые идеи, как сделать текстовые метрики более точными для моих случаев, которые не используют метод грубой силы.
agent-p
Отлично. Позволяет выравнивать метки диаграмм SVG статически, вместо того, чтобы выравнивать их на лету в локальном скрипте. Это значительно упрощает жизнь при отображении диаграмм в Ruby из RoR. Спасибо!
Лекс Линдси,
0

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

var width = textElement.getComputedTextLength(); // returns a pixel number
Cuixiping
источник