Moje tło
O <canvas> dowiedziałem się w 2006 roku, gdy ukazała się przeglądarka Firefox 2.0. Artykuł na stronie Ajaxian opisujący macierz przekształceń zainspirował mnie do stworzenia mojej pierwszej aplikacji internetowej <canvas> – Color Sphere (2007). Zanurzyłem się w świecie kolorów i prymitywów graficznych, co zainspirowało mnie do stworzenia Sketchpada (2007–2008) – aplikacji „lepszej niż Paint” działającej w przeglądarce.
Te eksperymenty doprowadziły ostatecznie do utworzenia startupu Mugtug z moim wieloletnim przyjacielem Charlesem Pritchardem. Tworzymy Darkroom w HTML5<canvas>. Darkroom to aplikacja do udostępniania zdjęć, która nie powoduje utraty jakości. Łączy ona możliwości filtrów opartych na pikselach z typografią i rysowaniem wektorowym.
Wprowadzenie
<canvas> daje programistom JavaScriptu pełną kontrolę nad kolorami, wektorami i pikselami na ekranie, czyli nad wizualną stroną monitora.
Poniższe przykłady dotyczą obszaru w <canvas>, który nie cieszy się dużym zainteresowaniem, czyli tworzenia efektów tekstowych. Różnorodność efektów tekstowych, które można utworzyć w <canvas>, jest tak duża, jak tylko można sobie wyobrazić – te wersje demonstracyjne pokazują tylko część możliwości. Chociaż w tym artykule zajmujemy się „tekstem”, opisane metody można zastosować do dowolnych obiektów wektorowych, aby tworzyć atrakcyjne elementy wizualne w grach i innych aplikacjach:
- Cienie tekstu w
<canvas>. - Efekty tekstowe podobne do CSS w
<canvas>, tworzenie masek przycinających, znajdowanie danych w<canvas>i używanie właściwości cienia. - Neonowa tęcza, odbicie zebry – efekty łańcuchowe.
- Efekty tekstowe podobne do tych z Photoshopa w
<canvas>– przykłady użycia globalCompositeOperation, createLinearGradient, createPattern. - Cienie wewnętrzne i zewnętrzne w
<canvas> - Przedstawienie mało znanej funkcji: użycie nawijania zgodnie z ruchem wskazówek zegara i przeciwnie do niego, aby utworzyć odwrotność cienia (cień wewnętrzny).
- Efekt generatywny – kosmos.
- Generatywny efekt tekstowy w
<canvas>wykorzystujący cykl kolorów hsl() iwindow.requestAnimationFramedo tworzenia wrażenia ruchu.
Cienie tekstu w obszarze roboczym
Jednym z moich ulubionych dodatków do specyfikacji CSS3 (obok border-radius, web-gradients i innych) jest możliwość tworzenia cieni. Ważne jest, aby zrozumieć różnice między cieniami CSS i <canvas>, a w szczególności:
CSS używa 2 metod: box-shadow w przypadku elementów blokowych, takich jak div, span itp., oraz text-shadow w przypadku treści tekstowych.
<canvas> ma 1 rodzaj cienia, który jest używany we wszystkich obiektach wektorowych: ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.strokeText itd.
Aby utworzyć cień w <canvas>, użyj tych 4 właściwości:
- ctx.shadowColor = "red" // string
- Kolor cienia. Można używać wartości RGB, RGBA, HSL, HEX i innych.
- ctx.shadowOffsetX = 0; // integer
- Odległość cienia w poziomie względem tekstu.
- ctx.shadowOffsetY = 0; // integer
- Odległość cienia w pionie względem tekstu.
- ctx.shadowBlur = 10; // integer
- Efekt rozmycia cienia. Im większa wartość, tym większe rozmycie.
Na początek zobaczmy, jak <canvas> może emulować efekty CSS.
Wyszukując w Grafice Google hasło „css text-shadow”, znaleźliśmy kilka świetnych wersji demonstracyjnych, które możemy naśladować: Line25, Stereoscopic i Shadow 3D.
Efekt stereoskopowy 3D (więcej informacji znajdziesz w artykule Anaglif) to przykład prostego wiersza kodu, który można wykorzystać na wiele sposobów. Za pomocą tego wiersza kodu CSS możemy stworzyć iluzję głębi, gdy patrzymy przez okulary 3D z czerwonym i niebieskim filtrem (takie, jakie dostaje się w kinie na filmach 3D):
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
Podczas konwertowania tego ciągu znaków na <canvas> należy zwrócić uwagę na 2 rzeczy:
- Nie ma rozmycia cienia (trzecia wartość), więc nie ma powodu, aby faktycznie uruchamiać cień, ponieważ fillText dałby takie same wyniki:
var text = "Hello world!"
ctx.fillStyle = "#000"
ctx.fillText(text, -7, 0);
ctx.fillStyle = "red"
ctx.fillText(text, 0, 0);
ctx.fillStyle = "cyan"
ctx.fillText(text, 7, 0);</pre>
- Jednostki EM nie są obsługiwane w
<canvas>, więc trzeba je przekonwertować na piksele. Współczynnik konwersji między jednostkami PT, PC, EM, EX, PX itd. możemy znaleźć, tworząc w DOM element o tych samych właściwościach czcionki i ustawiając szerokość na format, który ma być mierzony. Na przykład, aby przechwycić konwersję EM -> PX, zmierzylibyśmy element DOM z właściwością „height: 1em”. Wynikowa wartość offsetHeight określałaby, ile pikseli przypada na każdy em.
var font = "20px sans-serif"
var d = document.createElement("span");
d.style.cssText = "font: " + font + " height: 1em; display: block"
// the value to multiply PX 's by to convert to EM 's
var EM2PX = 1 / d.offsetHeight;</pre>
Zapobieganie mnożeniu kanału alfa
W bardziej złożonym przykładzie, takim jak efekt neonu na stronie Line25, do prawidłowego emulowania efektu należy użyć właściwości shadowBlur. Efekt neonu opiera się na wielu cieniach, więc pojawia się problem: w <canvas> każdy obiekt wektorowy może mieć tylko 1 cień. Aby narysować kilka cieni, musisz narysować kilka wersji tekstu na sobie.
Powoduje to mnożenie kanału alfa, a w efekcie poszarpane krawędzie.
Próbowałem uruchomić ctx.fillStyle = "rgba(0,0,0,0)" lub "transparent", aby ukryć tekst i wyświetlić cień, ale bezskutecznie. Ponieważ cień jest wielokrotnością wartości alfa fillStyle, nigdy nie może być bardziej nieprzezroczysty niż fillStyle.
Na szczęście można to obejść. Możemy narysować cień przesunięty względem tekstu, zachowując odstęp między nimi (aby się nie nakładały), i w ten sposób ukryć tekst z boku ekranu:
var text = "Hello world!"
var blur = 10;
var width = ctx.measureText(text).width + blur * 2;
ctx.textBaseline = "top"
ctx.shadowColor = "#000"
ctx.shadowOffsetX = width;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -width, 0);
Przycinanie wokół bloku tekstu
Aby to nieco uporządkować, możemy uniemożliwić rysowanie fillText (jednocześnie zezwalając na rysowanie cienia), dodając ścieżkę przycinania.
Aby utworzyć ścieżkę przycinania wokół tekstu, musimy znać wysokość tekstu (nazywaną „wysokością em”, która historycznie odpowiada wysokości litery „M” na prasie drukarskiej) oraz szerokość tekstu.
Szerokość możemy uzyskać za pomocą ctx.measureText().width, ale ctx.measureText().height nie istnieje.
Na szczęście dzięki sztuczkom z CSS (więcej sposobów na naprawienie starszych implementacji <canvas> za pomocą pomiarów CSS znajdziesz w artykule Typographic Metrics) możemy znaleźć wysokość tekstu, mierząc offsetHeight elementu <span> o tych samych właściwościach czcionki:
var d = document.createElement("span");
d.font = "20px arial"
d.textContent = "Hello world!"
var emHeight = d.offsetHeight;
Następnie możemy utworzyć prostokąt, który będzie używany jako ścieżka przycinająca, obejmująca „cień” i usuwająca kształt zastępczy.
ctx.rect(0, 0, width, emHeight);
ctx.clip();
Podsumowując i optymalizując – jeśli cień nie jest rozmyty, można użyć fillText, aby uzyskać ten sam efekt, co pozwala uniknąć konfigurowania maski przycinającej:
var width = ctx.measureText(text).width;
var style = shadowStyles[text];
// add a background to the current effect
ctx.fillStyle = style.background;
ctx.fillRect(0, offsetY, ctx.canvas.width, textHeight - 1)
// parse text-shadows from css
var shadows = parseShadow(style.shadow);
// loop through the shadow collection
var n = shadows.length; while(n--) {
var shadow = shadows[n];
var totalWidth = width + shadow.blur * 2;
ctx.save();
ctx.beginPath();
ctx.rect(offsetX - shadow.blur, offsetY, offsetX + totalWidth, textHeight);
ctx.clip();
if (shadow.blur) { // just run shadow (clip text)
ctx.shadowColor = shadow.color;
ctx.shadowOffsetX = shadow.x + totalWidth;
ctx.shadowOffsetY = shadow.y;
ctx.shadowBlur = shadow.blur;
ctx.fillText(text, -totalWidth + offsetX, offsetY + metrics.top);
} else { // just run pseudo-shadow
ctx.fillStyle = shadow.color;
ctx.fillText(text, offsetX + (shadow.x||0), offsetY - (shadow.y||0) + metrics.top);
}
ctx.restore();
}
// drawing the text in the foreground
if (style.color) {
ctx.fillStyle = style.color;
ctx.fillText(text, offsetX, offsetY + metrics.top);
}
// jump to next em-line
ctx.translate(0, textHeight);
Ponieważ nie chcesz wpisywać wszystkich tych poleceń <canvas> ręcznie, w kodzie źródłowym wersji demonstracyjnej umieściłem prosty parser cienia tekstu. Dzięki temu możesz przekazywać mu polecenia CSS i generować polecenia <canvas>.
Teraz nasze elementy <canvas> mogą być powiązane z całą gamą stylów.
Te same efekty cienia można stosować do dowolnego obiektu wektorowego, od czcionek internetowych po złożone kształty importowane z plików SVG, generatywne kształty wektorowe itd.
Przerwa (dygresja na temat przesuwania pikseli)
Podczas pisania tej sekcji artykułu zainteresował mnie przykład obrazu stereoskopowego. Jak trudno byłoby utworzyć efekt ekranu kinowego 3D za pomocą <canvas> i 2 zdjęć zrobionych z nieco różnych perspektyw? Najwyraźniej nie za ciężko. Poniższe jądro łączy kanał czerwony pierwszego obrazu (data) z kanałem cyjanowym drugiego obrazu (data2):
data[i] = data[i] * 255 / 0xFF;
data[i+1] = 255 * data2[i+1] / 0xFF;
data[i+2] = 255 * data2[i+2] / 0xFF;
Teraz wystarczy tylko przykleić 2 iPhone'y do czoła, kliknąć „Nagraj film” w tym samym czasie i możemy tworzyć własne filmy 3D w HTML5. Czy są chętni?
Neonowa tęcza, odbicie zebry – efekty łańcuchowe
Łączenie wielu efektów w <canvas> może być proste, ale wymaga podstawowej wiedzy o globalCompositeOperation (GCO). Aby porównać te operacje z GIMP-em (lub Photoshopem): w <canvas> jest 12 operacji GCO, darker i lighter można traktować jako tryby mieszania warstw, a pozostałe 10 operacji jest stosowanych do warstw jako maski alfa (jedna warstwa usuwa piksele drugiej warstwy). Właściwość globalCompositeOperation łączy „warstwy” (lub w naszym przypadku ciągi kodu) na nowe, ciekawe sposoby:
Wykres globalCompositeOperation pokazuje tryby GCO w działaniu. Wykorzystuje on dużą część spektrum kolorów i wiele poziomów przezroczystości alfa, aby szczegółowo pokazać, czego można się spodziewać. Jeśli chcesz zapoznać się z opisami tekstowymi, polecam dokumentację globalCompositeOperation Mozilli. Więcej informacji o tym, jak działa ta operacja, znajdziesz w artykule Compositing Digital Images autorstwa Portera Duffa.
Mój ulubiony tryb to globalCompositeOperation="lighter". Tryb Lighter (Jaśniej) miesza dołączone piksele podobnie jak światło. Gdy czerwone, zielone i białe światło mają pełną intensywność, widzimy białe światło. To ciekawa funkcja, którą warto wypróbować, zwłaszcza gdy <canvas> ma ustawioną niską wartość globalAlpha, co umożliwia dokładniejszą kontrolę i uzyskanie gładszych krawędzi. Lighter jest wykorzystywany na wiele sposobów. Moim ulubionym jest ostatnio kreator tła pulpitu w HTML5, który można znaleźć na stronie http://weavesilk.com/.
Jedna z moich wersji demonstracyjnych, Breathing Galaxies (JS1k), również korzysta z trybu „lighter” – rysując wzory z tych 2 przykładów, zaczynasz dostrzegać, jaki efekt daje ten tryb.
Obsługa globalCompositeOperation przez przeglądarkę.
Efekt neonowo-tęczowego migotania
W tej prezentacji uzyskamy neonowo-tęczowy blask z drgającym konturem, podobny do tego, który można uzyskać w Photoshopie, łącząc efekty za pomocą globalCompositeOperation (source-in, lighter i darker).
Ta wersja demonstracyjna jest rozwinięciem wersji „Cienie tekstu w <canvas>” i wykorzystuje tę samą strategię oddzielania cienia od tekstu (patrz poprzednia sekcja):
function neonLightEffect() {
var text = "alert('"+String.fromCharCode(0x2665)+"')";
var font = "120px Futura, Helvetica, sans-serif";
var jitter = 25; // the distance of the maximum jitter
var offsetX = 30;
var offsetY = 70;
var blur = getBlurValue(100);
// save state
ctx.save();
ctx.font = font;
// calculate width + height of text-block
var metrics = getMetrics(text, font);
// create clipping mask around text-effect
ctx.rect(offsetX - blur/2, offsetY - blur/2,
offsetX + metrics.width + blur, metrics.height + blur);
ctx.clip();
// create shadow-blur to mask rainbow onto (since shadowColor doesn't accept gradients)
ctx.save();
ctx.fillStyle = "#fff";
ctx.shadowColor = "rgba(0,0,0,1)";
ctx.shadowOffsetX = metrics.width + blur;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -metrics.width + offsetX - blur, offsetY + metrics.top);
ctx.restore();
// create the rainbow linear-gradient
var gradient = ctx.createLinearGradient(0, 0, metrics.width, 0);
gradient.addColorStop(0, "rgba(255, 0, 0, 1)");
gradient.addColorStop(0.15, "rgba(255, 255, 0, 1)");
gradient.addColorStop(0.3, "rgba(0, 255, 0, 1)");
gradient.addColorStop(0.5, "rgba(0, 255, 255, 1)");
gradient.addColorStop(0.65, "rgba(0, 0, 255, 1)");
gradient.addColorStop(0.8, "rgba(255, 0, 255, 1)");
gradient.addColorStop(1, "rgba(255, 0, 0, 1)");
// change composite so source is applied within the shadow-blur
ctx.globalCompositeOperation = "source-atop";
// apply gradient to shadow-blur
ctx.fillStyle = gradient;
ctx.fillRect(offsetX - jitter/2, offsetY,
metrics.width + offsetX, metrics.height + offsetY);
// change composite to mix as light
ctx.globalCompositeOperation = "lighter";
// multiply the layer
ctx.globalAlpha = 0.7
ctx.drawImage(ctx.canvas, 0, 0);
ctx.drawImage(ctx.canvas, 0, 0);
ctx.globalAlpha = 1
// draw white-text ontop of glow
ctx.fillStyle = "rgba(255,255,255,0.95)";
ctx.fillText(text, offsetX, offsetY + metrics.top);
// created jittered stroke
ctx.lineWidth = 0.80;
ctx.strokeStyle = "rgba(255,255,255,0.25)";
var i = 10; while(i--) {
var left = jitter / 2 - Math.random() * jitter;
var top = jitter / 2 - Math.random() * jitter;
ctx.strokeText(text, left + offsetX, top + offsetY + metrics.top);
}
ctx.strokeStyle = "rgba(0,0,0,0.20)";
ctx.strokeText(text, offsetX, offsetY + metrics.top);
ctx.restore();
};
Efekt odbicia zebry
Efekt odbicia zebry został zainspirowany doskonałym artykułem na stronie WebDesignerWall o tym, jak uatrakcyjnić stronę za pomocą CSS. To rozwinięcie poprzedniego pomysłu, które tworzy „odbicie” tekstu – takie jak w iTunes. Efekt łączy fillColor (biały), createPattern (zebra.png) i linearGradient (połysk). Pokazuje to możliwość zastosowania wielu typów wypełnienia do każdego obiektu wektorowego:
function sleekZebraEffect() {
// inspired by - http://www.webdesignerwall.com/demo/css-gradient-text/
var text = "Sleek Zebra...";
var font = "100px Futura, Helvetica, sans-serif";
// save state
ctx.save();
ctx.font = font;
// getMetrics calculates:
// width + height of text-block
// top + middle + bottom baseline
var metrics = getMetrics(text, font);
var offsetRefectionY = -20;
var offsetY = 70;
var offsetX = 60;
// throwing a linear-gradient in to shine up the text
var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
gradient.addColorStop(0.1, '#000');
gradient.addColorStop(0.35, '#fff');
gradient.addColorStop(0.65, '#fff');
gradient.addColorStop(1.0, '#000');
ctx.fillStyle = gradient
ctx.fillText(text, offsetX, offsetY + metrics.top);
// draw reflected text
ctx.save();
ctx.globalCompositeOperation = "source-over";
ctx.translate(0, metrics.height + offsetRefectionY)
ctx.scale(1, -1);
ctx.font = font;
ctx.fillStyle = "#fff";
ctx.fillText(text, offsetX, -metrics.height - offsetY + metrics.top);
ctx.scale(1, -1);
// cut the gradient out of the reflected text
ctx.globalCompositeOperation = "destination-out";
var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
gradient.addColorStop(0.0, 'rgba(0,0,0,0.65)');
gradient.addColorStop(1.0, '#000');
ctx.fillStyle = gradient;
ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height);
// restore back to original transform state
ctx.restore();
// using source-atop to allow the transparent .png to show through to the gradient
ctx.globalCompositeOperation = "source-atop";
// creating pattern from <image> sourced.
ctx.fillStyle = ctx.createPattern(image, 'repeat');
// fill the height of two em-boxes, to encompass both normal and reflected state
ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height * 2);
ctx.restore();
};
Cienie wewnętrzne i zewnętrzne w obszarze roboczym
Specyfikacja <canvas> nie porusza kwestii cieni „wewnętrznych” i „zewnętrznych”. Na pierwszy rzut oka może się wydawać,
że „wewnętrzny” cień nie jest obsługiwany. To nieprawda.
Włączenie tej funkcji jest nieco trudniejsze ;) Jak zaproponowano w ostatnim poście użytkownika F1LT3R, możesz tworzyć wewnętrzne cienie, korzystając z unikalnych właściwości reguł nawijania zgodnie z ruchem wskazówek zegara i przeciwnie do ruchu wskazówek zegara. Aby to zrobić, utwórz „cień wewnętrzny”, rysując prostokąt kontenera, a następnie, używając przeciwnych reguł nawijania, narysuj wycięty kształt, tworząc odwrotność kształtu.
Poniższy przykład umożliwia jednoczesne stylizowanie właściwości inner-shadow i fillStyle za pomocą koloru, gradientu i wzoru. Możesz określić obrót wzoru osobno. Zauważ, że paski zebry są teraz prostopadłe do siebie. Maska przycinająca o rozmiarze ramki ograniczającej eliminuje potrzebę używania bardzo dużego kontenera do zamknięcia kształtu wycięcia, co zwiększa szybkość, ponieważ zapobiega przetwarzaniu niepotrzebnych części cienia.
function innerShadow() {
function drawShape() { // draw anti-clockwise
ctx.arc(0, 0, 100, 0, Math.PI * 2, true); // Outer circle
ctx.moveTo(70, 0);
ctx.arc(0, 0, 70, 0, Math.PI, false); // Mouth
ctx.moveTo(-20, -20);
ctx.arc(30, -30, 10, 0, Math.PI * 2, false); // Left eye
ctx.moveTo(140, 70);
ctx.arc(-20, -30, 10, 0, Math.PI * 2, false); // Right eye
};
var width = 200;
var offset = width + 50;
var innerColor = "rgba(0,0,0,1)";
var outerColor = "rgba(0,0,0,1)";
ctx.translate(150, 170);
// apply inner-shadow
ctx.save();
ctx.fillStyle = "#000";
ctx.shadowColor = innerColor;
ctx.shadowBlur = getBlurValue(120);
ctx.shadowOffsetX = -15;
ctx.shadowOffsetY = 15;
// create clipping path (around blur + shape, preventing outer-rect blurring)
ctx.beginPath();
ctx.rect(-offset/2, -offset/2, offset, offset);
ctx.clip();
// apply inner-shadow (w/ clockwise vs. anti-clockwise cutout)
ctx.beginPath();
ctx.rect(-offset/2, -offset/2, offset, offset);
drawShape();
ctx.fill();
ctx.restore();
// cutout temporary rectangle used to create inner-shadow
ctx.globalCompositeOperation = "destination-out";
ctx.fill();
// prepare vector paths
ctx.beginPath();
drawShape();
// apply fill-gradient to inner-shadow
ctx.save();
ctx.globalCompositeOperation = "source-in";
var gradient = ctx.createLinearGradient(-offset/2, 0, offset/2, 0);
gradient.addColorStop(0.3, '#ff0');
gradient.addColorStop(0.7, '#f00');
ctx.fillStyle = gradient;
ctx.fill();
// apply fill-pattern to inner-shadow
ctx.globalCompositeOperation = "source-atop";
ctx.globalAlpha = 1;
ctx.rotate(0.9);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fill();
ctx.restore();
// apply fill-gradient
ctx.save();
ctx.globalCompositeOperation = "destination-over";
var gradient = ctx.createLinearGradient(-offset/2, -offset/2, offset/2, offset/2);
gradient.addColorStop(0.1, '#f00');
gradient.addColorStop(0.5, 'rgba(255,255,0,1)');
gradient.addColorStop(1.0, '#00f');
ctx.fillStyle = gradient
ctx.fill();
// apply fill-pattern
ctx.globalCompositeOperation = "source-atop";
ctx.globalAlpha = 0.2;
ctx.rotate(-0.4);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fill();
ctx.restore();
// apply outer-shadow (color-only without temporary layer)
ctx.globalCompositeOperation = "destination-over";
ctx.shadowColor = outerColor;
ctx.shadowBlur = 40;
ctx.shadowOffsetX = 15;
ctx.shadowOffsetY = 10;
ctx.fillStyle = "#fff";
ctx.fill();
};
Z tych przykładów widać, że za pomocą globalCompositeOperation możemy łączyć efekty, tworząc bardziej skomplikowane efekty (wykorzystując maskowanie i mieszanie). Ekran jest Twój ;)
Spaceage – efekty generatywne
W <canvas>, przechodząc od znaku Unicode 0x2708:
…do tego przykładu z cieniowaniem:
…można osiągnąć przez wielokrotne wywołanie funkcji ctx.strokeText() z cienką linią (0,25),
przy jednoczesnym powolnym zmniejszaniu przesunięcia x i alfa, co daje wrażenie ruchu elementów wektorowych.
Mapując pozycję XY elementów na falę sinusoidalną/cosinusoidalną i przechodząc przez kolory za pomocą właściwości HSL, możemy tworzyć ciekawsze efekty, takie jak ten przykład „zagrożenia biologicznego”:
HSL: odcień, nasycenie, jasność (1978)
HSL to nowo obsługiwany format w specyfikacji CSS3. Podczas gdy format HEX został zaprojektowany dla komputerów, format HSL jest przeznaczony dla ludzi.
Aby zilustrować łatwość użycia HSL, przejdziemy przez spektrum kolorów, po prostu zwiększając „odcień” od 360. Odcień jest mapowany na spektrum w sposób cylindryczny. Jasność określa, jak ciemny lub jasny jest kolor. 0% oznacza czarny piksel, a 100% – biały. Nasycenie określa, jak jasny lub żywy jest kolor. Odcienie szarości są tworzone przy nasyceniu 0%, a żywe kolory przy wartości 100%.
Ponieważ HSL jest nowym standardem, możesz nadal obsługiwać starsze przeglądarki, co jest możliwe dzięki konwersji przestrzeni barw. Poniższy kod akceptuje obiekt HSL { H: 360, S: 100, L: 100} i zwraca obiekt RGB { R: 255, G: 255, B: 255 }. Następnie możesz użyć tych wartości, aby utworzyć ciąg rgb lub rgba. Więcej informacji znajdziesz w artykule w Wikipedii na temat HSL.
// HSL (1978) = H: Hue / S: Saturation / L: Lightness
HSL_RGB = function (o) { // { H: 0-360, S: 0-100, L: 0-100 }
var H = o.H / 360,
S = o.S / 100,
L = o.L / 100,
R, G, B, _1, _2;
function Hue_2_RGB(v1, v2, vH) {
if (vH < 0) vH += 1;
if (vH > 1) vH -= 1;
if ((6 * vH) < 1) return v1 + (v2 - v1) * 6 * vH;
if ((2 * vH) < 1) return v2;
if ((3 * vH) < 2) return v1 + (v2 - v1) * ((2 / 3) - vH) * 6;
return v1;
}
if (S == 0) { // HSL from 0 to 1
R = L * 255;
G = L * 255;
B = L * 255;
} else {
if (L < 0.5) {
_2 = L * (1 + S);
} else {
_2 = (L + S) - (S * L);
}
_1 = 2 * L - _2;
R = 255 * Hue_2_RGB(_1, _2, H + (1 / 3));
G = 255 * Hue_2_RGB(_1, _2, H);
B = 255 * Hue_2_RGB(_1, _2, H - (1 / 3));
}
return {
R: R,
G: G,
B: B
};
};
Tworzenie animacji za pomocą requestAnimationFrame
W przeszłości do tworzenia animacji w języku JavaScript były dostępne 2 opcje: setTimeout i setInterval.
window.requestAnimationFrame to nowy standard, który ma zastąpić obie te metody. Pozwala oszczędzać energię elektryczną (i zasoby komputera), umożliwiając przeglądarce regulowanie animacji na podstawie dostępnych zasobów.
Oto niektóre z ważnych funkcji:
- Gdy użytkownik opuści kadr, animacja może zwolnić lub całkowicie się zatrzymać, aby zapobiec niepotrzebnemu zużyciu zasobów.
- Liczba klatek jest ograniczona do 60 kl./s. Dzieje się tak, ponieważ jest to znacznie powyżej poziomu, który ludzie mogą zauważyć (większość ludzi przy 30 kl./s widzi animację jako „płynną”).
W momencie pisania tego artykułu do korzystania z requestAnimationFrame wymagane są prefiksy dostawcy.
Paul Irish utworzył warstwę shim, która obsługuje różne przeglądarki, w artykule requestAnimationFrame for smart animating (requestAnimationFrame do inteligentnej animacji):
// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
Idąc o krok dalej, bardziej ambitni mogą połączyć to z polyfillem, takim jak requestAnimationFrame.js (jest kilka funkcji do opracowania), który w większym stopniu obsługiwałby starsze przeglądarki, jednocześnie przechodząc na ten nowy standard.
(function animate() {
var i = 50;
while(i--) {
if (n > endpos) return;
n += definition;
ctx.globalAlpha = (0.5 - (n + startpos) / endpos) * alpha;
if (doColorCycle) {
hue = n + color;
ctx.strokeStyle = "hsl(" + (hue % 360) + ",99%,50%)"; // iterate hue
}
var x = cos(n / cosdiv) * n * cosmult; // cosine
var y = sin(n / sindiv) * n * sinmult; // sin
ctx.strokeText(text, x + xoffset, y + yoffset); // draw rainbow text
}
timeout = window.requestAnimationFrame(animate, 0);
})();
Kod źródłowy
Dzięki wsparciu ze strony dostawców przeglądarek przyszłość <canvas> jest pewna. Można go przenieść do plików wykonywalnych na iPhone'a, Androida i komputery za pomocą PhoneGap lub