SVG mendapatkan lebar elemen teks


106

Saya sedang mengerjakan beberapa ECMAScript / JavaScript untuk file SVG dan perlu mendapatkan widthdan heightdari textelemen sehingga saya dapat mengubah ukuran persegi panjang yang mengelilinginya. Dalam HTML saya akan dapat menggunakan atribut offsetWidthand offsetHeightpada elemen tetapi tampaknya properti tersebut tidak tersedia.

Ini fragmen yang perlu saya kerjakan. Saya perlu mengubah lebar persegi panjang setiap kali saya mengubah teks tetapi saya tidak tahu bagaimana mendapatkan elemen sebenarnya width(dalam piksel) text.

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

Ada ide?

Jawaban:


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

dan kemudian setel atribut rect sesuai.

Tautan: getBBox()dalam standar SVG v1.1.


4
Terima kasih. Saya juga menemukan fungsi lain yang bisa membantu dengan lebar. textElement.getComputedTextLength (). Saya akan mencoba keduanya malam ini dan melihat mana yang bekerja lebih baik untuk saya.
Stephen Sorensen

5
Saya bermain dengan ini minggu lalu; metode getComputedTextLength () mengembalikan hasil yang sama seperti getBBox (). width untuk hal-hal yang saya coba, jadi saya hanya menggunakan kotak pembatas, karena saya membutuhkan lebar dan tinggi. Saya tidak yakin apakah ada keadaan ketika panjang teks akan berbeda dari lebarnya: Saya pikir mungkin metode itu disediakan untuk membantu tata letak teks seperti membagi teks menjadi beberapa baris, sedangkan kotak pembatas adalah metode umum yang tersedia pada semua elemen (?).
NickFitz

2
Masalahnya adalah, jika teks mengikuti suatu jalur, maka lebarnya bukanlah lebar teks yang sebenarnya (misalnya jika teks mengikuti jalur lingkaran maka bbox adalah yang melingkupi lingkaran, dan mendapatkan lebarnya tidak. bukan lebar teks sebelum itu mengelilingi lingkaran). Hal lainnya adalah bbox.width lebih besar dengan faktor 2, jadi jika pemeriksa elemen menunjukkan lebar 200, maka bbox.width adalah 400. Saya tidak yakin mengapa.
trusktr

Bagaimana Anda bisa menghitung panjangnya sebelum digambar?
Nikos

7

Mengenai panjang teks, tautan tampaknya menunjukkan BBox dan getComputedTextLength () mungkin mengembalikan nilai yang sedikit berbeda, tetapi yang cukup dekat satu sama lain.

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


Terima kasih untuk tautan tersebut!! Saya harus melalui semua skenario sendirian, tetapi ini adalah ringkasan yang sangat bagus ... Masalah saya menggunakan getBBox () pada elemen Tspan di firefox ... Ini tidak bisa menjadi masalah yang lebih spesifik dan mengganggu .. . Terima kasih lagi!
Andres Elizondo

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

bekerja untuk saya di


Solusi Anda mengembalikan panjang sebenarnya dari elemen teks yang merupakan jawaban yang benar. Beberapa jawaban menggunakan getBBox () yang bisa benar jika teks tidak diputar.
Netsi1964

2

Bagaimana dengan sesuatu seperti ini untuk kompatibilitas:

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

Ini mengembalikan rata-rata hasil valid dari ketiga metode. Anda dapat meningkatkannya untuk membuang pencilan atau mendukung getComputedTextLengthjika elemennya adalah elemen teks.

Peringatan: Seperti yang dikatakan dalam komentar, getBoundingClientRectitu rumit. Hapus dari metode atau gunakan ini hanya pada elemen di mana getBoundingClientRectakan mengembalikan hasil yang baik, jadi tidak ada rotasi dan mungkin tidak ada penskalaan (?)


1
getBoundingClientRect bekerja dalam sistem koordinat yang berpotensi berbeda dengan dua lainnya.
Robert Longson

2

Tidak yakin mengapa, tetapi tidak ada metode di atas yang berhasil untuk saya. Saya cukup berhasil dengan metode kanvas, tetapi saya harus menerapkan semua jenis faktor skala. Bahkan dengan faktor skala, saya masih memiliki hasil yang tidak konsisten antara Safari, Chrome, dan Firefox.

Jadi, saya mencoba yang berikut ini:

            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;

Bekerja dengan luar biasa dan sangat tepat, tetapi hanya di Firefox. Faktor skala untuk menyelamatkan Chrome dan Safari, tetapi tidak ada kegembiraan. Ternyata kesalahan Safari dan Chrome tidak linier dengan panjang string atau ukuran font.

Jadi, pendekatan nomor dua. Saya tidak terlalu peduli dengan pendekatan brute force, tetapi setelah berjuang dengan ini dan mematikan selama bertahun-tahun saya memutuskan untuk mencobanya. Saya memutuskan untuk menghasilkan nilai konstan untuk setiap karakter yang dapat dicetak. Biasanya ini akan agak membosankan, tetapi untungnya Firefox ternyata sangat akurat. Inilah dua solusi brute force saya:

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

Catatan: Cuplikan di atas harus dijalankan di Firefox untuk menghasilkan larik nilai yang akurat. Selain itu, Anda harus mengganti item array 32 dengan nilai lebar spasi di log konsol.

Saya cukup menyalin teks Firefox di layar, dan menempelkannya ke kode javascript saya. Sekarang saya memiliki array panjang karakter yang dapat dicetak, saya dapat mengimplementasikan fungsi get width. Ini kodenya:

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

Nah, itu dia. Brute force, tetapi sangat akurat di ketiga browser, dan tingkat frustrasi saya telah mencapai nol. Tidak yakin berapa lama ini akan bertahan seiring berkembangnya browser, tetapi seharusnya cukup lama bagi saya untuk mengembangkan teknik metrik font yang kuat untuk teks SVG saya.


Solusi terbaik sejauh ini! Terima kasih telah berbagi!
just_user

Ini tidak menangani kerning
tepuk

Benar, tetapi tidak masalah untuk font yang saya gunakan. Saya akan mengunjungi kembali ini tahun depan ketika saya punya waktu. Saya punya beberapa ide tentang membuat metrik teks lebih akurat untuk kasus saya yang tidak menggunakan pendekatan brute force.
agen-p

Bagus. Izinkan saya untuk meratakan label bagan SVG secara statis daripada harus meratakannya dengan cepat dalam skrip lokal. Itu membuat hidup jauh lebih sederhana saat menyajikan grafik di Ruby dari RoR. Terima kasih!
Lex Lindsey

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.