Il mio background
<canvas> è entrato nella mia sfera di interesse nel 2006, quando è stato rilasciato Firefox v2.0. Un articolo su Ajaxian, che descriveva la matrice di trasformazione, mi ha ispirato a creare la mia prima app web <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 creare un'applicazione "migliore 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 HTML5 <canvas>. Darkroom è un'app di condivisione di foto non distruttiva che combina la potenza dei filtri basati su pixel con la tipografia e il disegno basati su vettori.
Introduzione
<canvas> offre ai programmatori JavaScript il controllo completo di colori, vettori e pixel sui loro schermi, ovvero la composizione visiva del monitor.
I seguenti esempi 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> è vastissima. Queste demo coprono una parte di ciò che è possibile fare. Anche se in questo articolo ci occupiamo di "testo", i metodi possono essere applicati a qualsiasi oggetto vettoriale, creando immagini accattivanti nei giochi e in altre applicazioni:
- Ombreggiatura del testo in
<canvas>. - Effetti di testo simili a CSS in
<canvas>, creazione di maschere di ritaglio, ricerca di metriche in<canvas>e utilizzo della proprietà shadow. - Neon-rainbow, zebra-reflection - chaining effects.
- Effetti di testo simili a Photoshop in
<canvas>esempi di utilizzo di globalCompositeOperation, createLinearGradient, createPattern. - Ombre interne ed esterne in
<canvas> - Rivelazione di una funzionalità poco nota: l'utilizzo dell'avvolgimento in senso orario e antiorario per creare l'inverso di un'ombra (l'ombra interna).
- Spaceage - effetto generativo.
- Effetto di testo basato sulla generazione in
<canvas>utilizzando il ciclo di colori hsl() ewindow.requestAnimationFrameper creare la sensazione di movimento.
Ombreggiature testo nel canvas
Una delle mie aggiunte preferite alle specifiche CSS3 (insieme a border-radius, web-gradients e altre) è la possibilità di creare ombre. È importante comprendere 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, che 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" // string
- Colore dell'ombra; sono validi gli input RGB, RGBA, HSL, HEX e altri.
- ctx.shadowOffsetX = 0; // integer
- Distanza orizzontale dell'ombra rispetto al testo.
- ctx.shadowOffsetY = 0; // integer
- Distanza verticale dell'ombra rispetto al testo.
- ctx.shadowBlur = 10; // integer
- Effetto di sfocatura dell'ombra. Più alto è il valore, maggiore è la sfocatura.
Per iniziare, vediamo come <canvas> può emulare gli effetti CSS.
La ricerca su Google Immagini di "css text-shadow" ha portato ad alcune ottime demo da emulare: Line25, Stereoscopic e Shadow 3D.
L'effetto 3D stereoscopico (per saperne di più, consulta Immagine anaglifica) è un esempio di una semplice riga di codice utilizzata in modo ottimale. Con la seguente riga di CSS, possiamo creare l'illusione della profondità se visualizzata con occhiali 3D rosso/ciano (quelli che ti danno al cinema 3D):
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
Ci sono due aspetti da notare quando si converte questa stringa in <canvas>:
- Non esiste un valore shadow-blur (il terzo valore), quindi non c'è motivo di eseguire effettivamente shadow, 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>
- Le unità EM non sono supportate in
<canvas>, quindi dovranno essere convertite in 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à dei caratteri nel DOM e impostando la larghezza sul formato da misurare; ad esempio, per acquisire la conversione EM -> PX, misureremmo l 'elemento DOM con "height: 1em", l 'offsetHeight risultante sarebbe 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>
Prevenzione della moltiplicazione alfa
In un esempio più complesso, come l'effetto Neon presente su Line25, è necessario utilizzare la proprietà shadowBlur per emulare l'effetto correttamente. 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 una sopra l'altra.
Ciò comporta la moltiplicazione alfa e, in ultima analisi, bordi frastagliati.
Ho provato a eseguire ctx.fillStyle = "rgba(0,0,0,0)" o "transparent" per nascondere il testo, visualizzando l'ombra… tuttavia, questo tentativo è stato inutile; poiché l'ombra è una moltiplicazione dell'alpha di fillStyle, l'ombra non può mai essere più opaca di fillStyle.
Fortunatamente, esiste un modo per aggirare questo 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 ripulire un po' il tutto, possiamo impedire che fillText venga disegnato in primo luogo (consentendo al contempo il disegno dell'ombra) aggiungendo un percorso di ritaglio.
Per creare un tracciato di ritaglio che circondi il testo, dobbiamo conoscere l'altezza del testo (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, ma ctx.measureText().height non esiste.
Fortunatamente, tramite l'hack-ardry CSS (consulta Metriche tipografiche per altri modi per correggere le implementazioni precedenti di <canvas> utilizzando le misurazioni CSS), possiamo trovare l'altezza del testo misurando l'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();
Per unire il tutto e ottimizzare man mano, se un'ombra non ha sfocatura, fillText può essere utilizzato per lo stesso effetto, risparmiandoci la configurazione della 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 vorrai inserire manualmente tutti questi comandi <canvas>, ho incluso un semplice parser di ombreggiatura del testo nell'origine della demo; in questo modo puoi inserire i comandi CSS e generare i comandi <canvas>.
Ora i nostri elementi <canvas> hanno un'intera gamma di stili a cui possono essere collegati.
Questi stessi effetti ombra possono essere utilizzati su qualsiasi oggetto vettoriale, dai WebFont alle forme complesse importate da SVG, alle forme vettoriali generative e così via.
Intervallo (digressione sul pixel-pushing)
Durante la stesura di questa sezione dell'articolo, l'esempio stereoscopico ha suscitato la mia curiosità. Quanto sarebbe difficile creare un effetto schermo cinematografico 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 che qualcuno fissi due iPhone alla fronte con del nastro adesivo, faccia clic su "Registra video" contemporaneamente e potremo realizzare i nostri film in 3D in HTML5. Qualche volontario?
Neon-rainbow, zebra-reflection—chaining effects
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): esistono 12 GCO in <canvas>
darker e lighter possono essere considerati 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 unisce i "livelli" (o, nel nostro caso, le stringhe di codice), combinandoli in modi nuovi ed entusiasmanti:
Il grafico globalCompositeOperation mostra le modalità GCO in azione. Questo grafico utilizza gran parte dello spettro dei colori e più livelli di trasparenza alfa per mostrare in dettaglio cosa aspettarsi. Ti consiglio di consultare il riferimento globalCompositeOperation di Mozilla per le descrizioni testuali. Per ulteriori ricerche, puoi scoprire come funziona l 'operazione in Compositing Digital Images di Porter Duff.
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 è alla massima intensità, vediamo la luce bianca. È una funzionalità interessante con cui sperimentare, soprattutto quando <canvas> è impostato su un valore globalAlpha basso, consentendo un controllo più preciso e bordi più uniformi. Lighter è stato utilizzato per molti scopi, il mio preferito di recente è un
creatore di sfondi per computer HTML5 disponibile all'indirizzo http://weavesilk.com/.
Anche una delle mie demo, Breathing Galaxies (JS1k), utilizza la modalità più chiara. Se osservi i pattern di questi due esempi, inizi a capire l'effetto di questa modalità.
Gestione del browser globalCompositeOperation.
Effetto jitter arcobaleno neon
Nella demo seguente, otterremo un bagliore arcobaleno neon simile a Photoshop con un contorno con jitter, concatenando gli effetti utilizzando globalCompositeOperation (source-in, lighter e darker).
Questa demo è una progressione della demo "Ombreggiatura del testo in <canvas>",
utilizzando 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 zebrato
L'effetto Zebra Reflection è stato ispirato dall'eccellente risorsa di WebDesignerWall su come rendere più accattivante la tua pagina con CSS. In questo modo, l'idea viene sviluppata un po' di più, creando un "riflesso" per il testo, come quello che potresti vedere in iTunes. L'effetto combina fillColor (bianco), createPattern (zebra.png) e linearGradient (lucentezza); 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();
};
Ombre interne/esterne nel canvas
Le specifiche di <canvas> non toccano l 'argomento delle ombre "interne" rispetto a quelle "esterne". In effetti, a prima vista, potresti pensare che l'ombra"interna" non sia supportata. Non è così.
È solo un po ' più difficile da attivare ;) 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 contenitore e poi, utilizzando regole di avvolgimento opposte, disegna una forma ritagliata, creando l'inverso della forma.
L'esempio seguente consente di applicare lo stile a inner-shadow e fillStyle con colore, gradiente e pattern contemporaneamente. Puoi specificare la rotazione del pattern singolarmente. Nota che le strisce zebrate ora sono perpendicolari tra loro. Viene utilizzata una maschera di ritaglio delle dimensioni del riquadro di delimitazione, eliminando la necessità di un contenitore di grandi dimensioni per racchiudere la forma ritagliata, migliorando la velocità ed evitando 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 mascheramento e fusione). Lo schermo è il tuo regno ;)
Spaceage: effetti generativi
In <canvas>, partendo dal carattere Unicode 0x2708:
…a questo esempio ombreggiato:
…può essere ottenuto con più chiamate a ctx.strokeText() con un lineWidth sottile (0,25),
diminuendo lentamente l'offset x e l'alpha, dando ai nostri elementi vettoriali la sensazione di movimento.
Mappando la posizione XY degli elementi su un'onda sinusoidale/cosinusoidale e scorrendo i colori utilizzando la proprietà HSL, possiamo creare effetti più interessanti, come questo esempio di "rischio biologico":
HSL: tonalità, saturazione, luminosità (1978)
HSL è un formato supportato di recente nelle specifiche CSS3. Mentre HEX è stato progettato per i computer, HSL è progettato per essere leggibile dalle persone.
Per illustrare la facilità di HSL, per scorrere lo spettro dei colori, incrementiamo semplicemente la "tonalità" da 360; la tonalità viene mappata allo spettro in modo cilindrico. La luminosità controlla quanto è scuro/chiaro il colore; 0% indica un pixel nero, mentre 100% indica un pixel bianco. La saturazione controlla la luminosità o la vivacità di un colore; i grigi vengono creati con una saturazione dello 0%, mentre i colori vivaci vengono creati utilizzando 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 dei colori. 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 stringa rgb o rgba. Per informazioni più approfondite, consulta l 'articolo 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, c'erano due opzioni: setTimeout
e setInterval.
window.requestAnimationFrame è il nuovo standard che sostituisce entrambi, risparmiando elettricità (e qualche battito al tuo computer) consentendo al browser di regolare le animazioni in base alle risorse disponibili.
Alcune funzionalità importanti includono:
- Quando un utente esce dall'inquadratura, l'animazione può rallentare o interrompersi completamente per evitare l'utilizzo di risorse non necessarie.
- Esiste un limite massimo per la frequenza fotogrammi di 60 FPS. Il motivo è che è ben al di sopra del livello che gli esseri umani possono notare (la maggior parte degli esseri umani vede l 'animazione come "fluida" a 30 f/s).
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 for smart animating:
// 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, i più ambiziosi potrebbero collegare questo aspetto a un polyfill come requestAnimationFrame.js (ci sono alcune funzionalità da elaborare) che supporterebbe i browser meno recenti in misura maggiore, passando al contempo 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 ci sono dubbi sul futuro di <canvas>, che può essere trasferito ai file eseguibili di iPhone/Android/desktop utilizzando PhoneGap o