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


105

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

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

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

Якісь ідеї?

Відповіді:


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

а потім встановіть атрибути rect відповідно.

Посилання: getBBox()у стандарті SVG v1.1.


4
Дякую. Я також знайшов ще одну функцію, яка могла б допомогти ширині. textElement.getComputedTextLength (). Я спробую обидва сьогодні ввечері і побачу, що для мене краще працює.
Стівен Соренсен

5
Я грав на цьому минулого тижня; метод getComputedTextLength () повернув той самий результат, що й шина getBBox (). Ширина для речей, на які я його спробував, тому я просто пішов із обмежувальним вікном, оскільки мені потрібні були і ширина, і висота. Я не впевнений, чи існують обставини, коли довжина тексту буде відрізнятися від ширини: я думаю, можливо, цей метод передбачений, щоб допомогти в макеті тексту, наприклад, розбиванні тексту на кілька рядків, тоді як обмежувальне поле - це загальний метод на всіх (?) елементах.
NickFitz

2
Проблема полягає в тому, що якщо текст слідує контуру, то ширина не є фактичною шириною тексту (наприклад, якщо текст слідує за колом, то bbox - це той, який закриває коло, і отримує ширину цього знака не ширина тексту до того, як він обходить коло). Інша справа, що 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отримаєте хороші результати, тому немає повороту і, ймовірно, не масштабування (?)


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. Тепер, коли у мене є масив довжини символів для друку, я можу реалізувати функцію 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-тексту.


Найкраще рішення поки що! Дякую, що поділились!
just_user

Це не обробляє кернінг
пат

Щоправда, але для шрифтів, які я використовую, неважливо. Я збираюся переглянути це наступного року, коли отримаю трохи часу. У мене є ідеї щодо того, щоб зробити текстові метрики більш точними для моїх випадків, які не використовують грубу силу підходу.
агент-р

Чудово. Дозволяє мені вирівнювати мітки графіків SVG статично, а не вирівнювати їх за допомогою локального сценарію. Це робить життя набагато простішим, коли подають графіки в Ruby від RoR. Дякую!
Лекс Ліндсі

0

Специфікація SVG має специфічний метод повернення цієї інформації: getComputedTextLength()

var width = textElement.getComputedTextLength(); // returns a pixel number
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.