Przewodnik po canvas:Rysowanie kształtów
z Mozilla Developer Center, polskiego centrum programistów Mozilli.
UWAGA: Tłumaczenie tej strony nie zostało zakończone.
Może być ona niekompletna lub wymagać korekty.
Chcesz pomóc? | Dokończ tłumaczenie | Sprawdź ortografię | Więcej takich stron...
Spis treści |
[edytuj] Kratki
Zanim zaczniemy rysować, potrzebna nam jest rozmowa nt. kratek w canvas lub koordynacji przestrzenią. Szablon HTML z poprzedniej strony posiada element canvas szeroki na 150 pikseli i wysoki też na 150 pikseli. Narysowaliśmy ten obrazek z domyślną nakładką kratki. Normalnie 1 jednostka w kratce przesyła do 1 piksela na canvas. Początkowa kratka jest pozycjonowana w górnym-lewym rogu (równorzędnie (0,0)). Wszystkie elementy są ułożone relatywnie do początkowego ustawienia. Więc pozycja lewego-górnego rogu niebieskiego kwadratu zaczyna się od x pikseli z lewej strony i y pikseli od góry (równorzędnie (x,y)).Później w przewodniku zobaczymy jak możemy tłumaczyć genezę różnych pozycji, obracać kratki i skalować te zdarzenia. Na teraz trzymajmy się (ustawień) domyślnych.
[edytuj] Rysowanie kształtów
W odróżnieniu od elementu SVG, canvas obsługuje tylko jeden prymitywny kształt - prostokątny. Wszystkie inne kształty muszą być tworzone poprzez łączenie jednej lub większej ilości linii (ścieżek). Na szczęście mamy kolekcję funkcji rysujących linie, które umożliwiają tworzenie bardziej złożonych kształtów.
[edytuj] Prostokąty
Najpierw spójrzmy na prostokąt. Posiada on trzy funkcje, rysujące prostokąty w canvas:
fillRect(x,y,width,height) : Rysuje wypełnienie prostokąta
strokeRect(x,y,width,height) : Rysuje kontur prostokąta
clearRect(x,y,width,height) : Czyści określony obszar i tworzy ten obszar w pełni przeźroczystym
Każda z tych trzech funkcji posiada te same parametry x i y określające pozycję na elemencie canvas (w stosunku do punktu początkowego) w górnym,lewym rogu prostokąta. width i height są oczywistymi parametrami.
Zobaczmy te funkcje w działaniu.
Poniżej znajduje się funkcja draw() z poprzedniej strony, lecz teraz zostały dodane trzy powyższe funkcje.
[edytuj] Przykład prostokątnego kształtu
function draw(){
var canvas = document.getElementById('tutorial');
if (canvas.getContext){
var ctx = canvas.getContext('2d');
ctx.fillRect(25,25,100,100);
ctx.clearRect(45,45,60,60);
ctx.strokeRect(50,50,50,50);
}
}
Rezultat powinien wyglądać podobnie do obrazka, znajdującego się po prawej stronie. Funkcja fillRect rysuje duży czarny kwadrat o rozmiarze 100x100 pikseli. Funkcja clearRect usuwa kwadrat wielkości 60x60 pikseli ze środka i na koniec strokeRect rysuje prostokątną linie zewnętrzną o rozmiarach 50x50 pikseli wewnątrz usuniętego kwadratu.
Na następnych stronach zobaczymy dwie metody alternatywne do funkcji clearRect i także zobaczymy jak zmienić kolor i nadać styl renderowanym kształtom.
Inne funkcje tworzenia ścieżek zobaczymy w następnej sekcji, wszystkie trzy funkcje prostokątów narysują natychmiast canvas.
[edytuj] Rysowanie ścieżek
Aby utworzyć kształty przy użyciu ścieżek, będziemy potrzebowali kilka dodatkowych kroków.
beginPath()
closePath()
stroke()
fill()
Pierwszym krokiem, aby utworzyć ścieżkę jest wywoływana metoda beginPath. Wewnętrznie, ścieżki są magazynowane jako lista pod-ścieżek (linie, łuki, itd.), które razem formują kształt. Za każdym razem ta metoda jest wywoływana, następnie jest resetowana i pozwala zaczynać rysować nowy kształt.
Drugim krokiem jest narysowanie wywołanej metod, które aktualnie określają ścieżki. Zobaczymy je pokrótce.
Trzecim krokiem i opcjonalnym, będzie wywołanie metody closePath. Ta metoda próbuje zamknąć kształt poprzez narysowanie prostej linii z aktualnego punktu do miejsca gdzie zaczęliśmy go rysować. Jeśli kształt został zamknięty lub na liście jest tylko jeden punkt, funkcja nic nie zrobi.
Ostatnim krokiem będzie wywołanie metod stroke i/lub fill. Wywołanie jednej z tych metod aktualnie rysuje kształt canvas. Zastosujmy stroke do narysowania konturu kształtu, podczas gdy zastosujemy fill do namalowania mocnego kształtu/bryły.
Uwaga: Kiedy wywołujemy metodę fill otwarte kształty będą zamykane automatycznie i nie jest potrzebna do tego metoda closePath.
Kod dla narysowania prostego kształtu (trójkąta) będzie wyglądała jak poniżej.
ctx.beginPath(); ctx.moveTo(75,50); ctx.lineTo(100,75); ctx.lineTo(100,25); ctx.fill();
[edytuj] moveTo
Funkcja moveTo jest jedną z bardzo przydatnych i użytecznych funkcji, która nie rysuje niczego, jest ona częścią listy ścieżek o których mowa powyżej. Prawdopodobnie najlepszym sposobem pokazania jej możliwości jest porównanie jej do ołówka lub pióra robiącego kropkę/plamę na kartce papieru i umieszcza się ją następująco.
moveTo(x, y)
Funkcja moveTo pobiera dwa argumenty - x i y, które są koordynowane poprzez nowe punkty startowe.
beginPath punkt, w którym rozpoczynamy jest on ustawiony w równorzędnie (0,0). W większości wypadków użylibyśmy metody moveTo do umieszczenia początkowego punktu gdzieś jeszcze. Też moglibyśmy używać metody moveTo do rysowania niezwiązanych ze sobą ścieżek. Spójrz na buźkę znajdującą się na obrazku z prawej strony. Zaznaczone zostały miejsca, gdzie została użyła metoda moveTo (czerwone linie).
Wypróbuj sam kod załączony poniżej. Wystarczy tylko, że wkleisz poniższy kod do poznanej wcześniej funkcji draw.
[edytuj] Przykład moveTo
ctx.beginPath(); ctx.arc(75,75,50,0,Math.PI*2,true); // Zewnętrzny obwód ctx.moveTo(110,75); ctx.arc(75,75,35,0,Math.PI,false); // Usta (zgodnie z kierunkiem ruchu wskazówek zegara) ctx.moveTo(65,65); ctx.arc(60,65,5,0,Math.PI*2,true); // Lewe oko ctx.moveTo(95,65); ctx.arc(90,65,5,0,Math.PI*2,true); // Prawe oko ctx.stroke();
Uwaga: Usuń metody moveTo, aby zobaczyć połączenia linii.
Uwaga: Dla opisu funkcji arc to są parametry widoczne powyżej.
[edytuj] Linie
Do rysowania prostych linii stosujemy metodę lineTo.
lineTo(x, y)
Ta metoda pobiera dwa argumenty - x i y, które są współrzędnymi punktów końcówek linii. Punkt początkowy zależy od poprzednio narysowanych ścieżek, gdzie punkt końcowy poprzedniej ścieżki jest początkowym dla kolejnej itd. Punkt początkowy może być zmieniony poprzez użycie metody moveTo.
[edytuj] Przykład lineTo
W przykładzie poniżej - są narysowane dwa trójkąty, jeden wypełniony i jeden kontur trójkąta. (Rezultat możemy zobaczyć na obrazku po prawej). Po pierwsze wywołana jest metoda beginPath, aby rozpocząć rysowanie nowego kształtu ścieżki. Potem używamy metody moveTo tworząc punkt początkowy, aż do danej pozycji. Poniżej są narysowane te dwie linie, które tworzą dwa boki trójkąta.
Zwróć uwagę na różnice pomiędzy wypełnionym i konturem trójkąta. Jest to wspomniane powyżej, ponieważ kształty są automatycznie domykane, kiedy ścieżka jest wypełniana. Jeśli byśmy zrobili to dla konturu trójkąta zostałyby narysowane tylko dwie linie , a nie kompletny trójkąt.
// Wypełniony trójkąt ctx.beginPath(); ctx.moveTo(25,25); ctx.lineTo(105,25); ctx.lineTo(25,105); ctx.fill(); // Kontur trójkąta ctx.beginPath(); ctx.moveTo(125,125); ctx.lineTo(125,45); ctx.lineTo(45,125); ctx.closePath(); ctx.stroke();
[edytuj] Łuki
Do rysowania łuków oraz okręgów używamy metody arc. Specyfikacja opisuje także metodę arcTo, która jest obsługiwana przez Safari ale nie jest ona zaimplementowana w obecnych przeglądarkach opartych o silnik Gecko.
arc(x, y, promień, kąt początkowy, kąt końcowy, kierunek ruchu (zgodnie z wskazówkami zegara (false) lub niezgodnie z wskazówkami zegara (true))
Ta metoda pobiera pięć parametrów: x i y są współrzędnymi środka okręgu. Parametr radius odpowiada za promień okręgu. Parametr startAngle i endAngle określa kąt początkowy oraz końcowy łuku w radianach. Kąt początkowy oraz końcowy odnoszą się do osi x. Parametr anticlockwise jest wartością logiczną, która dla stanu: true rysuje łuk w kierunku przeciwnym do wskazówek zegara, w przeciwnym razie (false) zgodnie z kierunkiem wskazówek zegara.
Uwaga: Kąty w funkcji arc są mierzone w radianach, a nie stopniach. Żeby konwertować stopnie do radianów musisz użyć następującej instrukcji JavaScript: var radians = (Math.PI/180)*degrees.
[edytuj] Przykład arc
Kolejny przykład jest trochę bardziej skomplikowany od tego, który widzieliśmy powyżej. Mamy narysować 12 różnorodnych łuków, wszystkie o odmiennych kątach i wypełnieniu. Jeżeli wykonałeś przykład powyżej przedstawiający uśmiechniętą twarz, to bieżący po pierwsze musiałby posiadać bardzo długą listę instrukcji i po drugie, podczas rysowania łuków, potrzebowałbyś znać każdy pojedynczy punkt startowy. Dla pojedynczych łuków 90, 180 i 270 stopni, podobnych do użytych tutaj, nie powinno stanowić większego problemu, lecz dla liczniejszych, bardziej złożonych ten sposób jest za trudny.
Dwie pętle for służą do przeplatania pomiędzy rzędami i kolumnami łuków. Dla każdego punktu początkowego użyjemy nowej ścieżki beginPath. Poniżej zapisaliśmy wszystkie parametry jako zmienne, więc jest to prostsze do odczytania o co w tym chodzi. Normalnie byłaby to tylko jedna instrukcja. Współrzędne x i y powinny być wystarczająco czyste. radius i startAngle są elastyczne. endAngle starts of as 180 degrees (pierwsza kolumna) and is increased with steps of 90 degrees to form a complete circle (ostatnia kolumna). Instrukcja dla parametru clockwise results in the first and third row being drawn as clockwise arcs and the second and fourth row as counterclockwise arcs. Finally, the if statement makes the top half stroked arcs and the bottom half filled arcs.
for (i=0;i<4;i++){
for(j=0;j<3;j++){
ctx.beginPath();
var x = 25+j*50; // współrzędne x
var y = 25+i*50; // współrzędne y
var radius = 20; // promień okręgu
var startAngle = 0; // punkt początkowy okręgu
var endAngle = Math.PI+(Math.PI*j)/2; // punkt końcowy okręgu
var anticlockwise = i%2==0 ? false : true; // clockwise lub anticlockwise
ctx.arc(x,y,radius,startAngle,endAngle, anticlockwise);
if (i>1){
ctx.fill();
} else {
ctx.stroke();
}
}
}
[edytuj] Krzywe Béziera i kwadratowe
Następnym typem dostępnych ścieżek są krzywe Béziera, dostępne jako rozmaite sześcienny i kwadraty. Służą one do rysowania złożonych organicznych kształtów.
quadraticCurveTo(cp1x, cp1y, x, y) // NIE DZIAŁA w Firefoksie 1.5 (zobacz obszar roboczy - poniżej)
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
Różnice pomiędzy tymi krzywymi najlepiej opisuja zawarte po prawej obrazki. Punkty początkowe i końcowe krzywych kwadratowych Béziera to niebieskie punkty i jeden punkt kontrolujący (czerwona kropka), podczas gdy krzywe sześcienne używają dwóch punktów je kontrolujących.
Parametry x i y w obu metodach są równorzędne do punktu końcowego. cp1x i cp1y są równorzędne do pierwszego punktu kontrolnego, i cp2x i cp2y są równorzędne do drugiego punktu kontrolnego.
Zastosowane krzywe Béziera i krzywe kwadratowe mogą być quite challenging, because unlike vector drawing software like Adobe Illustrator, we don't have direct visual feedback as to what we're doing. This makes it pretty hard to draw complex shapes. In the following example, we'll be drawing some simple organic shapes, but if you have the time and, most of all, the patience, much more complex shapes can be created.
W tych przykładach nie ma nic trudnego. W obu przypadkach zobaczymy kolejne rysowane krzywe, które w końcowym efekcie dadzą nam kompletny kształt.
[edytuj] Przykład quadraticCurveTo
// Przykład krzywych kwadratowych ctx.beginPath(); ctx.moveTo(75,25); ctx.quadraticCurveTo(25,25,25,62.5); ctx.quadraticCurveTo(25,100,50,100); ctx.quadraticCurveTo(50,120,30,125); ctx.quadraticCurveTo(60,120,65,100); ctx.quadraticCurveTo(125,100,125,62.5); ctx.quadraticCurveTo(125,25,75,25); ctx.stroke();
Jest to możliwe, aby przekonwertować jakiekolwiek possible to convert any quadratic Bézier curve to a cubic Bézier curve by correctly computing both cubic Bézier control points from the single quadratic Bézier control point, although the reverse is NOT true. An exact conversion of a cubic Bézier curve to a quadratic Bézier curve is only possible if the cubic term is zero, more commonly a subdivision method is used to approximate a cubic Bézier using multiple quadratic Bézier curves.
[edytuj] Przykład bezierCurveTo
// Przykład krzywych Beziera ctx.beginPath(); ctx.moveTo(75,40); ctx.bezierCurveTo(75,37,70,25,50,25); ctx.bezierCurveTo(20,25,20,62.5,20,62.5); ctx.bezierCurveTo(20,80,40,102,75,120); ctx.bezierCurveTo(110,102,130,80,130,62.5); ctx.bezierCurveTo(130,62.5,130,25,100,25); ctx.bezierCurveTo(85,25,75,37,75,40); ctx.stroke();
[edytuj] Firefox 1.5 quadraticCurveTo() bug workaround
There is a bug in the Firefox 1.5 implementation of quadatricCurveTo(). It does NOT draw a quadratic curve, as it is just calling the same cubic curve function bezierCurveTo() calls, and repeating the single quadratic control point (x,y) coordinate twice. For this reason quadraticCurveTo() will yield incorrect results. If you require the use of quadraticCurveTo() you must convert your quadratic Bézier curve to a cubic Bézier curve yourself, so you can use the working bezierCurveTo() method.
var currentX, currentY; // set to last x,y sent to lineTo/moveTo/bezierCurveTo lub quadraticCurveToFixed()
function quadraticCurveToFixed( cpx, cpy, x, y ) {
/*
For the equations below the following variable name prefixes are used:
qp0 is the quadratic curve starting point (you must keep this from your last point sent to moveTo(), lineTo(), or bezierCurveTo() ).
qp1 is the quadatric curve control point (this is the cpx,cpy you would have sent to quadraticCurveTo() ).
qp2 is the quadratic curve ending point (this is the x,y arguments you would have sent to quadraticCurveTo() ).
We will convert these points to compute the two needed cubic control points (the starting/ending points are the same for both
the quadratic and cubic curves.
The equations for the two cubic control points are:
cp0=qp0 and cp3=qp2
cp1 = qp0 + 2/3 *(qp1-qp0)
cp2 = cp1 + 1/3 *(qp2-qp0)
In the code below, we must compute both the x and y terms for each point separately.
cp1x = qp0x + 2.0/3.0*(qp1x - qp0x);
cp1x = qp0y + 2.0/3.0*(qp1y - qp0y);
cp2x = cp1x + (qp2x - qp0x)/3.0;
cp2y = cp1y + (qp2y - qp0y)/3.0;
We will now
a) replace the qp0x and qp0y variables with currentX and currentY (which *you* must store for each moveTo/lineTo/bezierCurveTo)
b) replace the qp1x and qp1y variables with cpx and cpy (which we would have passed to quadraticCurveTo)
c) replace the qp2x and qp2y variables with x and y.
which leaves us with:
*/
var cp1x = currentX + 2.0/3.0*(cpx - currentX);
var cp1x = currentY + 2.0/3.0*(cpy - currentY);
var cp2x = cp1x + (x - currentX)/3.0;
var cp2y = cp1y + (y - currentY)/3.0;
// and now call cubic Bezier curve to function
bezierCurveTo( cp1x, cp1y, cp2x, cp2y, x, y );
currentX = x;
currentY = y;
}
[edytuj] Prostokąty
Oprócz trzech metod, jakie zobaczyliśmy powyżej, które rysują prostokątne kształty w canvas, możemy także posiadać metodę rect, która dodaje prostokątne ścieżki do listy ścieżek.
rect(x, y, width, height)
Ta metoda pobiera cztery argumenty. Parametr x i y definiuje
współrzędne lewego górnego rogu rysowanego prostokąta. Parametry width i height określają jego szerokość i wysokość.
Kiedy ta metoda jest wykonywana, to metoda moveTo jest wywołana automatycznie z parametrami (0,0) (np. przechowuje punkty startowe do jego domyślnej lokalizacji).
[edytuj] Robienie kombinacji
We wszystkich przykładach na tej stronie mieliśmy zastosowany tylko jeden typ funkcji ścieżek na kształt. Jednakże nie ma tu absolutnie żadnych wysokości limitów lub typów ścieżek jakie możemy zastosować. Więc, w ostatnim przykładzie próbowaliśmy kombinować ze wszystkimi funkcjami ścieżek, aby stworzyć bardzo znaną postać z gier.
[edytuj] Przykład
I'm not going to run through this complete script, but the most important things to note are the function roundedRect and the use of the fillStyle property. It can be very usefull and time saving to define your own functions to draw more complex shapes. In this script it would have taken me twice as many lines of code as I have now.
We will look at the fillStyle property in greater depth later in this tutorial. Here I'm using it to change the fill color from the default black, to white, and back again.
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
roundedRect(ctx,12,12,150,150,15);
roundedRect(ctx,19,19,150,150,9);
roundedRect(ctx,53,53,49,33,10);
roundedRect(ctx,53,119,49,16,6);
roundedRect(ctx,135,53,49,33,10);
roundedRect(ctx,135,119,25,49,10);
ctx.beginPath();
ctx.arc(37,37,13,Math.PI/7,-Math.PI/7,true);
ctx.lineTo(31,37);
ctx.fill();
for(i=0;i<8;i++){
ctx.fillRect(51+i*16,35,4,4);
}
for(i=0;i<6;i++){
ctx.fillRect(115,51+i*16,4,4);
}
for(i=0;i<8;i++){
ctx.fillRect(51+i*16,99,4,4);
}
ctx.beginPath();
ctx.moveTo(83,116);
ctx.lineTo(83,102);
ctx.bezierCurveTo(83,94,89,88,97,88);
ctx.bezierCurveTo(105,88,111,94,111,102);
ctx.lineTo(111,116);
ctx.lineTo(106.333,111.333);
ctx.lineTo(101.666,116);
ctx.lineTo(97,111.333);
ctx.lineTo(92.333,116);
ctx.lineTo(87.666,111.333);
ctx.lineTo(83,116);
ctx.fill();
ctx.fillStyle = "white";
ctx.beginPath();
ctx.moveTo(91,96);
ctx.bezierCurveTo(88,96,87,99,87,101);
ctx.bezierCurveTo(87,103,88,106,91,106);
ctx.bezierCurveTo(94,106,95,103,95,101);
ctx.bezierCurveTo(95,99,94,96,91,96);
ctx.moveTo(103,96);
ctx.bezierCurveTo(100,96,99,99,99,101);
ctx.bezierCurveTo(99,103,100,106,103,106);
ctx.bezierCurveTo(106,106,107,103,107,101);
ctx.bezierCurveTo(107,99,106,96,103,96);
ctx.fill();
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(101,102,2,0,Math.PI*2,true);
ctx.fill();
ctx.beginPath();
ctx.arc(89,102,2,0,Math.PI*2,true);
ctx.fill();
}
function roundedRect(ctx,x,y,width,height,radius){
ctx.beginPath();
ctx.moveTo(x,y+radius);
ctx.lineTo(x,y+height-radius);
ctx.quadraticCurveTo(x,y+height,x+radius,y+height);
ctx.lineTo(x+width-radius,y+height);
ctx.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);
ctx.lineTo(x+width,y+radius);
ctx.quadraticCurveTo(x+width,y,x+width-radius,y);
ctx.lineTo(x+radius,y);
ctx.quadraticCurveTo(x,y,x,y+radius);
ctx.stroke();
}







