> [Site index](https://perfectvector.com/llms.txt) — available pages and descriptions.

# Pembungkusan Teks SVG: Buat Baris Terukur dengan tspan

> Bungkus label SVG dengan mengukur kata memakai font sebenarnya, lalu buat tspan. Uji label sempit, tolak kata meluber atau baris berlebih, dan simpan hasilnya.

Source: https://perfectvector.com/id/blog/svg-text-wrap-tspan

Language: id
Author: Irene Kim
Published: 2026-10-06T08:00:00+09:00
Updated: 2026-10-06T08:00:00+09:00

Untuk membungkus label SVG yang berubah menjadi baris eksplisit, ukur kandidat kelompok kata dengan `getComputedTextLength()`, lalu tulis satu `<tspan>` untuk setiap baris yang diterima. Tunggu font dokumen terlebih dahulu dan tentukan tindakan saat satu kata lebih lebar daripada label atau hasil melebihi batas baris.

Kode awal di bawah memakai kata yang dipisahkan spasi dan menolak kedua jenis luapan tanpa menghasilkan label parsial. Kode memisahkan ilustrasi wajik berlubang asli dari kata-katanya, sehingga perubahan lebar label tidak menggambar ulang ilustrasi.

## Pilih kebijakan pembungkusan sebelum menempatkan baris

[Elemen `tspan`](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/tspan) memungkinkan Anda memosisikan bagian teks SVG. Prosedur kami menetapkan `x` yang sama pada setiap baris dan offset `dy` pada baris berikutnya. Prosedur memutuskan pemenggalan baris; elemen menyimpan hasilnya.

Ini salah satu pendekatan tata letak eksplisit. [Spesifikasi teks SVG 2](https://www.w3.org/TR/SVG2/text.html) juga mendefinisikan pembungkusan otomatis melalui area konten dan tata letak teks CSS. Artikel ini tidak menguji fitur tersebut atau mengklaim semua peramban tidak memilikinya.

Untuk paragraf HTML di luar ilustrasi, [bungkus teks mengelilingi siluet SVG mengambang dengan CSS](https://perfectvector.com/id/blog/css-text-around-svg-shape). Tata letak itu mengikuti kontur gambar sementara kata-katanya tetap HTML biasa, bukan baris label SVG yang diposisikan.

Untuk contoh label bahasa Inggris kecil ini, kebijakannya adalah:

* Satukan whitespace dan coba kata dalam urutan asli, dipisahkan satu spasi.
* Pertahankan kandidat baris hanya selama panjang terukurnya muat dalam lebar yang tersedia.
* Tolak kata yang tidak muat sendirian; jangan menciptakan titik pemenggalan dengan tanda hubung.
* Tolak seluruh hasil saat membutuhkan lebih dari `maxLines`; jangan diam-diam membuang kata terakhir.

Batas baris merupakan keputusan tata letak. Pilih bersama baseline awal, jarak antarbaris, dan tinggi yang tersedia. Pembungkus tidak menghitung kotak konten vertikal untuk Anda.

**Ubah lebar label, pertahankan ilustrasi**

![Perbandingan konseptual ilustrasi wajik berlubang di samping baris panjang pada label lebar dan baris pendek pada label sempit](https://perfectvector.com/blog/svg-text-wrap-tspan/measured-lines.webp)

*Ilustrasi: label lebih sempit dapat membutuhkan lebih banyak baris. Batang abstrak dan bingkai bertitik merupakan petunjuk konseptual; kata-kata, pengukuran, dan status penolakan sebenarnya di bawah berasal dari kode awal peramban independen.*

## Ukur dengan font yang akan menggambar label

[`getComputedTextLength()`](https://developer.mozilla.org/en-US/docs/Web/API/SVGTextContentElement/getComputedTextLength) mengukur panjang teks SVG yang dihitung. Kode awal menggandakan elemen teks label menjadi probe tersembunyi dalam SVG terpasang yang sama, lalu menghapus probe setelah merencanakan baris. Keduanya memakai `Arial, sans-serif` eksplisit pada 22 satuan.

Kode menunggu [`document.fonts.ready`](https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/ready) sebelum mengukur. Ini menunggu pemuatan font dokumen dan tata letak terkait; tidak menyematkan font ke SVG yang diekspor. Font berbeda yang tersedia bisa menghasilkan panjang baris berbeda. Jalankan ulang tata letak ketika kata-kata, pengaturan font, atau lebar yang tersedia berubah.

## Jalankan kode awal lengkap tanpa dependensi

Simpan sebagai `starter.html` dan buka di peramban. Kode membuat enam kasus, mencetak pengukurannya, dan menserialisasi setiap SVG hasilnya. Dua kasus pertama hanya mengubah lebar label; yang ketiga mengubah kata-kata. Kasus lain menguji penolakan dan teks literal.

```html
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Measured SVG label lines</title>
<style>
  body { margin: 24px; font: 16px system-ui; background: #f6f2e8; }
  #cases { display: grid; grid-template-columns: repeat(2, 360px); gap: 20px; }
  figure { margin: 0; }
  svg { display: block; background: white; }
  figcaption { margin: 6px 0; }
  pre { white-space: pre-wrap; font-size: 12px; }
</style>
<h1>Keep artwork and label layout separate</h1>
<div id="cases"></div>
<pre id="report"></pre>
<script>
  const ns = 'http://www.w3.org/2000/svg';
  const emblem = 'M60 32 L100 72 L60 112 L20 72 Z M60 54 L78 72 L60 90 L42 72 Z';
  function element(name, attributes) {
    const node = document.createElementNS(ns, name);
    for (const [key, value] of Object.entries(attributes)) node.setAttribute(key, value);
    return node;
  }
  function wrapWords(text, source, { width, maxLines, lineHeight }) {
    text.replaceChildren();
    if (!(width > 0 && lineHeight > 0 && Number.isInteger(maxLines) && maxLines > 0)) {
      return { status: 'invalid-options', lines: [] };
    }
    const words = source.trim().split(/\s+/u).filter(Boolean);
    const probe = text.cloneNode(false);
    probe.setAttribute('visibility', 'hidden');
    text.parentNode.append(probe);
    const measure = value => { probe.textContent = value; return probe.getComputedTextLength(); };
    const lines = [];
    let line = '';
    try {
      for (const word of words) {
        const wordWidth = measure(word);
        if (wordWidth > width) {
          return { status: 'overlong-word', word, measuredWidth: wordWidth, lines: [] };
        }
        const candidate = line ? line + ' ' + word : word;
        if (line && measure(candidate) > width) {
          lines.push(line);
          line = word;
        } else line = candidate;
      }
      if (line) lines.push(line);
      if (lines.length > maxLines) {
        return { status: 'too-many-lines', requiredLines: lines.length, lines: [] };
      }
      const x = text.getAttribute('x');
      lines.forEach((value, index) => {
        const span = element('tspan', { x, dy: index ? lineHeight : 0 });
        span.textContent = value;
        text.append(span);
      });
      return { status: 'ok', lines,
        lengths: [...text.children].map(span => span.getComputedTextLength()) };
    } finally { probe.remove(); }
  }
  const cases = [
    { id: 'normal', source: 'Keep artwork and labels separate', width: 200, maxLines: 4 },
    { id: 'narrow', source: 'Keep artwork and labels separate', width: 110, maxLines: 5 },
    { id: 'changed', source: 'Keep the original artwork beside every exported label', width: 200, maxLines: 4 },
    { id: 'long-word', source: 'Artwork supercalifragilisticexpialidocious', width: 110, maxLines: 5 },
    { id: 'line-limit', source: 'Keep artwork and labels separate', width: 110, maxLines: 2 },
    { id: 'literal', source: 'Cove <g> & field', width: 200, maxLines: 4 },
  ];
  const entries = cases.map(test => {
    const figure = document.createElement('figure');
    const svg = element('svg', { width: 360, height: 240, viewBox: '0 0 360 240' });
    svg.append(element('path', { d: emblem, fill: '#176b5b', 'fill-rule': 'evenodd' }));
    svg.append(element('rect', { x: 140, y: 34, width: test.width, height: 170,
      fill: 'none', stroke: '#ce7b55', 'stroke-dasharray': '4 4' }));
    const text = element('text', { x: 140, y: 60, 'font-family': 'Arial, sans-serif',
      'font-size': 22, fill: '#172b34' });
    svg.append(text);
    const caption = document.createElement('figcaption');
    figure.append(svg, caption);
    document.getElementById('cases').append(figure);
    return { test, svg, text, caption };
  });
  async function run() {
    await document.fonts.ready;
    const results = entries.map(({ test, svg, text, caption }) => {
      const result = wrapWords(text, test.source, { ...test, lineHeight: 30 });
      caption.textContent = test.id + ': ' + result.status;
      return { ...test, ...result, fontFamily: getComputedStyle(text).fontFamily,
        fontSize: getComputedStyle(text).fontSize, tspanCount: text.children.length,
        pathD: svg.querySelector('path').getAttribute('d'),
        pathCount: svg.querySelectorAll('path').length,
        imageCount: svg.querySelectorAll('image').length,
        foreignObjectCount: svg.querySelectorAll('foreignObject').length,
        serialized: new XMLSerializer().serializeToString(svg) };
    });
    document.getElementById('report').textContent = JSON.stringify({
      fontStatus: document.fonts.status, policy: 'Whitespace words; reject a long word or too many lines without partial output', results,
    }, null, 2);
  }
  run();
</script>
</html>
```

Prosedur membuat setiap baris dengan [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent), bukan memasukkan kata-kata sebagai markup. Dalam kasus teks literal, `Cove <g> & field` tetap teks biasa: SVG yang diserialisasi berisi tanda kurung sudut dan ampersand yang di-escape, bukan elemen grup tambahan.

Klon pengukuran terpasang tetapi tersembunyi, dengan atribut font yang sama dengan label terlihat. Klon dihapus dalam `finally`, termasuk saat ditolak. Baris baru ditambahkan setelah pemeriksaan lebar kata dan batas baris lolos. Prosedur terlebih dahulu menghapus label sebelumnya, sehingga pembaruan yang ditolak tidak meninggalkan teks lama atau parsial; tangani status yang dikembalikan dalam antarmuka aplikasi Anda.

## Hasil dari enam kasus asli

Kami menjalankan kode awal ini persis di Chrome dengan font dokumen melaporkan `loaded`. Panjang terukur berikut adalah panjang teks SVG untuk lingkungan itu, bukan batas glyph yang dicat atau jaminan portabilitas font.

| Kasus                                    | Lebar / maksimum baris | Hasil                                            | Baris terpanjang yang diamati |
| ---------------------------------------- | ---------------------- | ------------------------------------------------ | ----------------------------- |
| Frasa asli                               | 200 / 4                | Dua baris: Keep artwork and; labels separate     | 172.44                        |
| Frasa sama, kotak sempit                 | 110 / 5                | Empat baris: Keep; artwork; and labels; separate | 100.30                        |
| Kata-kata lebih panjang                  | 200 / 4                | Tiga baris                                       | 198.13                        |
| Kata terlalu panjang                     | 110 / 5                | overlong-word; nol tspan                         | Kata terukur 311.82           |
| Frasa sempit, batas dua baris            | 110 / 2                | too-many-lines; perlu empat; nol tspan           | Tidak ada label yang dibuat   |
| Tanda kurung sudut dan ampersand literal | 200 / 4                | Satu baris literal                               | 162.66                        |

Frasa asli adalah `Keep artwork and labels separate`. Versi lebih panjang adalah `Keep the original artwork beside every exported label`. Setiap baris yang diterima terukur muat dalam lebar pilihannya. Kasus penolakan membiarkan ilustrasi terlihat sambil menghilangkan label, sehingga status kegagalan mudah diperiksa.

Keenam berkas hasil serialisasi mempertahankan path wajik asli yang identik, satu elemen teks native, dan tanpa image atau `foreignObject`. Kasus diterima masing-masing berisi dua, empat, tiga, dan satu tspan; kedua kasus ditolak berisi nol. Kami juga membuka kembali keenam SVG hasil serialisasi sebagai gambar di peramban yang sama, dan semuanya dimuat pada 360×240. Ini pemeriksaan berkas dan peramban tersebut, bukan setiap editor atau renderer.

## Simpan SVG yang sudah ditata, lalu uji penyerahan

[`XMLSerializer.serializeToString()`](https://developer.mozilla.org/en-US/docs/Web/API/XMLSerializer/serializeToString) membuat markup terserialisasi dalam laporan. Simpan string `serialized` dari kasus berhasil sebagai berkas `.svg`. Berkas sudah berisi baris eksplisit; tidak membutuhkan prosedur JavaScript ini untuk membuatnya ulang.

Kata-katanya masih berupa teks hidup. Berkas tersimpan mempertahankan permintaan font-family, bukan berkas font atau glyph yang diubah menjadi kontur. Tujuan dengan font lain dapat mengubah bentuk huruf meskipun string baris dan posisinya tetap. Simpan kata-kata asli dan masukan tata letak bersama salinan ekspor, serta periksa tujuan sebenarnya sebelum menyerahkan. Untuk bentuk huruf tetap, buat salinan penyerahan berkontur di editor sumber yang sesuai sambil mempertahankan teks hidup dalam master.

[Panduan teks foreignObject](https://perfectvector.com/id/blog/svg-foreignobject-text) membahas label HTML yang menghilang di renderer lain. Kode awal ini membuat teks native sejak awal. [Panduan textLength](https://perfectvector.com/id/blog/svg-textlength-fit) membahas penyesuaian satu baris dengan mengubah panjang; itu keputusan berbeda dari menambahkan baris terukur. Gunakan [panduan perataan vertikal](https://perfectvector.com/id/blog/svg-text-vertical-alignment) saat perlu memosisikan seluruh label terhadap gambar di sekitarnya.

## Perjelas batas pembungkus

Pemisahan berdasarkan whitespace merupakan kebijakan yang sengaja kecil. Kebijakan menyatukan spasi berulang, tab, dan baris baru eksplisit, serta tidak menangani aturan pemenggalan khusus bahasa, tanda hubung, paragraf dua arah, atau span kaya dengan gaya campuran. Pilih sistem tata letak lain atau perluas dan uji kebijakan ketika teks membutuhkan fitur tersebut.

Pemeriksaan lebar juga tidak menjamin bagian glyph dekoratif yang menjulur tetap dalam bingkai tergambar, atau lima baseline muat pada tinggi pilihan. Periksa tulisan terlihat pada ukuran penyerahan. Contoh uji kami memakai label horizontal, atribut font tetap, dan langkah baris 30 satuan; jangan terapkan angkanya pada typeface lain tanpa mengukur ulang.

Jika ilustrasi hanya tersedia sebagai PNG atau JPG dan Anda membutuhkan gambar yang bisa diedit, [siapkan SVG clipart dengan PerfectVector](https://perfectvector.com/id/clipart-to-svg), periksa tepi dan bukaan dalam, lalu pisahkan gambar itu dari label. Ketik ulang kata-kata sebagai teks native, jalankan kebijakan pembungkusan, dan periksa ekspor gabungan. Vektorisasi menyiapkan path ilustrasi; tidak memilih pemenggalan baris atau memulihkan kata yang bisa diedit dari huruf yang dijiplak. Path bagus yang sudah ada dapat tetap seperti semula. [Panduan vektorisasi](https://perfectvector.com/id/blog/what-is-image-vectorization) menjelaskan tahap pemulihan sumber tersebut.

## Pertanyaan umum

**Apakah tspan otomatis memutuskan tempat setiap baris dipenggal?**
Kode awal memutuskan dengan mengukur kelompok kata, lalu membuat satu tspan berposisi untuk setiap baris yang diterima. Kode tidak menguji pembungkusan otomatis berbasis CSS SVG 2.

**Apa yang terjadi pada kata yang lebih lebar daripada label?**
Prosedur mengembalikan overlong-word dan tidak membuat label. Perlebar area, pendekkan kata-kata, atau berikan kebijakan pemenggalan yang diuji terpisah alih-alih memotongnya diam-diam.

**Apakah maxLines membuang kata yang tersisa?**
Tidak. Kebijakan menolak hasil ketika membutuhkan terlalu banyak baris, melaporkan jumlah yang diperlukan, dan menyisakan nol tspan. Aplikasi harus menangani status tersebut.

**Apakah baris yang diekspor terlihat identik dengan font lain?**
Kata dan posisi eksplisit tetap ada, tetapi berkas tidak menyematkan font atau membuat kontur glyph. Periksa font yang tersedia dan bentuk huruf di tujuan sebenarnya.

## Sumber

1. [MDN elemen tspan](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/tspan) — subteks berposisi dan atribut offset baris.
2. [W3C teks SVG 2](https://www.w3.org/TR/SVG2/text.html) — penempatan eksplisit dan model pembungkusan otomatis terpisah.
3. [MDN getComputedTextLength](https://developer.mozilla.org/en-US/docs/Web/API/SVGTextContentElement/getComputedTextLength) — mengukur teks SVG sebenarnya.
4. [MDN FontFaceSet.ready](https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/ready) — menunggu pemuatan font dan tata letak.
5. [MDN textContent](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) — memasukkan teks biasa alih-alih markup.
6. [MDN XMLSerializer](https://developer.mozilla.org/en-US/docs/Web/API/XMLSerializer/serializeToString) — menyimpan markup SVG yang sudah ditata.

***

Untuk ilustrasi raster Anda sendiri, [siapkan gambar clipart yang bisa diedit](https://perfectvector.com/id/clipart-to-svg), periksa bentuknya, dan simpan master. Tambahkan kata-kata secara terpisah, lalu verifikasi baris yang diterima dan tulisan terlihat dalam SVG tersimpan sebelum menyerahkan.

## Explore

- [Read the HTML article](https://perfectvector.com/id/blog/svg-text-wrap-tspan)
- [SVG.js: Impor Gambar, Edit Path, dan Ekspor SVG](https://perfectvector.com/id/blog/svgjs-import-export-artwork.md)
- [Marker SVG Leaflet: Sejajarkan Ujung dengan iconAnchor](https://perfectvector.com/id/blog/leaflet-svg-marker-anchor.md)
- [Product facts](https://perfectvector.com/product-facts.md)
- [FAQ](https://perfectvector.com/faq.md)
- [Pricing and credits](https://perfectvector.com/pricing.md)
