SVG pobiera szerokość elementu tekstowego

106

Pracuję nad pewnym ECMAScript / JavaScript do pliku SVG i trzeba dostać widthi heightz textelementu więc mogę zmienić rozmiar prostokąta, który go otacza. W HTML mógłbym użyć atrybutów offsetWidthi offsetHeightna elemencie, ale wygląda na to, że te właściwości są niedostępne.

Oto fragment, nad którym muszę popracować. Muszę zmienić szerokość prostokąta za każdym razem, gdy zmieniam tekst, ale nie wiem, jak uzyskać faktyczny width(w pikselach) textelement.

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

Jakieś pomysły?

Stephen Sorensen
źródło

Odpowiedzi:

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

a następnie odpowiednio ustaw atrybuty prostego.

Link: getBBox()w standardzie SVG v1.1.

NickFitz
źródło
4
Dzięki. Znalazłem też inną funkcję, która pomogłaby przy szerokości. textElement.getComputedTextLength (). Wypróbuję oba dziś wieczorem i zobaczę, co działa lepiej dla mnie.
Stephen Sorensen
5
Bawiłem się nimi w zeszłym tygodniu; metoda getComputedTextLength () zwróciła ten sam wynik co getBBox (). width dla rzeczy, na których ją wypróbowałem, więc po prostu poszedłem z obwiednią, ponieważ potrzebowałem zarówno szerokości, jak i wysokości. Nie jestem pewien, czy są jakieś okoliczności, w których długość tekstu byłaby inna niż szerokość: myślę, że być może ta metoda jest dostarczana, aby pomóc w układzie tekstu, takim jak dzielenie tekstu na wiele linii, podczas gdy obwiednia jest dostępną metodą ogólną na wszystkich (?) elementach.
NickFitz
2
Problem polega na tym, że jeśli tekst podąża ścieżką, to szerokość nie jest rzeczywistą szerokością tekstu (na przykład, jeśli tekst podąża za ścieżką koła, to ramka jest tą, która otacza okrąg, a uzyskanie szerokości tego nie jest 't szerokość tekstu przed okrążeniem koła). Inną rzeczą jest to, że bbox.width jest dwukrotnie większa, więc jeśli inspektor elementów pokazuje szerokość 200, to bbox.width wynosi 400. Nie wiem dlaczego.
trusktr
Jak możesz obliczyć długość przed narysowaniem?
Nikos
7

Jeśli chodzi o długość tekstu, link wydaje się wskazywać, że BBox i getComputedTextLength () mogą zwracać nieco inne wartości, ale takie, które są dość blisko siebie.

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

pasztet z andrzeja
źródło
Dziękuję za ten link !! Musiałem sam przejść przez wszystkie scenariusze, ale to jest naprawdę dobre podsumowanie ... Mój problem polegał na użyciu getBBox () w elemencie Tspan w przeglądarce Firefox ... Nie może to być bardziej konkretny i irytujący problem. . dzięki jeszcze raz!
Andres Elizondo
4
document.getElementById('yourTextId').getComputedTextLength();

pracował dla mnie w

Zambi
źródło
Twoje rozwiązanie zwraca rzeczywistą długość elementu tekstowego, który jest poprawną odpowiedzią. Niektóre odpowiedzi używają metody getBBox (), która może być poprawna, jeśli tekst nie jest obracany.
Netsi1964
2

Co powiesz na coś takiego dla kompatybilności:

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

Zwraca średnią wszystkich prawidłowych wyników trzech metod. Możesz go ulepszyć, aby wyeliminować wartości odstające lub faworyzować, getComputedTextLengthjeśli element jest elementem tekstowym.

Ostrzeżenie: jak mówi komentarz, getBoundingClientRectjest to trudne. Albo usuń to z metod, albo użyj tego tylko na elementach, w których getBoundingClientRectzwrócą dobre wyniki, więc bez rotacji i prawdopodobnie bez skalowania (?)

HostedMetrics.com
źródło
1
getBoundingClientRect działa w potencjalnie innym układzie współrzędnych niż pozostałe dwa.
Robert Longson,
2

Nie wiem dlaczego, ale żadna z powyższych metod nie działa dla mnie. Odniosłem pewien sukces z metodą płótna, ale musiałem zastosować wszystkie rodzaje współczynników skali. Mimo współczynników skali nadal miałem niespójne wyniki między przeglądarkami Safari, Chrome i Firefox.

Więc spróbowałem następujących rzeczy:

            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;

Działał niesamowicie i super precyzyjnie, ale tylko w Firefoksie. Skaluj współczynniki na ratunek dla Chrome i Safari, ale bez radości. Okazuje się, że błędy Safari i Chrome nie są liniowe ani w przypadku długości ciągu, ani rozmiaru czcionki.

A więc podejdź do numeru dwa. Nie przepadam za podejściem brutalnej siły, ale po wieloletnich zmaganiach z tym z przerwami postanowiłem spróbować. Postanowiłem wygenerować stałe wartości dla każdego drukowalnego znaku. Normalnie byłoby to trochę żmudne, ale na szczęście Firefox jest super dokładny. Oto moje dwuczęściowe rozwiązanie brutalnej siły:

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

Uwaga: powyższy fragment musi zostać wykonany w przeglądarce Firefox, aby wygenerować dokładną tablicę wartości. Musisz także zastąpić element tablicy 32 wartością szerokości spacji w dzienniku konsoli.

Po prostu kopiuję tekst ekranowy przeglądarki Firefox i wklejam go do mojego kodu javascript. Teraz, gdy mam tablicę drukowalnych długości znaków, mogę zaimplementować funkcję get width. Oto kod:

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

Cóż, to jest to. Brutalna siła, ale jest bardzo dokładna we wszystkich trzech przeglądarkach, a mój poziom frustracji spadł do zera. Nie jestem pewien, jak długo to potrwa w miarę rozwoju przeglądarek, ale powinno wystarczyć, abym opracował solidną technikę metryk czcionek dla mojego tekstu SVG.

agent-p
źródło
Jak dotąd najlepsze rozwiązanie! Dzięki za udostępnienie!
just_user
To nie obsługuje kerningu
przejdź
To prawda, ale nie ma to znaczenia dla czcionek, których używam. Mam zamiar wrócić do tego w przyszłym roku, kiedy będę miał trochę czasu. Mam kilka pomysłów na zwiększenie dokładności metryk tekstowych w moich przypadkach, w których nie stosuje się metody brutalnej siły.
agent-p
Wspaniały. Pozwala mi wyrównywać etykiety wykresów SVG statycznie, zamiast wyrównywać je w locie w lokalnym skrypcie. To sprawia, że ​​życie jest o wiele prostsze, gdy udostępniasz wykresy w Ruby z RoR. Dzięki!
Lex Lindsey
0

Specyfikacja SVG ma określoną metodę zwracania tych informacji: getComputedTextLength()

var width = textElement.getComputedTextLength(); // returns a pixel number
cuixiping
źródło