Moje tło
<canvas>
pojawiła się w 2006 roku, gdy wydano Firefoxa w wersji 2.0. Artykuł o Ajaxie, opisujący macierz przekształcenia, zainspirował mnie do stworzenia mojej pierwszej aplikacji internetowej <canvas>
: Color Sphere (2007 r.). To zaowocowało zanurzeniem się w świat kolorów i prostych elementów graficznych. W ramach tego projektu powstała aplikacja Sketchpad (2007–2008), która miała być „lepsza niż Paint” w przeglądarce.
Te eksperymenty doprowadziły w końcu do utworzenia startupu Mugtug wspólnie z moim wieloletnim przyjacielem Charlesem Pritchardem. Aktualnie opracowujemy Darkroom w HTML5 <canvas>
. Darkroom to aplikacja do udostępniania zdjęć, która nie powoduje trwałych zmian w obrazie. Łączy ona zalety filtrów opartych na pikselach z typografią i rysowaniem wektorowym.
Wprowadzenie

<canvas>
zapewnia programistom JavaScript pełną kontrolę nad kolorami, wektorami i pikselami na ekranach – czyli nad wyglądem monitora.
Poniższe przykłady dotyczą obszaru w <canvas>
, który nie był do tej pory zbyt popularny: tworzenia efektów tekstowych. Różnorodność efektów tekstu, które można tworzyć w <canvas>
, jest tak wielka, jak tylko możesz sobie wyobrazić. Te prezentacje pokazują tylko część tego, co jest możliwe. Chociaż w tym artykule zajmujemy się „tekstem”, metody te można stosować do wszystkich obiektów wektorowych, tworząc ekscytujące wizualizacje w grach i innych aplikacjach:
- Cienie tekstu w
<canvas>
. - Efekty tekstowe w stylu CSS w
<canvas>
, tworzenie masek przycinania, znajdowanie danych w<canvas>
i używanie właściwości cienia. - Neonowy tęcza, odbicie zebry – efekty łańcuchowe.
- Efekty tekstowe
- w stylu Photoshopa w przypadku przykładów użycia funkcji globalCompositeOperation, createLinearGradient i createPattern.
- Cienie wewnętrzne i zewnętrzne w
<canvas>
- Przedstawiamy nieznaną funkcję: tworzenie cienia wewnętrznego (inner-shadow) za pomocą skręcania w zależności od kierunku wskazówek zegara lub przeciwnie do niego.
- Spaceage – efekt generatywny.
- Efekt tekstowy oparty na generatywnych modelach w
<canvas>
, który wykorzystuje cykl kolorów hsl() iwindow.requestAnimationFrame
, aby stworzyć wrażenie ruchu.
<canvas>
Cienie tekstu w Canvas
Jedną z moich ulubionych funkcji w specyfikacji CSS3 (obok border-radius, web-gradients i innych) jest możliwość tworzenia cieni. Musisz wiedzieć, na czym polegają różnice między cieniami CSS a cieniami <canvas>
, a w szczególności:
CSS używa dwóch metod: box-shadow do elementów box, takich jak div, span itp., oraz text-shadow do treści tekstowych.
<canvas>
ma jeden typ cienia; jest on używany do wszystkich obiektów wektorowych; ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.strokeText itd.
Aby utworzyć cień w komponencie <canvas>
, użyj tych 4 właściwości:
- ctx.shadowColor = "red" // string
- Kolor cienia. Dozwolone są wartości RGB, RGBA, HSL, HEX i inne.
- ctx.shadowOffsetX = 0; // liczba całkowita
- Odległość cienia w poziomie względem tekstu.
- ctx.shadowOffsetY = 0; // liczba całkowita
- Odległość w pionie cienia względem tekstu.
- ctx.shadowBlur = 10; // liczba całkowita
- Efekt rozmywania cienia. Im większa wartość, tym większy stopień rozmywania.
Na początek zobaczmy, jak <canvas>
może emulować efekty CSS.
Wyszukanie w Google Images hasła „css text-shadow” doprowadziło nas do kilku świetnych demonstracji, które chcieliśmy naśladować: Line25, Stereoscopic i Shadow 3D.

Stereoskopowy efekt 3D (więcej informacji znajdziesz w sekcji Obraz anaglifowy) to przykład prostej linii kodu, która świetnie się sprawdza. Za pomocą tej linii kodu CSS możemy stworzyć iluzję głębi, gdy obraz jest oglądany w okularach 3D czerwono-cyjanowych (takich, jakie są rozdawane w kinach 3D):
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
Podczas konwertowania tego ciągu znaków na format <canvas>
należy zwrócić uwagę na 2 kwestie:
- Nie ma rozmycia cienia (trzecia wartość), więc nie ma powodu, aby stosować cień, ponieważ fillText da te 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>
- W
<canvas>
nie są obsługiwane elementy EM, więc trzeba je przekonwertować na elementy PX. Stosunek konwersji między formatami PT, PC, EM, EX, PX itd. można znaleźć, tworząc element z tymi samymi właściwościami czcionki w DOM i ustawiając szerokość na format, który ma być mierzony. Na przykład, aby uzyskać konwersję EM -> PX, mierzymy element DOM z wartością „height: 1em”, a wynik offsetHeight będzie określał, ile pikseli jest w każdym 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 alfa
W bardziej złożonym przykładzie, takim jak efekt neonu w Line25, należy użyć właściwości shadowBlur, aby prawidłowo emulować efekt. Ponieważ efekt neonu wymaga wielu cieni, napotykamy problem: w <canvas>
każdy obiekt wektorowy może mieć tylko jeden cień. Aby narysować wiele cieni, musisz narysować kilka wersji tekstu na siebie.
Powoduje to mnożenie alfa i ostatecznie postrzępienie krawędzi.

Próbowałem użyć ctx.fillStyle = "rgba(0,0,0,0)"
lub "transparent"
, aby ukryć tekst, wyświetlając cień. Próba była jednak daremna, ponieważ cień jest mnożnikiem przezroczystości fillStyle, więc nigdy nie może być bardziej nieprzejrzysty niż fillStyle.
Na szczęście jest na to sposób. Możemy narysować cień z odstępem od tekstu, aby były od siebie oddzielone (aby się nie nakładały), i w ten sposób ukryć tekst z boków 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 bloku tekstu
Aby nieco uporządkować ten kod, możemy zablokować wyświetlanie fillText (pozostawiając wyświetlanie cienia), dodając ścieżkę obcinania.
Aby utworzyć ścieżkę obcinania otaczającą tekst, musimy znać jego wysokość (tzw. wysokość em, czyli historycznie wysokość litery „M” na prasie drukarskiej) oraz szerokość.
Szerokość możemy uzyskać za pomocą funkcji ctx.measureText().width
, ale funkcja ctx.measureText().height
nie istnieje.
Na szczęście dzięki sztuczce w CSS (zobacz Typographic Metrics, aby dowiedzieć się więcej o sposobach poprawiania starszych implementacji <canvas>
za pomocą pomiarów CSS) możemy znaleźć wysokość tekstu przez pomiar offsetHeight
elementu <span>
z tymi samymi właściwościami 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 posłuży jako ścieżka wycinania, obejmując „cień” i usuwając kształt zastępczy.
ctx.rect(0, 0, width, emHeight);
ctx.clip();
Łączenie wszystkich elementów i optymalizowanie ich w miarę pracy – jeśli cień nie ma rozmycia, do tego samego efektu można użyć fillText, co pozwoli nam uniknąć konfigurowania maski przycinania:
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ć ręcznie wszystkich tych poleceń <canvas>
, w źródle wersji demonstracyjnej umieściłem prosty parsujący tekst parser. Dzięki temu możesz podawać mu polecenia CSS, a on będzie generować polecenia <canvas>
.
Nasze elementy <canvas>
mogą być używane w różnych stylach.
Tych samych efektów cieni można używać w przypadku dowolnego obiektu wektorowego, od WebFonts po złożone kształty zaimportowane z pliku SVG, a także w przypadku generowanych kształtów wektorowych.

Intermezzo (o przesuwaniu pikseli)
Podczas pisania tej sekcji artykułu zaciekawił mnie przykład stereoskopowy. Jak trudno jest uzyskać efekt 3D na ekranie filmowym za pomocą <canvas>
i 2 obrazów zrobionych z nieco innych perspektyw? Wygląda na to, że nie jest to zbyt trudne. Ten kernel łączy czerwony kanał pierwszego obrazu (data) z niebieskim kanałem 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 przykleić 2 iPhone'y do czoła, kliknąć „nagrywaj film” i uzyskać własne filmy 3D w formacie HTML5. Ktoś się zgłasza?

Neonowy tęcza, odbicie zebry – efekty łańcuchowe
Łańcuchowanie wielu efektów w funkcji <canvas>
może być proste, ale wymaga podstawowej znajomości operacji globalCompositeOperation (GCO). Aby porównać operacje z GIMP-em (lub Photoshopem): w <canvas>
ciemniejszym i jaśniejszym jest 12 GCO, a 10 innych operacji jest stosowanych do warstw jako maski alfa (jedna warstwa usuwa piksele z innej warstwy). Operacja globalCompositeOperation łączy „warstwy” (w naszym przypadku ciągi kodu) w nowy i ciekawy sposób:

Wykres globalCompositeOperation pokazuje działanie trybów GCO. Wykres ten wykorzystuje dużą część widma kolorów i wiele poziomów przezroczystości alfa, aby można było dokładnie zobaczyć, czego można się spodziewać. W przypadku opisów tekstowych zalecamy zapoznanie się z dokumentacją globalCompositeOperation firmy Mozilla. Aby dowiedzieć się więcej, możesz zapoznać się z artykułem Compositing Digital Images (w języku angielskim) autorstwa Portera Duffa, w którym omówiono sposób działania tej operacji.
Mój ulubiony tryb to globalCompositeOperation="lighter". Lighter miesza dołączone piksele w sposób podobny do tego, w jaki światło miesza się z innymi kolorami. Gdy czerwone, zielone i białe światło są w pełni natężone, widzimy światło białe. To ekscytująca funkcja, zwłaszcza gdy parametr <canvas>
ma ustawioną niską wartość globalAlpha, co umożliwia większą kontrolę i gładsze krawędzie. Lighter ma wiele zastosowań, a ostatnio mój ulubiony to kreator tła pulpitu HTML5 dostępny na stronie http://weavesilk.com/.
W jednym z moich pokazów, Breathing Galaxies (JS1k),
używam też trybu jaśniejszego – na podstawie tych 2 przykładów możesz zobaczyć, jaki efekt daje ten tryb.
Obsługa przeglądarki w operacji globalCompositeOperation.
Efekt jittera neonowo-tęczowego
W tym pokazie wykonamy efekt podobny do tego w Photoshopie, czyli neonowe, tęczowe światło z rozmazanym konturem, łącząc efekty za pomocą globalCompositeOperation (source-in, lighter i darker).
To demo jest rozwinięciem prezentacji „Cienie tekstu w <canvas>
”, w której zastosowano 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 materiałem WebDesignerWall na temat urozmaicania strony za pomocą CSS. Ta funkcja rozwija tę ideę, tworząc „odbicie” tekstu – podobne do tego, które możesz zobaczyć w iTunes. Efekt łączy kolor wypełnienia (biały), createPattern (zebra.png) i linearGradient (połysk); pokazuje to możliwość zastosowania wielu rodzajó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();
};
Wewnętrzne i zewnętrzne cienie w Canvas
Specyfikacje <canvas>
nie poruszają tematu cieni „wewnętrznych” w porównaniu z „zewnętrznymi”. Na pierwszy rzut oka możesz sądzić, że cień „wewnętrzny” nie jest obsługiwany. Nie jest to prawda.
Włączenie tej funkcji jest nieco bardziej skomplikowane ;) Jak sugerowano w niedawnym poście F1LT3R, możesz tworzyć cienie wewnętrzne, korzystając z wyjątkowych właściwości reguł skręcania w kierunku zgodnym z lub przeciwnym do ruchu wskazówek zegara. Aby to zrobić, utwórz „cień wewnętrzny”, rysując prostokąt kontenera, a następnie za pomocą odwrotnych reguł nawiasów narysuj kształt wycięcia – tworząc odwrotny kształt.
W tym przykładzie cienie wewnętrzne i fillStyle są stylizowane jednocześnie za pomocą koloru, gradientu i wzorów. Możesz określić obrót wzoru indywidualnie. Zwróć uwagę, że paski zebry są teraz prostopadłe do siebie. Używana jest maska przycinania o rozmiarze ramki ograniczającej, co eliminuje potrzebę tworzenia bardzo dużego kontenera, aby mieścił kształt wycięcia. Zwiększa to szybkość, ponieważ nie są przetwarzane niepotrzebne 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ą metody globalCompositeOperation możemy łączyć efekty, tworząc bardziej złożone efekty (korzystając z maskowania i mieszania). Ekran jest Twój ;)
Spaceage – efekty generatywne
W <canvas>
znak Unicode 0x2708:

…do tego przykładu z cieniowaniem:

…można uzyskać, wykonując wiele wywołań funkcji ctx.strokeText()
z cienką grubością linii (0,25),
a także stopniowo zmniejszając przesunięcie w osi X i wartość alfa, co spowoduje, że elementy wektorowe będą sprawiać wrażenie poruszających się.
Dzięki mapowaniu pozycji XY elementów na fali sinusoidalnej lub kosinusoidalnej oraz przełączaniu kolorów za pomocą właściwości HSL możemy tworzyć ciekawsze efekty, takie jak ten przykład „biohazard”:

HSL: odcień, nasycenie, jasność (1978 r.)
HSL to nowy format obsługiwany w specyfikacji CSS3. Format HEX został zaprojektowany z myślą o komputerach, a format HSL – z myślą o ludzkim odczycie.
Ilustracja pokazująca łatwość korzystania z HSL. Aby przejść przez widmo kolorów, wystarczy zwiększać wartość „hue” (barwa) od 360. Barwa jest mapowana na widmo w sposób cylindryczny. Jasność określa, jak ciemny lub jasny jest kolor; 0% oznacza czarny piksel, a 100% – biały. Nasycenie określa jasność lub intensywność koloru. Szarości są tworzone przy nasyceniu 0%, a żywe kolory przy wartości 100%.

Standard HSL jest nowy, ale 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 wyprowadza obiekt RGB {R: 255, G: 255, B: 255}. Następnie możesz użyć tych wartości, aby utworzyć ciąg znaków rgb lub rgba. Więcej informacji znajdziesz w artykule w Wikipedii poświęconym 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ą metody requestAnimationFrame
Wcześniej do tworzenia animacji w języku JavaScript były dostępne 2 metody: setTimeout
i setInterval
.
window.requestAnimationFrame
to nowy standard, który zastępuje oba poprzednie, oszczędzając energię elektryczną (i trochę czasu na komputerze), ponieważ pozwala przeglądarce regulować animacje na podstawie dostępnych zasobów.
Oto kilka ważnych funkcji:
- Gdy użytkownik opuści kadr, animacja może zwolnić lub całkowicie się zatrzymać, aby nie zużywać niepotrzebnych zasobów.
- Maksymalna liczba klatek na sekundę wynosi 60. Wynika to z tego, że jest to znacznie powyżej poziomu, który jest zauważalny dla ludzi (większość ludzi uważa animację z 30 FPS za „płynną”).
W momencie pisania tego artykułu do korzystania z funkcji requestAnimationFrame
wymagane są prefiksy specyficzne dla danego sprzedawcy.
Paul Irish stworzył warstwę pośrednią, która obsługuje wiele urządzeń, w ramach 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 dalej, bardziej ambitne rozwiązania mogą łączyć to z polyfillem, takim jak requestAnimationFrame.js (trzeba jeszcze dopracować kilka funkcji), który będzie obsługiwać starsze przeglądarki w większym stopniu, a zarazem pozwoli na przejście na 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 nie ma wątpliwości, że <canvas>
może zostać przeniesiony do plików wykonywalnych na iPhone’a, Androida lub komputer za pomocą PhoneGap lub