Il mio background
<canvas>
è entrato a far parte della mia conoscenza nel 2006, quando è stato rilasciato Firefox v2.0. Un articolo su Ajaxian, che descrive la matrice di trasformazione, mi ha ispirato a creare la mia prima web-app <canvas>
: Color Sphere (2007). Questo mi ha immerso nel mondo dei colori e delle primitive grafiche, ispirando la creazione di Sketchpad (2007-2008) nel tentativo di mettere insieme un'applicazione "migliore di Paint" nel browser.
Questi esperimenti hanno portato alla creazione della startup Mugtug, insieme
al mio amico di lunga data Charles Pritchard. Stiamo sviluppando Darkroom in HTML5 <canvas>
. Darkroom è un'app di condivisione di foto non distruttiva che combina le potenzialità dei filtri basati su pixel con la tipografia e il disegno basati su vettori.
Introduzione
<canvas>
offre ai programmatori JavaScript il pieno controllo di colori, vettori e pixel sullo schermo, ovvero la composizione visiva del monitor.
Gli esempi seguenti riguardano un'area di <canvas>
che non ha ricevuto molta attenzione: la creazione di effetti di testo. La varietà di effetti di testo
che è possibile creare in <canvas>
è quanto più vasta che puoi immaginare. Queste demo
illustrano una sottosezione di ciò che è possibile. Anche se abbiamo a che fare con il "testo" in questo articolo, i metodi possono essere applicati a qualsiasi oggetto vettoriale, creazione di immagini entusiasmanti nei giochi e in altre applicazioni:
- Ombreggiatura testo in
<canvas>
. - Effetti di testo simili a CSS in
<canvas>
per creare maschere di ritaglio, trovare metriche in<canvas>
e utilizzare la proprietà ombra. - Effetti di congiunzione arcobaleno neon, riflessi zebrati.
- Effetti di testo
<canvas>
come in Photoshop negli esempi di utilizzo di globalCompositeOperation, createLinearGradient, createPattern. - Ombres interne ed esterne in
<canvas>
- Rivelazione di una funzionalità poco nota: l'uso della rotazione in senso orario o antiorario per creare l'inverso di un'ombra interna (inner-shadow).
- Spaceage - effetto generativo.
- Effetto di testo basato su generative in
<canvas>
che utilizza il ciclo di colori hsl() ewindow.requestAnimationFrame
per creare la sensazione di movimento.
Ombreggiatura testo in Canvas
Una delle mie aggiunte preferite alle specifiche CSS3 (insieme a border-radius, web-gradients e altre) è la possibilità di creare ombre. È importante conoscere le differenze tra le ombre CSS e <canvas>
, in particolare:
CSS utilizza due metodi: box-shadow per gli elementi box, come div, span e così via; e text-shadow per i contenuti di testo.
<canvas>
ha un tipo di ombra; viene utilizzato per tutti gli oggetti vettoriali; ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.strokeText e così via.
Per creare un'ombra in <canvas>
, utilizza queste quattro proprietà:
- ctx.shadowColor = "red" // stringa
- Colore dell'ombra: sono validi i valori RGB, RGBA, HSL, HEX e altri.
- ctx.shadowOffsetX = 0; // intero
- Distanza orizzontale dell'ombra rispetto al testo.
- ctx.shadowOffsetY = 0; // integer
- Distanza verticale dell'ombra rispetto al testo.
- ctx.shadowBlur = 10; // intero
- Effetto di sfocatura sull'ombra: maggiore è il valore, maggiore è la sfocatura.
Per iniziare, vediamo come <canvas>
può emulare gli effetti CSS.
La ricerca di "css text-shadow" su Google Immagini ci ha portato a trovare alcune ottime demo da emulare: Line25, Stereoscopic e Shadow 3D.
L'effetto 3D stereoscopico (per saperne di più, consulta Immagine anaglifa) è un esempio di una semplice riga di codice, di grande utilità. Con la seguente riga di CSS, possiamo creare l'illusione della profondità se visualizzata con gli occhiali 3D rosso/ciano (quelli che ti danno nei cinema 3D):
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
Quando converti questa stringa in <canvas>
, tieni presente due aspetti:
- Non è presente l'effetto sfocatura ombra (il terzo valore), quindi non c'è motivo di eseguire effettivamente l'ombra, poiché fillText creerebbe gli stessi risultati:
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>
- I moduli EM non sono supportati in
<canvas>
, pertanto dovranno essere convertiti in moduli PX. Possiamo trovare il rapporto di conversione per la conversione tra PT, PC, EM, EX, PX e così via creando un elemento con le stesse proprietà di carattere nel DOM e impostando la larghezza sul formato da misurare. Ad esempio, per acquisire la conversione EM -> PX, misureremo l 'elemento DOM con "altezza: 1em", l 'offsetHeight risultante sarà il numero di PX in ogni 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>
Impedire la moltiplicazione alfa
In un esempio più complesso, come l'effetto neon di Line25, è necessario utilizzare la proprietà shadowBlur per emulare correttamente l'effetto. Poiché
l'effetto Neon si basa su più ombre, si verifica un problema: in <canvas>
ogni oggetto vettoriale può essere presente una sola ombra. Pertanto, per disegnare più ombre, devi disegnare più versioni del testo una sopra l'altra.
Ciò comporta la moltiplicazione dell'alpha e, in ultima analisi, bordi frastagliati.
Ho provato a eseguire ctx.fillStyle = "rgba(0,0,0,0)"
o "transparent"
per nascondere il testo, mentre mostrava l'ombra… tuttavia, questo tentativo è stato inutile;
poiché l'ombra è una moltiplicazione dell'alfa fillStyle, non può essere mai
più opaca del fillStyle.
Fortunatamente, esiste un modo per aggirare il problema: possiamo disegnare l'offset dell'ombra dal testo, mantenendoli separati (in modo che non si sovrappongano) e nascondendo così il testo sul lato dello schermo:
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);
Ritaglio intorno a un blocco di testo
Per fare un po' di pulizia, possiamo impedire che il FillText venga disegnato nella prima posizione (consentendo allo stesso tempo di tracciare l'ombra) aggiungendo un tracciato di ritaglio.
Per creare un percorso di ritaglio che circondi il testo, dobbiamo conoscere l'altezza del testo (chiamata "altezza in em", storicamente l'altezza della lettera "M" su una macchina da stampa) e la larghezza del testo.
Possiamo ottenere la larghezza utilizzando ctx.measureText().width
, tuttavia,ctx.measureText().height
non esiste.
Fortunatamente, grazie a CSS hack-ardry (consulta Metriche tipografiche per scoprire altri modi per correggere le implementazioni precedenti di <canvas>
utilizzando le misurazioni CSS), possiamo trovare l'altezza del testo misurando il offsetHeight
di un <span>
con le stesse proprietà dei caratteri:
var d = document.createElement("span");
d.font = "20px arial"
d.textContent = "Hello world!"
var emHeight = d.offsetHeight;
Da qui, possiamo creare un rettangolo da utilizzare come percorso di ritaglio, racchiudendo l'"ombra" e rimuovendo la forma fittizia.
ctx.rect(0, 0, width, emHeight);
ctx.clip();
Riassumendo e ottimizzando man mano che procediamo: se un'ombra non ha sfocatura, fillText può essere utilizzato per lo stesso effetto, evitandoci di configurare la maschera di ritaglio:
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);
Dato che non vorrai inserire tutti questi comandi <canvas>
manualmente, ho incluso un semplice parser di testo ombra nel codice sorgente della demo. In questo modo puoi fornire i comandi CSS e generare i comandi <canvas>
.
Ora gli elementi <canvas>
hanno un'intera gamma di stili a cui possono essere associati.
Questi stessi effetti di ombra possono essere utilizzati su qualsiasi oggetto vettoriale, dai WebFonts alle forme complesse importate da file SVG, alle forme vettoriali generative e così via.
Intermissione (tangente sull'aumento dei pixel)
Mentre scrivevo questa sezione dell'articolo, l'esempio stereoscopico mi ha incuriosito. Quanto è difficile creare un effetto di schermo di un film 3D utilizzando <canvas>
e due immagini scattate da prospettive leggermente diverse? A quanto pare, non troppo difficile. Il seguente kernel combina il canale rosso della prima immagine (data) con il canale ciano della seconda immagine (data2):
data[i] = data[i] * 255 / 0xFF;
data[i+1] = 255 * data2[i+1] / 0xFF;
data[i+2] = 255 * data2[i+2] / 0xFF;
Ora basta attaccare due iPhone alla fronte con del nastro adesivo, fare clic contemporaneamente su "Registra video" e creare i nostri filmati 3D in HTML5. Ci sono volontari?
Effetti di accodamento arcobaleno neon e riflessi zebrati
Collegare più effetti in <canvas>
può essere semplice, ma è necessaria una conoscenza di base dell'operazione composita globale (GCO). Per confrontare le operazioni con GIMP (o Photoshop): in <canvas>
esistono 12 GCO, più scuro e più chiaro possono essere considerati come modalità di miscela dei livelli; le altre 10 operazioni vengono applicate ai livelli come maschere alfa (un livello rimuove i pixel dell 'altro livello). L'operazione globalCompositeOperation lega i "livelli"
(o, nel nostro caso, le stringhe di codice) insieme, combinandoli in modi nuovi ed entusiasmanti:
Il grafico globalCompositeOperation mostra le modalità GCO in azione. Questo grafico utilizza una vasta porzione dello spettro di colori e più livelli di trasparenza alpha per vedere in dettaglio cosa aspettarsi. Ti consiglio di consultare il riferimento globaleCompositeOperation di Mozilla per le descrizioni testuali. Per ulteriori ricerche, puoi scoprire come funziona l 'operazione nel libro di Porter Duff Compositing Digital Images.
La mia modalità preferita è globalCompositeOperation="lighter". Lighter combina i pixel aggiunti in modo simile a come si combina la luce; quando la luce rossa, verde e bianca è a piena intensità, vediamo la luce bianca. È una funzionalità interessante da provare, soprattutto quando <canvas>
è impostato su un valore globaleAlpha basso, in quanto consente un controllo più preciso e bordi più lisci. La versione più leggera è stata utilizzata in molti modi, tra cui di recente è stato uno
creator di sfondo per desktop HTML5 che posso trovare all'indirizzo http://weavesilk.com/.
Una delle mie demo, Breathing Galaxies (JS1k),
utilizza anche la modalità più leggera. Se osservi i pattern di questi due esempi, inizi a vedere qual è l'effetto di questa modalità.
gestione del browser globalCompositeOperation.
Effetto Jitter neon-arcobaleno
Nella seguente demo, creeremo un effetto simile a Photoshop
sfarfalla-neon-arcobaleno con un contorno jittered, collegando gli effetti tra loro utilizzando
globalCompositeOperation (source-in, lighter e darker).
Questa demo è un'avanzamento della demo "Ombre di testo in <canvas>
",
che utilizza la stessa strategia per separare l'ombra dal testo (vedi la sezione precedente):
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();
};
Effetto riflesso zebra
L 'effetto di riflessione zebra è stato ispirato dalla eccellente risorsa di WebDesignerWall su come rendere più accattivante la tua pagina con il CSS. In questo modo, l'idea viene approfondita un po' di più, creando un "riflesso" del testo, come quello che potresti vedere su iTunes. L'effetto combina fillColor (bianco), createPattern (zebra.png) e linearGradient (brillo); questo illustra la possibilità di applicare più tipi di riempimento a ogni oggetto vettoriale:
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();
};
Ombreggiature interne/esterne in Canvas
Le specifiche di <canvas>
non trattano l 'argomento delle ombre "interne" rispetto a quelle "esterne". Infatti, a prima vista, potresti pensare che l'ombra"interna" non sia supportata. Non è così.
È solo un po ' più complicato da attivare ;) Come proposto in un post recente di F1LT3R, puoi creare ombre interne utilizzando le proprietà uniche delle regole di orientamento in senso orario e antiorario. Per farlo, crea un'"ombra interna" disegnando un rettangolo
contenitore e poi, utilizzando le regole di avvolgimento opposte, disegna una forma di ritaglio, creando
l'opposto della forma.
L'esempio seguente consente di stilizzare contemporaneamente inner-shadow e fillStyle con color+gradient+pattern. Puoi specificare la rotazione del motivo individualmente. Tieni presente che le strisce zebrate ora sono perpendicolari tra loro. Viene utilizzata una maschera di ritaglio delle dimensioni della scatola delimitante, eliminando la necessità di un contenitore molto grande per racchiudere la forma ritagliata, migliorando la velocità impedendo l'elaborazione delle parti non necessarie dell'ombra.
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();
};
Da questi esempi puoi vedere che, utilizzando globalCompositeOperation, possiamo concatenare gli effetti, producendo effetti più elaborati (utilizzando il mascheramento e la miscelazione). Lo schermo è tuo ;)
Spazio spaziale: effetti generativi
In <canvas>
, dal carattere Unicode 0x2708:
…a questo esempio ombreggiato:
…può essere ottenuto con più chiamate a ctx.strokeText()
con un tratto sottile (0,25),
riducendo lentamente l'offset x e l'alpha; dando ai nostri elementi vettoriali la sensazione di movimento.
Mappando la posizione XY degli elementi a un'onda sinusoidale/cosinusoidale e passando in rassegna i colori utilizzando la proprietà HSL, possiamo creare effetti più interessanti, come questo esempio di "pericolo biologico":
HSL: tonalità, saturazione, luminosità (1978)
HSL è un formato di recente introduzione nelle specifiche CSS3. Dove HEX è stato progettato per i computer, HSL è progettato per essere leggibile.
Illustrazione della semplicità dell 'HSL: per scorrere lo spettro dei colori, dobbiamo semplicemente incrementare la "tinta" da 360; la tinta viene mappata allo spettro in modo cilindrico. La luminosità controlla l'intensità del colore. Il 0% indica un pixel nero, mentre il 100% indica un pixel bianco. La saturazione controlla il livello di brillantezza o la brillantezza di un colore; i grigi vengono creati con una saturazione dello 0%, mentre i colori vivaci vengono creati con un valore del 100%.
Poiché HSL è uno standard recente, potresti voler continuare a supportare i browser meno recenti, il che è possibile tramite la conversione dello spazio di colore. Il seguente codice accetta un oggetto HSL { H: 360, S: 100, L: 100} e restituisce un oggetto RGB { R: 255, G: 255, B: 255 }. Da qui, puoi utilizzare questi valori per creare la tua stringa rgb o rgba. Per informazioni più approfondite, consulta l 'articolo informativo di Wikipedia su 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
};
};
Creazione di animazioni con requestAnimationFrame
In passato, per creare animazioni in JavaScript erano disponibili due opzioni: setTimeout
e setInterval
.
window.requestAnimationFrame
, è il nuovo standard che sostituirà entrambi, consentendo al browser di regolare le animazioni in base alle risorse disponibili e risparmiando l'elettricità mondiale (e qualche battito cardiaco del computer).
Ecco alcune importanti funzionalità:
- Quando un utente esce dal frame, l'animazione può rallentare o interrompersi completamente per evitare l'utilizzo di risorse non necessarie.
- Esiste un limite massimo alla frequenza frame di 60 FPS. Il motivo è che è molto più alto del livello che gli esseri umani possono percepire (la maggior parte delle persone considera "fluida " un'animazione a 30 FPS).
Al momento della stesura di questo articolo, per utilizzare requestAnimationFrame
sono necessari prefissi specifici del fornitore.
Paul Irish ha creato un livello shim con supporto cross-vendor in requestAnimationFrame per animazioni intelligenti:
// 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);
};
})();
Detto questo, il più ambizioso potrebbe collegare tutto questo a un polyfill come requestAnimationFrame.js (ci sono alcune funzionalità da capire) che supporterebbe ulteriormente i browser meno recenti, passando a questo nuovo 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);
})();
Codice sorgente
Con il supporto di tutti i fornitori di browser, non c'è dubbio sul futuro di <canvas>
: può essere portato agli eseguibili per iPhone/Android/computer utilizzando PhoneGap o