Moje dane
<canvas>
pojawiła się w 2006 roku, gdy wydano Firefoxa w wersji 2.0. Artykuł o Ajaxie, opisujący macierz przekształceń, zainspirował mnie do stworzenia mojej pierwszej aplikacji internetowej <canvas>
: Color Sphere (2007 r.). Dzięki temu zanurzył mnie w świecie kolorów i elementów graficznych podstawowych. Inspirował do stworzenia Sketchpada (2007–2008) w ramach konstrukcji aplikacji, która jest „lepsza niż Paint” w przeglądarce.
Te eksperymenty doprowadziły w końcu do utworzenia startupu Mugtug we współpracy z moim wieloletnim przyjacielem Charlesem Pritchardem. Pracujemy nad Darkroomem w HTML5 <canvas>
. Darkroom to nieszkodliwa aplikacja do udostępniania zdjęć, która łączy możliwości filtrów pikselowych z typografią i rysunkiem opartym na wektorach.
Wprowadzenie
<canvas>
daje 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.
<canvas>
- Wewnętrzne i zewnętrzne cienie 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 generowany automatycznie w
<canvas>
, który wykorzystuje cykl kolorów hsl() iwindow.requestAnimationFrame
, aby stworzyć wrażenie ruchu.
Cienie tekstu w Canvas
Jedną z moich ulubionych funkcji wzbogaconych o CSS3 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 2 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ą dane wejściowe RGB, RGBA, HSL, HEX i inne.
- ctx.shadowOffsetX = 0; // liczba całkowita
- Odległość cienia w poziomie w stosunku do 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.
Efekt stereoskopowego 3D (więcej informacji znajdziesz w sekcji Obraz anaglificzny) 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. Współczynnik konwersji dla konwersji między PT, PC, EM, EX, PX i tak dalej możemy ustalić, tworząc element o tych samych właściwościach czcionki w DOM i ustawiając szerokość na format do pomiaru. Aby na przykład przechwytywać konwersje EM -> PX, mierzymy element DOM z wartością „wysokość: 1em”, czyli wysokość przesunięcia PEM.
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 jednocześnie cień. Próba ta była jednak daremna, ponieważ cień jest mnożnikiem przezroczystości fillStyle, a zatem 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 wokół bloku tekstu
Aby nieco uporządkować ten kod, możemy zablokować wyświetlanie fillText (pozostawiając wyświetlanie cienia), dodając ścieżkę cięcia.
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ą ctx.measureText().width
, ale zmienna 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ąca „cień” i wycinająca 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 dodałem prosty parsujący tekst parser. Dzięki temu możesz podawać mu polecenia CSS, a on będzie generować polecenia <canvas>
.
Teraz elementy <canvas>
mają całą gamę stylów, z którymi można się połączyć.
Te same efekty cieni można stosować na dowolnym obiekcie wektorowym, od czcionek WebFonts, przez złożone kształty zaimportowane z plików SVG, aż po generatywne kształty wektorowe itd.
Przerwa (dygresja na temat pchania pikseli)
W tej części artykułu bardzo mnie zaciekawił 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. Czy ktoś się zgłasza?
Neonowy-tęczowy, 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, które można traktować jako tryby mieszania warstw; pozostałe 10 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ęść spektrum kolorów i wiele poziomów przezroczystości alfa, aby można było dokładnie zobaczyć, czego się spodziewać. Aby uzyskać opisy tekstowe, zalecam 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 tę operację.
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, który można uzyskać w Photoshopie, czyli neonowe, tęczowe światło z rozmytym 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
Inspiracją do stworzenia efektu Zebra Reflection był znakomity materiał przygotowany przez WebDesignerWall, który pokazuje, jak urozmaicić stronę za pomocą CSS. Idzie o krok dalej i tworzy „refleksję” tekstu, taką jak 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” i „zewnętrznych”. 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 trudniejsze ;) Jak proponowaliśmy w niedawnym poście z platformy F1LT3R, można tworzyć wewnętrzne cienie, wykorzystując unikalne właściwości reguł nawijania zgodnie z ruchem wskazówek zegara i przeciwnie do ruchu wskazówek zegara. W tym celu utworzysz „wewnętrzny cień”, rysując prostokąt kontenera, a następnie, stosując przeciwne zasady zwinięcia, narysuj kształt wycięcia, aby utworzyć odwrotność kształtu.
W tym przykładzie stylizacja cienia wewnętrznego i fillStyle jest możliwa dzięki jednoczesnemu zastosowaniu 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 ująć 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();
};
Jak widać na tych przykładach, przy użyciu metody globalCompositeOperation można łączyć efekty łańcuchowe, co daje bardziej skomplikowane efekty (z wykorzystaniem maskowania i mieszania). Ekran jest Twój ;)
Spaceage – efekty generatywne
W <canvas>
znak Unicode 0x2708:
…do 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 kierunku osi X i wartość alfa, co spowoduje, że elementy wektorowe będą sprawiać wrażenie poruszania się.
Mapowanie pozycji XY elementów na fali sinuso-cosinusowej i przełączenie kolorów za pomocą właściwości HSL pozwala uzyskać bardziej interesujące efekty, takie jak ten przykład „zagrożeń biologicznych”:
HSL: odcień, nasycenie, jasność (1978 r.)
HSL to nowy format obsługiwany w specyfikacji CSS3. Natomiast kod HEX zaprojektowano tak, aby był czytelny dla człowieka.
Ilustracja pokazująca łatwość korzystania z HSL. Aby przejść przez spektrum kolorów, wystarczy zwiększać wartość „hue” (barwa) od 360. Barwa jest mapowana 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 jasność lub intensywność koloru. Szarości są tworzone przy nasyceniu 0%, a żywe kolory przy wartości 100%.
HSL jest najnowszym standardem, dlatego warto nadal obsługiwać starsze przeglądarki, korzystając z konwersji z użyciem przestrzeni kolorów. 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
W przeszłości animacje w języku JavaScript można było tworzyć na dwa sposoby: setTimeout
i setInterval
.
window.requestAnimationFrame
to nowy standard, który zastępuje oba poprzednie, oszczędzając energię elektryczną (i trochę czasu na komputerze) dzięki temu, że przeglądarka może regulować animacje na podstawie dostępnych zasobów.
Oto niektóre ważne funkcje:
- Gdy użytkownik opuści kadr, animacja może zwolnić lub całkowicie się zatrzymać, aby nie zużywać niepotrzebnych zasobów.
- Obowiązuje limit 60 kl./s. Wynika to z tego, że jest to znacznie powyżej poziomu, który jest zauważalny dla ludzi (większość ludzi uważa, że animacja z 30 FPS jest „płynna”).
W momencie pisania tego artykułu do korzystania z requestAnimationFrame
wymagane są prefiksy specyficzne dla dostawcy.
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 (jest kilka funkcji, które należy dopracować), aby zapewnić obsługę starszych przeglądarek w większym stopniu, jednocześnie przechodząc 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>
będzie można przenieść na pliki wykonywalne na iPhone’a, Androida lub komputer za pomocą PhoneGap lub