Efekty typograficzne na płótnie

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

Grafika banera na płótnie.

<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<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.
<canvas>
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<canvas>, który wykorzystuje cykl kolorów hsl() i window.requestAnimationFrame, aby stworzyć wrażenie ruchu.

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, StereoscopicShadow 3D.

Grafika 3D w usłudze porównywania cen

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:

  1. 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>
  1. 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.

Grafika neonowa

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.

Cień tekstu w efektach w obszarze roboczym

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?

Okulary 3D

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> ciemniejszymjaś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:

Grafiki efektów łańcuchowych

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):

Rainbow Jitter
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:

Efekt zebry
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.

Cienie wewnętrzne/zewnętrzne
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

<canvas> znak Unicode 0x2708:

Grafika w Unicode

…do tego przykładu z cieniowaniem:

Zacieniony przykład

…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”:

Efekt cykliczny HSL

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%.

Grafika HSL

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: setTimeoutsetInterval.

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);
})();
Grafika z rozmyciem
Grafika animacyjna
Grafika typu Matrix

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

Titanium.