Efeitos tipográficos no canvas

Meu plano de fundo

<canvas> entrei no meu conhecimento em 2006, quando o Firefox v2.0 foi lançado. Um artigo sobre Ajaxiano (link em inglês), descrevendo a matriz de transformação, me inspirou a criar meu primeiro <canvas> app da Web: Color Sphere (2007). Isso me mergulheu no mundo das cores e dos primitivos gráficos, inspirando a criação do Sketchpad (2007-2008) na tentativa de reunir um aplicativo "melhor que o Paint" no navegador. Esses experimentos acabaram levando à criação da startup Mugtug com meu amigo de longa data Charles Pritchard. Estamos desenvolvendo o Darkroom em HTML5 <canvas>. O Darkroom é um app não destrutivo de compartilhamento de fotos que combina o poder dos filtros baseados em pixels com tipografia e desenho baseados em vetores.

Introdução

Gráfico do banner da tela.

O <canvas> oferece aos programadores de JavaScript controle total sobre as cores, vetores e pixels das telas, ou seja, a composição visual do monitor.

Os exemplos a seguir tratam de uma área em <canvas> que não chamou muita atenção, criando efeitos de texto. A variedade de efeitos de texto que podem ser criados no <canvas> é tão vasta quanto você pode imaginar. Essas demonstrações abrangem uma subseção do que é possível. Embora estejamos falando de "texto" neste artigo, os métodos podem ser aplicados a qualquer objeto vetorial, criando recursos visuais incríveis em jogos e outros aplicativos:

Sombras de texto em <canvas>.
Efeitos de texto semelhantes a CSS no <canvas>, criando máscaras de corte, encontrando métricas em <canvas> e usando a propriedade de sombra.
Arco-íris, reflexão de zebra - efeitos de encadeamento.
Efeitos de texto semelhantes ao Photoshop em exemplos de <canvas> de uso de globalCompositeOperation, createLinearGradient e createPattern.
Sombras internas e externas em <canvas>
Revelação de um recurso pouco conhecido, uso de movimento no sentido horário ou anti-horário para criar o inverso de uma sombra projetada (a sombra interna).
Espaçamento: efeito generativo.
Efeito de texto baseado em geração de <canvas> usando o ciclo de cores hsl() e window.requestAnimationFrame para criar a sensação de movimento.

Sombras de texto no Canvas

Uma das minhas adições favoritas às especificações do CSS3 (com raio de borda, gradientes da Web e outras) é a capacidade de criar sombras. É importante entender as diferenças entre as sombras de CSS e <canvas>, especificamente:

O CSS usa dois métodos: box-shadow para elementos de caixa, como div, span e assim por diante, e text-shadow para conteúdo de texto.

<canvas> tem um tipo de sombra, que é usado para todos os objetos vetoriais: ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.strokeText e assim por diante. Para criar uma sombra em <canvas>, toque nestas quatro propriedades:

ctx.shadowColor = "vermelho" // string
Cor da sombra. RGB, RGBA, HSL, HEX e outras entradas são válidas.
ctx.shadowOffsetX = 0; // inteiro
Distância horizontal da sombra em relação ao texto.
ctx.shadowOffsetY = 0; // inteiro
Distância vertical da sombra em relação ao texto.
ctx.shadowBlur = 10; // número inteiro
Efeito de desfoque da sombra, quanto maior o valor, maior o desfoque.

Para começar, veja como o <canvas> pode emular efeitos de CSS. A pesquisa de "css text-shadow" nas imagens do Google nos levou a algumas ótimas demonstrações para emular: Line25, Stereoscopic e Shadow 3D.

Gráfico 3D do CSS

O efeito 3D estereoscópico (consulte Imagem analífica para mais informações) é um exemplo de uma linha de código simples, muito bem utilizada. Com a linha de CSS a seguir, podemos criar a ilusão de profundidade quando visualizada com óculos 3D vermelho/ciano (o tipo que eles oferecem em filmes 3D):

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

Há duas coisas a serem observadas ao converter essa string para <canvas>:

  1. Não há desfoque de sombra (o terceiro valor), portanto, não há motivo para executar a sombra, já que fillText criaria os mesmos resultados:
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. EMs não são compatíveis com <canvas>, então precisam ser convertidos em PX. Podemos encontrar a taxa de conversão para conversão entre PT, PC, EM, EX, PX e assim por diante, criando um elemento com as mesmas propriedades de fonte no DOM e definindo a largura como o formato a ser medido. ou, por exemplo, para capturar a conversão de EM -> PX, mediríamos o elemento DOM com "height: 1em", o deslocamento resultante em cada PXEM.
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>

Como evitar a multiplicação alfa

Em um exemplo mais complexo, como o efeito de neon encontrado em Line25, a propriedade shadowBlur precisa ser usada para emular o efeito corretamente. Como o efeito de neon depende de várias sombras, encontramos um problema. Em <canvas>, cada objeto vetorial pode ter apenas uma sombra. Portanto, para desenhar várias sombras, é necessário desenhar várias versões do texto em cima dele mesmo. Isso resulta em multiplicação alfa e, por fim, em bordas irregulares.

Gráfico de neon

Tentei executar ctx.fillStyle = "rgba(0,0,0,0)" ou "transparent" para ocultar o texto enquanto exibia a sombra. No entanto, essa tentativa foi em vão. Como a sombra é uma multiplicação do alfa fillStyle, a sombra nunca pode ser mais opaca que o fillStyle.

Felizmente, há uma maneira de contornar isso. Podemos desenhar o deslocamento da sombra do texto, mantendo-os separados (para que não se sobreponham) e, assim, ocultando o texto na lateral da tela:

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

Corte ao redor de um bloco de texto

Para limpar um pouco as coisas, podemos evitar que o fillText seja desenhado em primeiro lugar (ao mesmo tempo que permita que a sombra seja desenhada) adicionando um caminho de corte. Para criar um caminho de corte ao redor do texto, precisamos saber a altura do texto (chamada de "em-height" historicamente a altura da letra "M" em uma impressão) e a largura do texto. Podemos conseguir a largura usando ctx.measureText().width. No entanto, ctx.measureText().height não existe.

Felizmente, com as técnicas de CSS (consulte Métricas tipográficas para ver mais formas de corrigir implementações mais antigas de <canvas> usando medições CSS), podemos encontrar a altura do texto medindo o offsetHeight de um <span> com as mesmas propriedades de fonte:

var d = document.createElement("span");
d.font = "20px arial"
d.textContent = "Hello world!"
var emHeight = d.offsetHeight;

A partir daí, podemos criar um retângulo para usar como caminho de corte, colocando a "sombra" e removendo a forma fictícia.

ctx.rect(0, 0, width, emHeight);
ctx.clip();

Combinando tudo e otimizando à medida que avançamos, se uma sombra não tiver desfoque, fillText poderá ser usado para o mesmo efeito, evitando a configuração da máscara de recorte:

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

Como você não vai inserir todos esses comandos <canvas> manualmente, incluí um analisador de sombra de texto simples na fonte da demonstração. Dessa forma, você pode alimentá-lo com comandos CSS e fazer com que ele gere comandos <canvas>. Agora, nossos elementos <canvas> têm uma grande variedade de estilos que podem ser vinculados. Esses mesmos efeitos de sombra podem ser usados em qualquer objeto vetorial, desde WebFonts até formas complexas importadas de SVGs, formas vetoriais generativas e assim por diante.

Sombra do texto em efeitos de tela

Intermissão (tangente ao envio de pixels)

Ao escrever esta seção do artigo, o exemplo estereoscópico me deixou curioso. Seria difícil criar um efeito de tela de filme 3D usando <canvas> e duas imagens tiradas de perspectivas ligeiramente diferentes? Aparentemente, não é muito difícil. O kernel a seguir combina o canal vermelho da primeira imagem (dados) com o canal ciano da segunda imagem (data2):

data[i] = data[i] * 255 / 0xFF;
data[i+1] = 255 * data2[i+1] / 0xFF;
data[i+2] = 255 * data2[i+2] / 0xFF;

Agora, alguém precisa colocar dois iPhones na testa com fita adesiva, clicar em "gravar vídeo" ao mesmo tempo, e nós podemos fazer nossos próprios filmes 3D em HTML5. Algum voluntário?

óculos 3D

Arco-íris de neon, reflexão de zebra - efeitos de encadeamento

O encadeamento de vários efeitos em <canvas> pode ser simples, mas é necessário ter um conhecimento básico de globalCompositeOperation (GCO). Para comparar as operações com o GIMP (ou Photoshop): há 12 GCOs em <canvas> mais escuro, e mais claro pode ser considerado como modos de combinação de camadas. As outras 10 operações são aplicadas às camadas como máscaras alfa (uma camada remove os pixels da outra camada). globalCompositeOperation une "camadas" (ou, em nosso caso, strings de código), combinando-as de maneiras novas e interessantes:

Encadeamento de gráficos de efeitos

O gráfico globalCompositeOperation mostra os modos de GCO em funcionamento. Ele usa uma grande parte do espectro de cores e vários níveis de transparência alfa para ver em detalhes o que esperar. Recomendamos que você consulte a referência globalCompositeOperation do Mozilla para ver descrições textuais. Para pesquisas adicionais, saiba como a operação funciona em Compositing Digital Images (link em inglês), de Porter Duff.

Meu modo favorito é globalCompositeOperation="lighter". Lighter mistura os pixels anexados de forma semelhante à mistura de luz. Quando a luz vermelha, verde e branca estão com intensidade máxima, vemos a luz branca. É um recurso interessante de testar, principalmente quando <canvas> está definido como um globalAlpha baixo, permitindo um controle mais preciso e bordas mais suaves. O Lighter tem muitos usos. Meu favorito recente é um criador de plano de fundo HTML5 para área de trabalho, que pode ser encontrado em http://weavesilk.com/ (em inglês). Uma das minhas demonstrações, Breathing Galaxies (JS1k), também usa o modo mais claro, desenhando padrões desses dois exemplos. Você começa a ver o efeito desse modo.

Processamento do navegador globalCompositeOperation.

Efeito de instabilidade de arco-íris neon

Na demonstração a seguir, vamos conseguir um brilho de arco-íris neon semelhante ao Photoshop com um contorno instável, encadeando efeitos usando globalCompositeOperation (source-in, lighter e mais escuro). Esta demonstração é uma progressão da demonstração "Sombras de texto em <canvas>", usando a mesma estratégia para separar a sombra do texto (consulte a seção anterior):

Instabilidade do arco-íris
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();
};

Efeito de reflexão de zebra

O efeito de reflexão de zebra foi inspirado no excelente recurso do WebDesignerWall sobre como incrementar sua página com CSS. Isso leva a ideia um pouco mais longe, criando uma "reflexão" para o texto, como o que você pode encontrar no iTunes. O efeito combina fillColor (branco), createPattern (zebra.png) e linearGradient (brilhante). Isso ilustra a capacidade de aplicar vários tipos de preenchimento a cada objeto vetorial:

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

Sombras internas/externas no Canvas

As especificações de <canvas> não tocam no assunto de sombras "internas" e "externas". Na verdade, à primeira aparência, você pode esperar que a sombra "interna" não seja compatível. Esse não é o caso. É um pouco mais complicado ativar . Para fazer isso, crie uma "sombra interna" desenhando um retângulo de contêiner e, usando regras de enrolamento opostos, desenhe uma forma de recorte, criando o inverso da forma.

O exemplo a seguir permite que a sombra interna e o fillStyle sejam estilizados com cor + gradiente + padrão simultaneamente. É possível especificar a rotação do padrão individualmente. Observe que as listras de zebra agora são perpendiculares umas às outras. Uma máscara de recorte do tamanho da caixa delimitadora é usada, eliminando a necessidade de um contêiner muito grande para incluir a forma de recorte. Isso melhora a velocidade ao impedir que partes desnecessárias da sombra sejam processadas.

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

A partir desses exemplos, como é possível ver, com o uso de globalCompositeOperation, podemos encadear efeitos, produzindo efeitos mais elaborados (usando o mascar e a mistura). A tela é um ótimo exemplo ;)

Espaçamento – efeitos generativos

Em <canvas>, a partir do caractere Unicode 0x2708:

Unicode gfaphic

... a este exemplo sombreado:

Exemplo sombreado

...podem ser alcançados por várias chamadas para ctx.strokeText() com uma lineWidth fina (0,25), enquanto diminuem lentamente o deslocamento x e o alfa, proporcionando a sensação de movimento aos nossos elementos vetoriais.

Ao mapear a posição XY dos elementos para uma onda de seno/cosseno e alternar entre cores usando a propriedade HSL, podemos criar efeitos mais interessantes, como este exemplo de "perigo biológico":

Efeito de ciclismo HSL

HSL: Hue, Saturation, Lightness (1978)

HSL é um formato aceito recentemente nas especificações do CSS3. Quando o HEX foi projetado para computadores, o HSL é projetado para ser legível por humanos.

Ilustração da facilidade do HSL: para percorrer o espectro de cores, nós simplesmente incrementamos a "matiz" de 360; a matiz é mapeada para o espectro de modo cilíndrico. A iluminação controla o quanto a cor é escura/clara. 0% indica um pixel preto, enquanto 100% indica um pixel branco. A saturação controla se uma cor é brilhante ou vívida. O cinza é criado com uma saturação de 0%, e cores vívidas são criadas usando um valor de 100%.

Gráfico HSL

Como HSL é um padrão recente, você pode continuar oferecendo suporte a navegadores mais antigos, o que é possível por meio da conversão do espaço de cor. O código a seguir aceita um objeto HSL { H: 360, S: 100, L: 100} e gera um objeto RGB { R: 255, G: 255, B: 255 }. A partir daí, você pode usar esses valores para criar sua string RGB ou rgba. Para informações mais detalhadas, consulte o artigo útil da Wikipédia sobre 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
};
};

Como criar animações com requestAnimationFrame

Anteriormente, para criar animações em JavaScript, havia duas opções: setTimeout e setInterval.

O window.requestAnimationFrame é o novo padrão para substituir ambos, economizando a eletricidade mundial (e alguns batimentos do computador), permitindo que o navegador regule as animações com base nos recursos disponíveis. Alguns recursos importantes incluem:

  • Quando o frame é disponibilizado pelo usuário, a animação pode desacelerar ou parar completamente para evitar o uso de recursos desnecessários.
  • O frame rate tem um limite de 60 QPS. O motivo para isso é que está bem acima do nível que os humanos podem notar (a maioria dos humanos por 30 QPS considera a animação "fluida").

No momento em que este artigo foi escrito, os prefixos específicos do fornecedor precisam usar requestAnimationFrame. Paul Ireland criou uma camada shim que tem suporte para vários fornecedores em requestAnimationFrame for smart animating (link em inglês):

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

Indo um pouco mais além, mais ambicioso pode vincular isso a um poly-fill, como requestAnimationFrame.js (há alguns recursos a serem resolvidos) que oferece suporte a navegadores mais antigos para um nível maior de suporte durante a mudança para esse novo padrão.

(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);
})();
Gráfico de desfoque de notas
Gráfico de animação
Gráfico de matriz

Código-fonte

Com suporte em todos os fornecedores de navegadores, não há dúvida sobre o futuro do <canvas>. Ele pode ser transferido para os executáveis do iPhone/Android/computador usando o PhoneGap.

Titânio: