PerfectVector
Autor: Irene Kim9 min czytania

Canvas Path2D SVG: używaj ścieżek ponownie we właściwym rozmiarze

Używaj danych ścieżek SVG na canvas z właściwą transformacją viewBox, kolorami, otworami i gęstością pikseli. Skorzystaj z autorskiego przykładu i zachowaj edytowalny oryginał SVG.

Na tej stronie

Przekaż atrybut d ścieżki do new Path2D(d), a następnie narysuj ją za pomocą kontekstu canvas. Aby uzyskać taki sam wynik jak w SVG, przenieś również odwzorowanie współrzędnych, ustawienia wypełnienia i obrysu oraz regułę wypełnienia. Konstruktor przyjmuje dane ścieżki; nie importuje dokumentu SVG ani nie stosuje jego viewBox, CSS czy transformacji grup (MDN Path2D constructor).

Ma to znaczenie, gdy własna ikona wygląda poprawnie jako SVG, ale na canvas staje się mała, przesunięta, czarna lub ma wypełniony otwór. Poniższy przykład usuwa te różnice za pomocą dwóch ścieżek i jawnie zdefiniowanej transformacji dopasowującej. Zacznij od istniejącej geometrii wektorowej, jeśli ją masz. Wektoryzacja przydaje się tylko wtedy, gdy grafika, której chcesz ponownie użyć, nadal istnieje w postaci pikseli.

Co zawiera obiekt Path2D

Traktuj dane ścieżki jako opis konturu. Pozostałe instrukcje SVG znajdują się poza tym ciągiem znaków, a kod rysujący na canvas musi dostarczyć te, których potrzebuje.

Dane wejściowe SVGZadanie po stronie canvas
Wartość d ścieżkiUtwórz geometrię Path2D
viewBox i rozmiar wyświetlaniaOblicz transformację rysowania
fill i fill-ruleUstaw fillStyle i przekaż regułę do fill()
transform elementu nadrzędnego lub ścieżkiZastosuj osobno równoważną transformację
CSS, gradienty, maski, tekst i obrazyZaimplementuj wymagane renderowanie lub wybierz inną metodę

Przykład używa wypełnień określonych bezpośrednimi wartościami, dwóch ścieżek będących bezpośrednimi elementami potomnymi i żadnych innych funkcji SVG. Celowo jest na tyle mały, by można go było przeanalizować. Skopiowanie jego pętli wyodrębniającej dane do importera dowolnych plików SVG spowodowałoby utratę dziedziczonych stylów i nieobsługiwanych elementów.

Porównaj źródło z nieskonfigurowanym canvas

Ten autorski emblemat ma zaokrąglony korpus w kolorze morskim, pomarańczowy czubek i kwadratowy otwór. Jego viewBox zaczyna się w punkcie (20, 10), a nie (0, 0). Wewnętrzna podścieżka biegnie w tym samym kierunku co zewnętrzna granica, więc zadeklarowana reguła evenodd ma znaczenie.

Umieść ten SVG i element canvas na stronie HTML:

<svg id="source" xmlns="http://www.w3.org/2000/svg"
     viewBox="20 10 120 80" width="240" height="160">
  <path fill="#126d70" fill-rule="evenodd"
        d="M40 20 H90 Q110 20 110 40 V60 Q110 80 90 80 H40 Q30 80 30 70 V30 Q30 20 40 20 Z M50 35 H80 V60 H50 Z"/>
  <path fill="#f19b38" d="M115 25 L135 50 L115 75 Z"/>
</svg>
<canvas id="result"></canvas>

Jeśli przejdziesz w pętli przez obie wartości d i wywołasz ctx.fill(new Path2D(d)) bez ustawiania czegokolwiek innego, geometria będzie używać współrzędnych canvas oraz domyślnego koloru i reguły wypełnienia. Nie przejmie automatycznie wyglądu SVG. Metoda canvas fill() przyjmuje zarówno ścieżkę, jak i jawną regułę evenodd lub nonzero; domyślną regułą jest nonzero (MDN fill()).

Porównanie w przeglądarce: emblemat SVG, mały czarny wynik renderowania samego Path2D z wypełnionym otworem oraz poprawnie dopasowany dwukolorowy wynik renderowania na canvas
Te same ręcznie utworzone ścieżki w przeglądarce: wzorcowy SVG, samo Path2D i jawne dopasowanie z przywróconymi kolorami oraz regułą evenodd. Tabela przedstawia wymiary bitmapy dla czterech kombinacji rozmiaru i DPR.

Metoda: wyrenderowaliśmy ten ręcznie utworzony SVG w przeglądarce, narysowaliśmy jego dwie ścieżki obiema metodami na canvas i ponownie uruchomiliśmy opublikowany kod na osobnej stronie HTML. Zrzut ekranu przedstawia rzeczywisty wynik z przeglądarki. Pokazuje obsługę współrzędnych i kolorów, a nie konwersję w PerfectVector.

Odwzoruj viewBox na canvas

Domyślnie SVG dopasowuje viewBox do obszaru wyświetlania z zachowaniem proporcji i umieszcza go na środku. SVG coordinate-system specification definiuje to odwzorowanie. W tym płaskim przykładzie odtwórz xMidYMid meet za pomocą jednego współczynnika skali dla obu osi i przesunięcia:

scale = min(cssWidth / viewBoxWidth, cssHeight / viewBoxHeight)
tx = (cssWidth - viewBoxWidth * scale) / 2 - viewBoxX * scale
ty = (cssHeight - viewBoxHeight * scale) / 2 - viewBoxY * scale

Ostatnie składniki kompensują niezerowy początek układu współrzędnych. Przy rozmiarze 240 × 160 skala wynosi 2, tx wynosi -40, a ty wynosi -20. Przy rozmiarze 300 × 120 skala wynosi 1.5; dopasowany viewBox o szerokości 180 pikseli ma po 60 pikseli wolnego miejsca po obu stronach w poziomie. Po uwzględnieniu początku układu współrzędnych źródła przesunięcie wynosi zatem (30, -15).

Użyj poniższego skryptu po powyższym kodzie HTML. Obsługuje on atrybuty ustawione bezpośrednio na ścieżkach w tym przykładzie, przerysowuje cały canvas i oddziela rozmiar CSS od rozmiaru bitmapy:

const source = document.querySelector("#source");
const vb = source.viewBox.baseVal;
const layers = [...source.querySelectorAll("path")].map(el => ({
  path: new Path2D(el.getAttribute("d")),
  fill: el.getAttribute("fill"),
  rule: el.getAttribute("fill-rule") || "nonzero"
}));
 
function draw(canvas, cssWidth, cssHeight, dpr = window.devicePixelRatio || 1) {
  canvas.style.width = `${cssWidth}px`;
  canvas.style.height = `${cssHeight}px`;
  canvas.width = Math.round(cssWidth * dpr);
  canvas.height = Math.round(cssHeight * dpr);
  const ctx = canvas.getContext("2d");
  const scale = Math.min(cssWidth / vb.width, cssHeight / vb.height);
  const tx = (cssWidth - vb.width * scale) / 2 - vb.x * scale;
  const ty = (cssHeight - vb.height * scale) / 2 - vb.y * scale;
  const px = canvas.width / cssWidth;
  const py = canvas.height / cssHeight;
  ctx.setTransform(px * scale, 0, 0, py * scale, px * tx, py * ty);
  for (const layer of layers) {
    ctx.fillStyle = layer.fill;
    ctx.fill(layer.path, layer.rule);
  }
}
 
draw(document.querySelector("#result"), 240, 160);
 

Ustawienie canvas.width i canvas.height czyści i resetuje kontekst. Następnie funkcja ustawia pełną transformację i ponownie rysuje każdą warstwę, więc kolejne wywołania nie kumulują skalowania. Zakłada ona poprawny viewBox o dodatniej szerokości i wysokości oraz dodatnie żądane wymiary.

px i py używają rzeczywistych, zaokrąglonych wymiarów bitmapy. Dzięki temu współrzędne CSS są odwzorowane na przydzielone piksele nawet wtedy, gdy żądana gęstość daje wymiary ułamkowe. Przykład devicePixelRatio w MDN wyjaśnia, dlaczego rozmiar wyświetlania canvas i jego wymiary w pikselach wymagają osobnego traktowania.

Sprawdź osobno rozmiar i gęstość pikseli

Przekazaliśmy jawnie wartości DPR 1 i 2 do tej samej funkcji dla dwóch rozmiarów CSS. Są to kontrolowane wartości gęstości podane na wejściu; nie oznacza to, że przetestowaliśmy dwa fizyczne urządzenia. Współczynnik pikseli urządzenia zgłoszony przez przeglądarkę dla domyślnego wywołania wynosił 2.

Rozmiar CSSArgument DPRZaobserwowana bitmapa canvas
240 × 1601240 × 160
240 × 1602480 × 320
300 × 1201300 × 120
300 × 1202600 × 240

Środek otworu pozostał przezroczysty we wszystkich czterech przypadkach. Większa gęstość zmienia rozdzielczość bitmapy; nie naprawia błędnej transformacji ani nie przywraca brakującej reguły wypełnienia. Przerysuj canvas, gdy zmienią się docelowe wymiary CSS lub współczynnik pikseli urządzenia. Powiększenie wyłącznie za pomocą CSS rozciąga bitmapę, która już znajduje się na canvas.

Jeśli źródło jest niepoprawne jeszcze przed przeniesieniem na canvas, skorzystaj z kontroli rozmiaru SVG i viewBox. Jeśli niepoprawny jest tylko wynik na canvas, najpierw porównaj jego transformację i obsługę kolorów z poprawnie działającym wzorcowym SVG.

Zachowaj źródło i wybierz właściwą metodę importu

Zachowaj oryginał SVG i jego atrybuty wraz z wszelkimi wyodrębnionymi danymi ścieżek. Procedura rysowania z użyciem Path2D przydaje się, gdy kontrolujesz niewielki zestaw konturów i ich stylów. W przypadku złożonego SVG, gdy potrzebujesz tylko wyglądu renderowanego przez przeglądarkę, wczytanie go jako obrazu i narysowanie tego obrazu może pozwolić uniknąć odtwarzania każdej funkcji. Ta metoda również rysuje na bitmapie canvas.

Zapisanie wyniku z canvas jako PNG tworzy obraz rastrowy, nawet jeśli rysowanie zaczęło się od ścieżek wektorowych. API eksportu canvas tworzy obiekt blob z obrazem; obsługa PNG jest wymagana (MDN toBlob()). Zachowaj SVG do późniejszej edycji ścieżek. Podobnie plik SVG może zawierać obraz rastrowy zamiast użytecznych danych ścieżek; diagnostyka osadzonego obrazu pomaga rozróżnić te przypadki.

PerfectVector przydaje się, gdy z Twojej własnej ikony pozostał tylko PNG lub JPG, a potrzebujesz konturów do ponownego użycia. Przekonwertuj ten obraz na SVG, a następnie sprawdź jego sylwetkę, poszczególne kolory i małe otwory, zanim wyodrębnisz ścieżki do canvas. Poprawny oryginał SVG powinien trafić bezpośrednio do etapu przygotowania. Wektoryzacja nie zapewnia odwzorowania viewBox, nie kopiuje CSS ani nie sprawia, że PNG z canvas można edytować jako grafikę wektorową.

Dla całej rodziny ikon ustal wspólną siatkę oraz zasady wypełnienia i obrysu w zestawie UI, zanim podłączysz poszczególne procedury rysowania. Więcej przykładów przygotowania SVG znajdziesz na blogu.

Zanim użyjesz rysunku w swoim interfejsie

  • Sprawdź wzorcowy SVG przy tych samych wymiarach wyświetlania co canvas.
  • Potwierdź początek układu współrzędnych viewBox oraz jego szerokość i wysokość.
  • Przenieś ustawienia wypełnienia i obrysu oraz zamierzoną regułę wypełnienia; sprawdź każdy otwór.
  • Uwzględnij transformacje grup zamiast kopiować samo d.
  • Odczytaj rzeczywiste wartości canvas.width i canvas.height, a następnie przetestuj największy docelowy rozmiar wyświetlania.
  • Zachowaj oryginał SVG i zapewnij nazwę dostępną dla technologii wspomagających lub tekst przy rysunku, odpowiedni do danego interfejsu.

FAQ

Czy Path2D może zaimportować cały plik SVG? Nie. Jego wejściowy ciąg znaków to dane ścieżki SVG, takie jak wartość atrybutu d ścieżki. Struktura dokumentu, odwzorowanie viewBox, style i transformacje grup wymagają osobnej obsługi.

Dlaczego otwór w moim SVG został wypełniony na canvas? Sprawdź regułę wypełnienia i kierunki podścieżek. Ten przykład wymaga evenodd, podczas gdy domyślna reguła wypełnienia canvas to nonzero. Przekaż zamierzoną regułę podczas wypełniania obiektu Path2D.

Czy rysowanie ścieżek SVG na canvas zachowuje możliwość eksportu wektorowego? Ścieżki pozostają w Twoim programie instrukcjami rysowania do ponownego użycia, ale eksport canvas do PNG daje piksele. Zachowaj oryginalny SVG do edycji i przekazywania grafiki w formacie wektorowym.

Źródła

  1. MDN — Path2D constructor — Definiuje ciąg znaków z danymi ścieżki przyjmowany przez konstruktor.
  2. W3C — SVG coordinate systems — Definiuje początek układu współrzędnych viewBox, odwzorowanie na obszar wyświetlania i wyrównanie zachowujące proporcje.
  3. MDN — Canvas fill() — Dokumentuje wypełnianie ścieżek oraz reguły evenodd i nonzero.
  4. MDN — devicePixelRatio — Wyjaśnia rozmiar wyświetlania w CSS i rozdzielczość bitmapy canvas.
  5. MDN — Canvas toBlob() — Dokumentuje eksport obrazu rastrowego z canvas.

Masz tylko ikonę rastrową? Przygotuj jej SVG w PerfectVector, sprawdź kontur i otwory, a następnie porównaj rysunek na canvas z tym SVG przy rzeczywistych rozmiarach używanych w Twoim interfejsie.

Więcej z bloga

Zacznij od czystszego SVG,
który łatwiej edytować