Ostatnio przyszło mi napisać o wykorzystaniu grafiki wektorowej w prostej animacji. Tym razem również o grafice na stronach, ale rastrowej, a jeśli mowa o grafice rastrowej to nie sposób nie zatrzymać się przy kanwach. Element canvas umożliwia nam tworzenie grafiki rastrowej na stronach internetowych i jest relatywnie nowym elementem. To co daje mu przewagę nad prozaicznym elementem img to możliwość dynamicznego manipulowania obrazem i bynajmniej nie chodzi tu o animacje do jakich przyzwyczaiły nas, niestety nadal popularne, gify (zwłaszcza ta część Internetu bogata w koty i drogowe perypetie naszych sąsiadów za wschodniej granicy). Element canvas daje nam możliwość manipulowania obrazem w czasie rzeczywistym dzięki wykorzystaniu języka JavaScript do tego stopnia by tworzyć za jego pomocą proste gry. Nie od razu Rzym zbudowano, więc skupimy się na podstawach.
HTML
Tworząc element canvas dobrze jest zdefiniować za pomocą atrybutów stałą szerokość i wysokość obrazu. Nie da się tego zrobić za pomocą CSS, w ten sposób będziemy jedynie skalować obraz.
1 | <canvas width="150" height="150"></canvas> |
JavaScript
By zacząć rysowanie musimy pobrać kontekst (nam starczy dwu wymiarowy) elementu canvas. Jak widać na poniższym kodzie, stworzenie rozwiązania zastępczego dla przeglądarek nie wspierających canvas (IE 8.0 i starsze) jest dziecinnie proste i spokojnie obejdziemy się bez Modernizr.
1 2 3 4 5 6 7 8 9 10 11 12 13 | canvas_line = document.getElementById('line'); if (canvas_line.getContext) { var c = canvas_line.getContext('2d'); c.beginPath(); c.moveTo(20,20); c.lineTo(130,130); c.moveTo(130,20); c.lineTo(20,130); c.stroke(); } else { //rozwiązanie zastępcze } |
UWAGA: Poniższe funkcje nie są metodami, ani właściwościami obiektu window, a kontekstu kanwy, więc muszą być używane wg. schematu: kontekst.metoda() lub kontekst.wlasciwosc.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //linie c.beginPath(); c.moveTo(20,20); c.lineTo(130,130); c.moveTo(130,20); c.lineTo(20,130); c.stroke(); //trójkąt c.beginPath(); c.moveTo(75,20); c.lineTo(130,130); c.lineTo(20,130); c.closePath(); c.stroke(); |
beginPath()
Rozpoczęcie ścieżki.
closePath()
Zamknięcie ścieżki.
moveTo(x, y)
Przenosi nas na wskazany punkt na kanwie. Warto w tym momencie zaznaczyć, że punkt o współrzędnych (0,0) na kanwie znajduje się w lewym górnym rogu.
- x – współrzędna pozioma na kanwie
- y – współrzędna pionowa na kanwie
lineTo(x, y)
Tworzy linie z aktualnego punktu do punktu wskazanego przez parametry.
- x – współrzędna pozioma na kanwie
- y – współrzędna pionowa na kanwie
stroke()
Rysuje aktualną ścieżkę.
1 2 | c.rect(20,20,110,110); c.stroke(); |
rect(x, y, szerokosc, wyskokosc)
Rysuje prostokąt.
- x – współrzędna pozioma na kanwie lewego, górnego wierzchołka prostokąta
- y – współrzędna pionowa na kanwie lewego, górnego wierzchołka prostokąta
- szerokosc – długość prostokąta
- wysokosc – długość prostokąta
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | function degToRad(deg){ return deg*Math.PI/180; } //okrąg c.beginPath(); c.arc(75,75,55,0,degToRad(360)); c.stroke(); //półokrąg c.beginPath(); c.arc(75,75,55,degToRad(90),degToRad(270)); c.closePath(); c.stroke(); //łuk c.beginPath(); c.moveTo(20,20); c.lineTo(40,20); c.arcTo(130,20,130,110,90); c.lineTo(130,130); c.stroke(); |
arc(x, y, r, start, stop, [odwrotniedowskazowek])
Rysuje łuk.
- x – środek okręgu na którym zbudowana łuk
- y – środek okręgu na którym zbudowana łuk
- start – miejsce na okręgu podawane w radianach, od którego zaczyna się łuk (za zero przyjmuje się punkt wysunięty maksymalnie na prawo)
- stop – miejsce na okręgu podawane w radianach, na którym kończy się łuk
- odwrotniedowskazowek – opcjonalny parametr boolowski określający rysowanie odwrotnie z ruchem wskazówek zegara, w praktyce ustawiony na prawdę „odwraca” łuk.
1 2 3 4 5 6 | c.beginPath(); c.moveTo(20,20); c.lineTo(40,20); c.quadraticCurveTo(130,20,130,110); c.lineTo(130,130); c.stroke(); |
quadraticCurveTo(xpktk, ypktk, x, y)
Rysuje krzywą Beziera drugiego stopnia (kwadratową).
- xpktk – współrzędna pozioma punktu kontrolnego
- ypktk – współrzędna pionowa punktu kontrolnego
- x – współrzędna pozioma zakończenia krzywej
- y – współrzędna pionowa zakończenia krzywej
1 2 3 4 5 6 | c.beginPath(); c.moveTo(20,20); c.lineTo(40,20); c.bezierCurveTo(130,20,20,130,130,130); c.lineTo(130,130); c.stroke(); |
bezierCurveTo(xpktk1, ypktk1, xpktk2, ypktk2, x, y)
Rysuje krzywą Beziera trzeciego stopnia (sześcienną).
- xpktk1 – współrzędna pozioma 1. punktu kontrolnego
- ypktk1 – współrzędna pionowa 1. punktu kontrolnego
- xpktk2 – współrzędna pozioma 2. punktu kontrolnego
- ypktk2 – współrzędna pionowa 2. punktu kontrolnego
- x – współrzędna pozioma zakończenia krzywej
- y – współrzędna pionowa zakończenia krzywej
Przyznacie jednak, że kształty, które rysowaliśmy do tej pory wyglądają dość ascetycznie. Teraz nadamy im trochę blasku.
1 2 3 4 5 6 7 8 9 | c.beginPath(); c.moveTo(20,20); c.lineTo(130,130); c.moveTo(130,20); c.lineTo(20,130); c.strokeStyle = "#3F8FFF"; c.lineWidth = 10; c.lineCap = "round"; c.stroke(); |
Nie jest to morze dzieło sztuki, no ale jak to mówią: „Lepszy wróbel w garści…”
strokeStyle
Pozwala nadać kolor obrysowi
lineWidth
Pozwala nadać obrysowi grubość wyrażoną w pikselach. Warto zaznaczyć, że na kanwach kontur jest wyśrodkowany. Oznacza to tyle, że w przypadku konturu o grubości 10px, 5px będzie „na zewnątrz” i 5px „wewnątrz” figury.
lineCap
Pozwala nadać styl zakończenia linii. Może przyjąć jedną z trzech wartości: butt (domyślna), round, squared.
1 2 3 4 5 6 7 | c.rect(20,20,110,110); c.fillStyle = "#FFFB3F"; c.strokeStyle = "#3F8FFF"; c.lineWidth = 10; c.lineJoin = "round"; c.fill(); c.stroke(); |
fillStyle
Pozwala nadać styl wypełnienia. Może to być jednolity kolor, gradient lub wzór (createPattern()).
lineJoin
Pozwala określić sposób w jaki łączą się linie. Może przyjąć jedną z trzech wartości: miter (domyślna), round, bevel.
fill()
Wypełnia aktualną ścieżkę.
1 2 3 4 5 6 7 8 9 10 11 12 | var gradient = c.createLinearGradient(20,20,130,130); gradient.addColorStop(0, "#3F8FFF"); gradient.addColorStop(0.3, "#3F8FFF"); gradient.addColorStop(0.7, "#FFFB3F"); gradient.addColorStop(1, "#FFFB3F"); c.beginPath(); c.moveTo(75,20); c.lineTo(130,130); c.lineTo(20,130); c.closePath(); c.fillStyle = gradient; c.fill(); |
createLinearGradient(x, y, xk, yk)
Tworzy obiekt gradientu liniowego. Istnieje też gradient kołowy.
- x – współrzędna pozioma początkowego punktu
- y – współrzędna pionowa początkowego punktu
- xk – współrzędna pozioma końcowego punktu
- yk – współrzędna pionowa końcowego punktu
addColorStop(n, color)
Dodaje kolor do gradientu.
- n – miejsce dodania nowego koloru, podawane w procentach, gdzie 1 = 100%, a 0 = 0%
- color – nowo dodawany kolor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | c.save(); c.beginPath(); c.shadowColor = "#3F8FFF"; c.shadowBlur = 30; c.shadowOffsetX = 10; c.shadowOffsetY = -10; c.fillStyle = "#FFFB3F"; c.arc(75,75,55,0,degToRad(360)); c.fill(); c.closePath(); c.restore(); c.beginPath(); c.fillStyle = "#3F8FFF"; c.arc(75,75,20,0,degToRad(360)); c.fill(); c.closePath(); |
shadowColor
Pozwala ustawić kolor cienia.
shadowBlur
Pozwala ustawić rozmazanie cienia.
shadowOffsetX
Współrzędna x przesunięcia cienia.
shadowOffsetY
Współrzędna y przesunięcia cienia.
save()
Zapisuje aktualny stan kontekstu.
restore()
Przywraca ostatnio zapisany stan kontekstu. Zasado kolejkowania stanów kontekstu działa na zasadzie stosu, buforu LIFO. Oznacza to tyle, że pierwszy zapisany stan kontekstu będzie ostatnim stanem, który będzie można odzyskać. Łatwo to sobie wyobrazić na przykładzie talerzy ułożonych jeden na drugim. Pierwszy talerz w stosie jest na samym spodzie więc aby do wyjąć musimy zdjąć wszystkie talerze, które znajdują się nad nim, a talerz ułożony jako ostatni zdejmiemy najszybciej.
1 2 3 | var img = document.createElement("img"); img.setAttribute('src', 'http://sciezka/do/zdjecia.jpg'); c.drawImage(img,10,10); |
drawImage(img, x, y)
Rysuję obraz na kanwie.
- img – obiekt obrazu
- x – współrzędna pozioma lewego górnego rogu obrazka
- y – współrzędna pionowa lewego dolnego rogu obrazka
1 2 3 4 5 6 | c.beginPath(); c.font = "italic 28px Arial"; c.textBaseline = "middle"; c.textAlign = "center"; c.fillText("Leonardo da Vinci, La Gioconda", 231,75); c.closePath(); |
font
Właściwość pozwalająca określić czcionkę, jej styl i wielkość. Jej składnia jest identyczna, jak ta którą znamy z CSS.
textBaseline
Pozwala na ustawienie linii pisma (ang. baseline). Przyjmuje jedną z sześciu wartości: alphabetic (domyślna), top, hanging, middle, ideographic i bottom.
textAlign
Pozwala na ustawienie wyrównania tekstu. Przyjmuje jedną z pięciu wartości: start (domyślna), end, center, left, right.
fillText(tekst, x, y, [maxdl])
Rysuje tekst na kanwie. Istnieje też metoda strokeText(), która przyjmuje te same parametry jednak zamiast wypełniać tekst kolorem zaznacza jego kontur.
- tekst – Ciąg znaków, który ma pojawić się na kanwie
- x – współrzędna pozioma
- y – współrzędna pionowa
- maxdl – maksymalna długość tekstu, ten parametr jest opcjonalny
> jest relatywnie nowym elementem.
słowo-klucz: relatywnie ;) jak mnie pamięć nie myli, jego historia sięga roku 2007, więc nie tak znowu blisko (jak na Internet oczywiście)
>Jak widać na poniższym kodzie, stworzenie rozwiązania zastępczego dla przeglądarek nie wspierających canvas (IE 8.0 i starsze) jest dziecinnie proste i spokojnie obejdziemy się bez Modernizr.
Modernizr nigdy nie służył do stworzenia rozwiązania zastępczego – on tylko służy do wykrywania feature’ów. od rozwiązań zastępczych są polyfille, takie jak FlashCanvas (jedna z niewielu rzeczy zbudowanych na Flashu, które polecam). tak, wiem, czepiam się ;)
jeśli mówimy o canvas bez różnych upraszczaczy (polecam CanvasQuery – znawcy innego JS-owego produktu z Query w nazwie powinni się szybko odnaleźć ;) polecam tym bardziej, że to produkt polski), to warto wspomnieć o takim fajnym tricku jak przeskalowanie całego układu współrzędnych kanwy. wówczas zamiast 1567 od topu będziemy mieli 0.5. często na takich liczbach/proporcjach lepiej się operuje (przynajmniej mnie).
O rozwiązaniach zastępczych i Modernizr było w kontekście prostego wykrycia braku tej funkcjonalności. Wziąłem to za pewniak, abstrahując to nawet nie bardzo mnie te narzędzie do siebie przekonuje ze względu na parę bugów, które i tak finalnie trzeba samemu poprawiać i/lub szukać rozwiązań w sieci.
Ja osobiście zacząłem używać KineticJS.