Effetti tipografici su canvas

Offerta Michael
Promozione di Michael

Il mio background

<canvas> ha cominciato a farsi conoscere nel 2006, quando è stato rilasciato Firefox v2.0. Un articolo su Ajaxian, che descrive la matrice di trasformazione, mi ha spinto a creare la mia prima app web <canvas>: Color sferica (2007). Questo mi ha fatto entrare nel mondo dei colori e delle primitive grafiche; ispirazione per la creazione di Sketchpad (2007-2008) nel tentativo di creare un'applicazione "meglio di Paint" nel browser. Questi esperimenti hanno portato alla creazione della startup Mugtug con il mio amico di lunga data Charles Pritchard. Stiamo sviluppando Darkroom in <canvas> HTML5. Darkroom è un'app non distruttiva per la condivisione di foto che combina la potenza dei filtri basati su pixel con la tipografia e il disegno vettoriali.

Introduzione

Immagine del banner su tela.

<canvas> consente ai programmatori JavaScript di avere il pieno controllo di colori, vettori e pixel sui loro schermi, ovvero la composizione visiva del monitor.

Gli esempi riportati di seguito riguardano un'area del sito <canvas> che non ha ricevuto molta attenzione; la creazione di effetti di testo. La varietà di effetti di testo che puoi creare in <canvas> è vastissima quanto puoi immaginare: in queste demo viene fornita una sottosezione delle possibilità offerte. Anche se in questo articolo ci occuperemo di "testo", i metodi possono essere applicati a qualsiasi oggetto vettoriale, creando immagini emozionanti nei giochi e in altre applicazioni:

Ombre di testo in <canvas>.
Effetti di testo di tipo CSS in <canvas>: creazione di maschere di ritaglio, ricerca di metriche in <canvas> e utilizzo della proprietà shadow.
Arcobaleno neon, riflessione delle zebre: effetti concatenati.
Effetti di testo simile a Photoshop in <canvas> esempi di utilizzo di globalCompositeOperation, createLinearGradient, createPattern.
Ombre interne ed esterne in <canvas>
Presentazione di una piccola caratteristica nota; utilizzo della rotazione in senso orario e antiorario per creare l'inversa di un'ombra (l'ombra interna).
Spaziatura: effetto generativo.
Effetto testo basato su generazioni in <canvas> che utilizza il ciclo di colore hsl() e window.requestAnimationFrame per creare la sensazione di movimento.

Ombre di testo in Canvas

Una delle mie aggiunte preferite alle specifiche CSS3 (insieme a raggio-bordo, gradienti web e altro) è la possibilità di creare ombre. È importante capire 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 testuali.

<canvas> ha un tipo di ombra, da usare per tutti gli oggetti vettori: ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.StrutText e così via. Per creare un'ombra in <canvas>, tocca queste quattro proprietà:

ctx.shadowColor = "red" // stringa
Colore dell'ombra: gli input RGB, RGBA, HSL, HEX e altri input sono validi.
ctx.shadowOffsetX = 0; // numero intero
Distanza orizzontale dell'ombra, rispetto al testo.
ctx.shadowOffsetY = 0; // numero intero
Distanza verticale dell'ombra, rispetto al testo.
ctx.shadowBlur = 10; // numero intero
Effetto di sfocatura all'ombra: maggiore è il valore, maggiore sarà la sfocatura.

Per iniziare, vediamo in che modo <canvas> può emulare gli effetti CSS. La ricerca di "css text-shadow" nelle immagini Google ci ha consentito di emulare alcune fantastiche demo: Line25, Stereoscopico e Shadow 3D.

Grafica 3D CSS

L'effetto 3D stereoscopico (per saperne di più, vedi Immagine anaglifica) è un esempio di semplice riga di codice, utilizzata in modo ottimale. Con la seguente riga di CSS, possiamo creare un'illusione di profondità se visualizzati con occhiali 3D rosso/ciano (quelli che offrono nei film in 3D):

text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;

Ci sono due cose da notare quando si converte questa stringa in <canvas>:

  1. Non esiste una sfocatura ombra (il terzo valore), quindi non c'è motivo di eseguire effettivamente l'ombreggiatura, 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>
  1. Gli EM non sono supportati in <canvas>, quindi dovranno essere convertiti in PX. Possiamo trovare il rapporto di conversione tra PT, PC, EM, EX, PX e così via creando un elemento con le stesse proprietà font in DOM e impostando la larghezza sul formato da misurare; o istanza, per acquisire la conversione EM -> PX, misureremo l'elemento DOM con un'"altezza: 1em", l'importo di ogni offset EMX
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>

Prevenzione della moltiplicazione alfa

In un esempio più complesso, come l'effetto Neon presente su Line25, la proprietà shadowBlur deve essere utilizzata per emulare correttamente l'effetto. Poiché l'effetto Neon si basa su più ombre, si verifica un problema; in <canvas> ogni oggetto vettoriale può avere una sola ombra. Quindi, per disegnare più ombre, devi disegnare più versioni del testo sopra se stesso. Questo comporta una moltiplicazione alfa e, in definitiva, bordi frastagliati.

Grafica neon

Ho provato a eseguire ctx.fillStyle = "rgba(0,0,0,0)" o "transparent" per nascondere il testo, visualizzando al contempo l'ombra... ma questo tentativo è stato inutile; poiché l'ombra è una moltiplicazione della alpha FillStyle, l'ombra non può mai essere più opaca di FillStyle.

Fortunatamente, esiste un modo per aggirarla. Possiamo disegnare lo scostamento delle ombre dal testo, mantenendoli separati (in modo che non si sovrappongano) e nascondendo così il testo fuori dal 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 evitare un po' più di pulizia, possiamo impedire che il FillText venga disegnato al primo posto (consentendo di disegnare l'ombra) aggiungendo un percorso di ritaglio. Per creare un percorso di ritaglio intorno al testo, dobbiamo conoscere l'altezza del testo (storicamente chiamata "em-height", 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, con la compromissione CSS (consulta Metriche tipografiche per scoprire altri modi per correggere implementazioni precedenti di <canvas> con le misurazioni CSS), possiamo trovare l'altezza del testo misurando il offsetHeight di un <span> con le stesse proprietà font:

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();

Unendo tutto insieme e ottimizzando man mano che procediamo, se un'ombra non presenta sfocature, si può usare FillText per ottenere lo stesso effetto, evitando di impostare 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);

Poiché non hai intenzione di inserire tutti questi comandi <canvas> manualmente, ho incluso un semplice parser text-shadow nella sorgente demo; in questo modo puoi fornire i comandi CSS e fare in modo che generi i comandi <canvas>. Ora gli elementi <canvas> hanno un'ampia gamma di stili a cui è possibile associarsi. Puoi usare gli stessi effetti ombra su qualsiasi oggetto vettoriale, dai caratteri web alle forme complesse importate da file SVG, alle forme vettoriali generative e così via.

Ombra del testo negli effetti canvas

Intervallo (tangente allo spinta del pixel)

Durante la stesura di questa sezione dell'articolo, l'esempio stereoscopico mi ha incuriosito. Quanto sarebbe difficile creare un effetto sullo schermo di un filmato 3D usando <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 (dati) 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, qualcuno deve semplicemente collegare due iPhone alla fronte e fare clic contemporaneamente su "Registra video" per poter creare i nostri filmati 3D in HTML5. Ci sono volontari?

occhiali 3D

Arcobaleno neon, riflessione a forma di zebra: collegamento di effetti

Concatenare più effetti in <canvas> può essere semplice, ma è necessaria una conoscenza di base di globalCompositeOperation (GCO). Per confrontare le operazioni con GIMP (o Photoshop): ci sono 12 GCO in <canvas> più scuro, e più chiaro può essere considerato come modalità di fusione dei livelli; le altre 10 operazioni vengono applicate ai livelli come maschere alfa (un livello rimuove i pixel dell 'altro livello). GlobalCompositeOperation mette insieme dei "livelli" (o, nel nostro caso, stringhe di codice) combinandoli in modi nuovi ed entusiasmanti:

Grafica degli effetti di catena

Il grafico globalCompositeOperation mostra le modalità GCO in uso; questo grafico utilizza un'ampia parte dello spettro di colori e più livelli di trasparenza alfa per capire in dettaglio cosa aspettarsi. Ti consiglio di dare un 'occhiata al riferimento globaleCompositeOperation di Mozilla per le descrizioni testuali. Per ulteriori ricerche, puoi scoprire come funziona l 'operazione nella sezione Compositing Digital Images di Porter Duff.

La mia modalità preferita è globalCompositeOperation="lighter". L'opzione più chiara mescola i pixel aggiunti in modo simile a come la luce si mescola; quando la luce rossa, verde e bianca sono alla massima intensità, si vede luce bianca. È una funzionalità entusiasmante con cui sperimentare, soprattutto quando <canvas> è impostato su un livello alpha globale basso, consentendo un controllo più preciso e bordi più fluidi. Più leggero è stato usato per molti usi. Di recente il mio preferito è un creator di sfondi desktop HTML5 disponibile all'indirizzo http://weavesilk.com/. Una delle mie demo, Breathing Galaxies (JS1k), utilizza anche la modalità più leggera. Grazie agli schemi di questi due esempi, puoi iniziare a vedere l'effetto di questa modalità.

Gestione del browser globaleCompositeOperation.

Effetto tremolio arcobaleno- neon

Nella demo seguente, otterremo un bagliore arcobaleno simile a Photoshop con un contorno tremolante, concatenando gli effetti utilizzando la globaleCompositeOperation (sorgente, più chiaro e più scuro). Questa demo è un avanzamento della demo "Text-Shadows in <canvas>" e utilizza la stessa strategia per separare l'ombra dal testo (vedi la sezione precedente):

Tremolio arcobaleno
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 Riflesso Zebra è stato ispirato dall'ottima risorsa di WebDesignerWall su come dare una marcia in più alla tua pagina con CSS. Questo porta l'idea un po' più a fondo, creando una "riflesso" per il testo, come ciò che potresti vedere in iTunes. L'effetto combina fillColor (bianco), createPattern (zebra.png) e linearGradient (shine); questo illustra la possibilità di applicare più tipi di riempimento a ciascun oggetto vettoriale:

Effetto zebra
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();
};

Ombre interne/esterne in Canvas

Le specifiche di <canvas> non menzionano il soggetto delle ombre "interne" o "esterne". Infatti, alla prima apparizione, probabilmente l'ombreggiatura"interna" non è supportata. Non è così. L'attivazione è un po' più complessa ;) Come proposto in un recente post di F1LT3R, puoi creare ombre interne utilizzando le proprietà uniche delle regole di avvolgimento in senso orario e antiorario. Per farlo, crea un'"ombra interna" disegnando un rettangolo del contenitore e poi, utilizzando regole di avvolgimento opposte, disegna una forma di ritaglio, creando l'inversa della forma.

L'esempio seguente consente di stilizzare contemporaneamente l'ombra interna e FillStyle con colore, gradiente e motivo. Puoi specificare la rotazione dello schema singolarmente; tieni presente che le strisce a strisce ora sono perpendicolari l'una all'altra. Viene utilizzata una maschera di ritaglio delle dimensioni del riquadro di delimitazione, in modo da non avere bisogno di un container di grandi dimensioni per racchiudere la forma di ritaglio, migliorando la velocità impedendo l'elaborazione delle parti non necessarie dell'ombra.

Ombre interne/esterne
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, utilizzando globalCompositeOperation, che possiamo concatenare gli effetti, producendo effetti più elaborati (utilizzando mascheramento e combinazione). Lo schermo è tuo. ;)

Spazio spaziale: effetti generativi

In <canvas>, dal carattere Unicode 0x2708:

Gfaphic Unicode

... a questo esempio ombreggiato:

Esempio di ombreggiatura

...può essere raggiunto tramite più chiamate a ctx.strokeText() con una larghezza di linea sottile (0,25), diminuendo lentamente l'offset x e l'alfa, dando ai nostri elementi vettoriali la sensazione di movimento.

Mappando la posizione XY degli elementi a un'onda seno/coseno e scorrendo i colori utilizzando la proprietà HSL, possiamo creare effetti più interessanti, come questo esempio di "pericolo biologico":

Effetto ciclismo HSL

HSL: Tonalità, saturazione, luminosità (1978)

HSL è un nuovo formato supportato nelle specifiche CSS3. Se HEX è progettato per i computer, HSL è progettato per essere leggibile.

Per illustrare la semplicità dell 'HSL, per scorrere lo spettro cromatico dobbiamo semplicemente incrementare la "tonalità" da 360; la tonalità è mappata allo spettro in modo cilindrico. La luminosità controlla la luminosità del colore: 0% indica un pixel nero, 100% indica un pixel bianco. La saturazione controlla il livello di brillantezza o intensità di un colore. I grigi vengono creati con una saturazione dello 0%, mentre i colori vivaci vengono creati utilizzando un valore pari al 100%.

Grafica HSL

Poiché HSL è uno standard recente, ti consigliamo di continuare a supportare i browser meno recenti, il che è possibile tramite la conversione dello spazio 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 usare quei valori per creare la tua stringa RGB o RGB. Per informazioni più dettagliate, consulta l 'articolo dettagliato 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 sostituisce entrambi e permette di risparmiare energia elettrica (e qualche battito cardiaco) sul computer consentendo al browser di regolare le animazioni in base alle risorse disponibili. Alcune funzionalità importanti includono:

  • Quando un utente esiste il frame, l'animazione può rallentare o interrompersi completamente per evitare l'uso di risorse non necessarie.
  • Esiste un limite di frequenza fotogrammi a 60 f/s. Il motivo è che si trova ben al di sopra del livello che gli esseri umani possono notare (la maggior parte degli esseri umani a 30 FPS nota che l 'animazione è "fluida").

Al momento della scrittura, è necessario utilizzare i prefissi specifici del fornitore per l'utilizzo di requestAnimationFrame. Paul Ireland ha creato un livello shim con supporto cross-vender, in requestAnimationFrame per l'animazione intelligente:

// 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);
        };
})();

Andando un po' oltre, la soluzione più ambiziosa potrebbe collegare tutto questo a un polyfill come requestAnimationFrame.js (ci sono alcune funzionalità da elaborare) che supporteranno 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);
})();
Immagine sfocatura note
Grafica animazione
Grafica matrice

Codice sorgente

Grazie al supporto di tutta la sfera del fornitore di browser, non c'è dubbio sul futuro di <canvas> che potrà essere trasferito negli eseguibili di iPhone/Android/Desktop utilizzando PhoneGap, oppure

Titanio.