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

# Zawijanie tekstu SVG: mierzone wiersze z tspan

> Zawijaj etykiety SVG, mierząc słowa właściwą czcionką i tworząc tspany. Sprawdź wąskie pola, odrzucaj zbyt długie słowa lub nadmiar wierszy i zapisz wynik.

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

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

Aby podzielić zmienną etykietę SVG na jawnie zapisane wiersze, zmierz proponowane grupy słów metodą `getComputedTextLength()`, a następnie zapisz po jednym `<tspan>` na każdy zaakceptowany wiersz. Najpierw poczekaj na czcionki dokumentu i ustal, co zrobić, gdy jedno słowo jest szersze od etykiety lub wynik przekracza limit wierszy.

Poniższy przykład korzysta ze słów rozdzielonych białymi znakami i odrzuca oba rodzaje przepełnienia bez tworzenia częściowej etykiety. Oryginalna ilustracja pustego rombu pozostaje oddzielona od tekstu, więc zmiana szerokości etykiety nie przerysowuje grafiki.

## Ustal zasady zawijania przed rozmieszczeniem wierszy

[Element `tspan`](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/tspan) pozwala pozycjonować fragmenty tekstu SVG. Nasza funkcja ustawia jednakowe `x` dla każdego wiersza oraz przesunięcie `dy` dla kolejnych wierszy. To funkcja decyduje o podziale; element przechowuje wynik.

Jest to jeden ze sposobów jawnego rozmieszczania tekstu. [Specyfikacja tekstu SVG 2](https://www.w3.org/TR/SVG2/text.html) definiuje również automatyczne zawijanie z wykorzystaniem obszaru treści i układu tekstu CSS. Ten artykuł nie testuje tych funkcji ani nie twierdzi, że brakuje ich w każdej przeglądarce.

W przypadku akapitu HTML poza ilustracją [oblej tekstem pływającą sylwetkę SVG za pomocą CSS](https://perfectvector.com/pl/blog/css-text-around-svg-shape). Taki układ podąża za konturem obrazu, a treść pozostaje zwykłym HTML zamiast stawać się pozycjonowanymi wierszami etykiety SVG.

W tym niewielkim przykładzie angielskiej etykiety obowiązują następujące zasady:

* Sprowadź białe znaki do pojedynczych spacji i sprawdzaj słowa w pierwotnej kolejności.
* Zachowuj proponowany wiersz tylko wtedy, gdy jego zmierzona długość mieści się w dostępnej szerokości.
* Odrzuć słowo, które nie mieści się samodzielnie; nie wymyślaj miejsca podziału wyrazu.
* Odrzuć cały wynik, gdy wymaga więcej niż `maxLines`; nie usuwaj po cichu ostatnich słów.

Limit wierszy jest decyzją dotyczącą układu. Dobierz go razem z początkową linią bazową, odstępem między wierszami i dostępną wysokością. Funkcja zawijająca nie oblicza za ciebie pionowego obszaru treści.

**Zmieniaj szerokość etykiety, zachowuj grafikę**

![Schematyczne porównanie ilustracji pustego rombu obok dłuższych wierszy szerokiej etykiety i krótszych wierszy wąskiej etykiety](https://perfectvector.com/blog/svg-text-wrap-tspan/measured-lines.webp)

*Ilustracja: węższa etykieta może wymagać większej liczby wierszy. Abstrakcyjne paski i kropkowane ramki są wskazówkami koncepcyjnymi; rzeczywisty tekst, pomiary i stany odrzucenia poniżej pochodzą z niezależnego przykładu uruchamianego w przeglądarce.*

## Mierz czcionką, która będzie rysować etykietę

[`getComputedTextLength()`](https://developer.mozilla.org/en-US/docs/Web/API/SVGTextContentElement/getComputedTextLength) mierzy obliczoną długość tekstu SVG. Ten przykład klonuje element tekstowy etykiety jako ukrytą sondę wewnątrz tego samego SVG dołączonego do dokumentu, a po zaplanowaniu wierszy usuwa sondę. Oba elementy mają jawnie ustawione `Arial, sans-serif` o rozmiarze 22 jednostek.

Kod przed pomiarem czeka na [`document.fonts.ready`](https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/ready). Oznacza to oczekiwanie na wczytanie czcionek dokumentu i związane z nimi przeliczenie układu; nie osadza czcionki w eksportowanym SVG. Inna dostępna czcionka może dać inne długości wierszy. Przelicz układ po zmianie tekstu, ustawień czcionki lub dostępnej szerokości.

## Uruchom kompletny przykład bez zależności

Zapisz poniższy kod jako `starter.html` i otwórz go w przeglądarce. Tworzy sześć przypadków, wypisuje ich pomiary i serializuje każdy wynikowy SVG. Pierwsze dwa przypadki zmieniają tylko szerokość etykiety; trzeci zmienia tekst. Pozostałe sprawdzają odrzucanie wyniku i tekst dosłowny.

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

Funkcja tworzy każdy wiersz za pomocą [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent), zamiast wstawiać treść jako znaczniki. W przypadku tekstu dosłownego `Cove <g> & field` pozostaje zwykłym tekstem: zserializowany SVG zawiera zakodowane nawiasy ostrokątne i znak ampersand, a nie dodatkowy element grupy.

Klon pomiarowy jest dołączony do dokumentu, lecz ukryty, i ma takie same atrybuty czcionki jak widoczna etykieta. Jest usuwany w `finally`, również przy odrzuceniu wyniku. Wiersze są dodawane dopiero po pomyślnym sprawdzeniu szerokości słów i maksymalnej liczby wierszy. Funkcja najpierw usuwa poprzednią etykietę, więc odrzucona aktualizacja nie pozostawia starego ani częściowego tekstu; obsłuż zwrócony stan w interfejsie aplikacji.

## Wyniki sześciu oryginalnych przypadków

Uruchomiliśmy dokładnie ten przykład w Chrome, gdzie czcionki dokumentu zgłaszały stan `loaded`. Poniższe zmierzone długości są długościami tekstu SVG w tym środowisku, a nie granicami namalowanych glifów ani gwarancją przenośności czcionek.

| Przypadek                               | Szerokość / maksymalna liczba wierszy | Wynik                                               | Najdłuższy zaobserwowany wiersz |
| --------------------------------------- | ------------------------------------- | --------------------------------------------------- | ------------------------------- |
| Oryginalna fraza                        | 200 / 4                               | Dwa wiersze: Keep artwork and; labels separate      | 172.44                          |
| Ta sama fraza, wąskie pole              | 110 / 5                               | Cztery wiersze: Keep; artwork; and labels; separate | 100.30                          |
| Dłuższy tekst                           | 200 / 4                               | Trzy wiersze                                        | 198.13                          |
| Zbyt długie słowo                       | 110 / 5                               | overlong-word; zero tspanów                         | Słowo zmierzone jako 311.82     |
| Wąska fraza, limit dwóch wierszy        | 110 / 2                               | too-many-lines; wymagane cztery; zero tspanów       | Nie utworzono etykiety          |
| Dosłowne nawiasy ostrokątne i ampersand | 200 / 4                               | Jeden dosłowny wiersz                               | 162.66                          |

Oryginalna fraza to `Keep artwork and labels separate`. Dłuższa wersja to `Keep the original artwork beside every exported label`. Każdy zaakceptowany wiersz mieścił się w wybranej szerokości. W odrzuconych przypadkach ilustracja pozostała widoczna, a etykieta została pominięta, co ułatwia sprawdzenie stanu błędu.

Wszystkie sześć zserializowanych plików zachowało identyczną oryginalną ścieżkę rombu, jeden natywny element tekstowy oraz brak obrazu i elementu `foreignObject`. Zaakceptowane przypadki zawierały odpowiednio dwa, cztery, trzy i jeden tspan; oba odrzucone przypadki zawierały zero. Otworzyliśmy też ponownie sześć zserializowanych SVG jako obrazy w tej samej przeglądarce, gdzie wszystkie wczytały się w rozmiarze 360×240. To sprawdzenie tych plików i tej przeglądarki, a nie każdego edytora lub renderera.

## Zapisz SVG z układem i sprawdź plik docelowy

[`XMLSerializer.serializeToString()`](https://developer.mozilla.org/en-US/docs/Web/API/XMLSerializer/serializeToString) tworzy zserializowane znaczniki w raporcie. Zapisz ciąg `serialized` z udanego przypadku jako plik `.svg`. Plik zawiera już jawnie zapisane wiersze; nie potrzebuje tej funkcji JavaScript, aby je odtworzyć.

Słowa nadal pozostają edytowalnym tekstem. Zapisany plik zachowuje żądanie rodziny czcionki, a nie plik czcionki lub glify zamienione na krzywe. Środowisko docelowe z inną czcionką może zmienić wygląd liter, mimo zachowania tekstów i pozycji wierszy. Zachowaj oryginalną treść i parametry układu obok wyeksportowanej kopii, a przed przekazaniem sprawdź rzeczywiste środowisko docelowe. Jeśli kształty liter muszą być stałe, przygotuj kopię do przekazania z tekstem zamienionym na krzywe w odpowiednim edytorze źródłowym, zachowując edytowalny tekst w pliku głównym.

[Poradnik o tekście foreignObject](https://perfectvector.com/pl/blog/svg-foreignobject-text) dotyczy etykiet HTML znikających w innym rendererze. Ten przykład od początku buduje natywny tekst. [Poradnik textLength](https://perfectvector.com/pl/blog/svg-textlength-fit) omawia dopasowanie pojedynczego wiersza przez zmianę długości; to inna decyzja niż dodawanie mierzonych wierszy. Skorzystaj z [poradnika wyrównania pionowego](https://perfectvector.com/pl/blog/svg-text-vertical-alignment), jeśli musisz ustawić całą etykietę względem otaczającej grafiki.

## Jasno określ ograniczenia funkcji zawijającej

Dzielenie po białych znakach jest celowo prostą zasadą. Sprowadza powtarzane spacje, tabulatory i jawne znaki nowego wiersza do jednego separatora, a nie obsługuje reguł łamania właściwych językom, dzielenia wyrazów, akapitów dwukierunkowych ani bogato formatowanych fragmentów o mieszanych stylach. Wybierz inny system układu lub rozbuduj i przetestuj zasady, gdy twój tekst wymaga tych funkcji.

Sprawdzenie szerokości nie gwarantuje też, że ozdobne części glifów nie wyjdą poza narysowaną ramkę ani że pięć linii bazowych zmieści się w wybranej wysokości. Obejrzyj widoczne litery w rozmiarze docelowym. Nasz przykład używa poziomych etykiet, stałych atrybutów czcionki i kroku wiersza 30 jednostek; nie przenoś jego wyników pomiarów na inny krój bez ponownego mierzenia.

Jeśli ilustracja istnieje tylko jako PNG lub JPG, a potrzebujesz edytowalnej grafiki, [przygotuj clipart SVG za pomocą PerfectVector](https://perfectvector.com/pl/clipart-to-svg), sprawdź krawędzie i wewnętrzne otwory, a grafikę trzymaj oddzielnie od etykiety. Wpisz treść ponownie jako natywny tekst, zastosuj zasady zawijania i sprawdź złożony eksport. Wektoryzacja przygotowuje ścieżki ilustracji; nie wybiera podziału wierszy ani nie odzyskuje edytowalnych słów z obrysowanych liter. Istniejące dobre ścieżki mogą pozostać bez zmian. [Poradnik wektoryzacji](https://perfectvector.com/pl/blog/what-is-image-vectorization) wyjaśnia ten etap odzyskiwania źródła.

## FAQ

**Czy tspan automatycznie decyduje o miejscu podziału wiersza?**
Ten przykład podejmuje decyzję przez pomiar grup słów, a następnie tworzy po jednym pozycjonowanym tspanie dla każdego zaakceptowanego wiersza. Nie testuje automatycznego zawijania opartego na CSS w SVG 2.

**Co dzieje się ze słowem szerszym od etykiety?**
Funkcja zwraca overlong-word i nie tworzy etykiety. Poszerz obszar, skróć tekst lub zastosuj osobno przetestowaną zasadę dzielenia, zamiast po cichu go przycinać.

**Czy maxLines usuwa pozostałe słowa?**
Nie. Ta zasada odrzuca wynik, gdy wymaga on zbyt wielu wierszy, zgłasza wymaganą liczbę i pozostawia zero tspanów. Aplikacja musi obsłużyć ten stan.

**Czy wyeksportowane wiersze będą wyglądać identycznie z inną czcionką?**
Jawnie zapisane słowa i pozycje pozostają, lecz plik nie osadza czcionki ani nie zamienia glifów na krzywe. Sprawdź dostępną czcionkę i wygląd liter w rzeczywistym środowisku docelowym.

## Źródła

1. [Element tspan w MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/tspan) — pozycjonowane fragmenty tekstu i atrybuty przesunięć wierszy.
2. [Tekst SVG 2 w W3C](https://www.w3.org/TR/SVG2/text.html) — jawne pozycjonowanie i odrębny model automatycznego zawijania.
3. [MDN getComputedTextLength](https://developer.mozilla.org/en-US/docs/Web/API/SVGTextContentElement/getComputedTextLength) — mierzenie rzeczywistego tekstu SVG.
4. [MDN FontFaceSet.ready](https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/ready) — oczekiwanie na wczytanie czcionek i układ.
5. [MDN textContent](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) — wstawianie zwykłego tekstu zamiast znaczników.
6. [MDN XMLSerializer](https://developer.mozilla.org/en-US/docs/Web/API/XMLSerializer/serializeToString) — zapis znaczników SVG z gotowym układem.

***

Dla własnej ilustracji rastrowej [przygotuj edytowalny clipart](https://perfectvector.com/pl/clipart-to-svg), sprawdź kształty i zachowaj plik główny. Dodaj tekst oddzielnie, a przed przekazaniem zweryfikuj zaakceptowane wiersze i widoczny wygląd liter w zapisanym SVG.

## Explore

- [Read the HTML article](https://perfectvector.com/pl/blog/svg-text-wrap-tspan)
- [SVG.js: import grafiki, edycja ścieżki i eksport SVG](https://perfectvector.com/pl/blog/svgjs-import-export-artwork.md)
- [Znaczniki SVG Leaflet: wyrównaj czubek przez iconAnchor](https://perfectvector.com/pl/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)
