Meu plano de fundo
Conheci o <canvas> em 2006 quando o Firefox v2.0 foi lançado. Um artigo sobre Ajaxian, que descreve a matriz de transformação, me inspirou a criar meu primeiro aplicativo da web <canvas>; Color Sphere (2007). Isso me aprofundou no mundo das cores e dos elementos gráficos básicos, inspirando a criação do Sketchpad (2007-2008) em uma tentativa de reunir um aplicativo “melhor que o Paint” no navegador.Essas experiências acabaram levando à criação do Mugtug de inicialização com meu amigo de longa data Charles Pritchard. Estamos desenvolvendo o Darkroom no elemento <canvas> do HTML5. Darkroom é um aplicativo de compartilhamento de fotos não destrutivo que combina o poder dos filtros baseados em pixel com tipografia e desenhos baseados em vetor.
Introdução
O <canvas> traz aos programadores de JavaScript controle total de cores, vetores e pixels na tela - o layout visual do monitor.
Os exemplos a seguir tratam de outra área do <canvas> que não chama tanta atenção, a criação de efeitos de texto. A variedade de efeitos de texto que podem ser criados no <canvas> é tão vasta quanto você possa imaginar; essas demonstrações abrangem um subconjunto do que é possível. Embora estejamos falando de “texto” neste artigo, os métodos podem ser aplicados a qualquer objeto vetorial, criando visuais incríveis em jogos e outros aplicativos:
- Sombras de texto no <canvas>.
- Efeitos de texto como CSS do <canvas> criação de máscaras de corte, localização de métricas no <canvas> e uso da propriedade de sombra.
- Arco-íris neon, reflexão de zebra - efeitos de encadeamento.
- Efeitos de texto como Photoshop em exemplos do <canvas> de como usar globalCompositeOperation, createLinearGradient e createPattern.
- Sombras internas e externas no <canvas>
- Como revelar um recurso pouco conhecido; uso de movimento no sentido horário x anti-horário para criar o inverso de uma sombra oblíqua (a sombra interna).
- Efeito de geração de espaço.
- Efeito de texto baseado em geração do <canvas> usando o ciclo de cores hsl() e
window.requestAnimationFramepara criar a sensação de movimento.
Sombras de texto no cCanvas
Uma de minhas adições favoritas às especificações do CSS3 (junto com raio de borda, gradientes da web e outros) é a possibilidade de criar sombras. É importante notar as diferenças entre as sombras do CSS e do <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.
O <canvas> tem um único tipo de sombra, 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 no <canvas>, toque nestas quatro propriedades:
- ctx.shadowColor = “red” // string
- Cor da sombra; RGB, RGBA, HSL, HEX e outras entradas são válidas.
- ctx.shadowOffsetX = 0; // integer
- Distância horizontal da sombra em relação ao texto.
- ctx.shadowOffsetY = 0; // integer
- Distância vertical da sombra em relação ao texto.
- ctx.shadowBlur = 10; // integer
- Efeito de mancha da sombra; quanto maior o valor, maior a mancha.
Para começar, vamos ver como o <canvas> pode simular os efeitos de CSS. Pesquisar nas imagens do Google por “css text-shadow” resultou em algumas demonstrações excelentes para simulação; Line25, Stereoscopic e Shadow 3D.
O efeito 3D estereoscópico (consulte Imagem de anaglifo para obter mais informações) é um exemplo de uma linha de código simples usada de modo adequado. Com a seguinte linha do CSS, podemos criar a ilusão de profundidade quando visualizada com óculos 3D vermelho/cinza (o mesmo usado em filmes 3D):
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
Duas coisas devem ser consideradas ao converter essa string em <canvas>:
(1) Não há nenhuma mancha de sombra (o terceiro valor), ou seja, não há nenhum motivo para executar a sombra, pois 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);
(2) Os EMs não são compatíveis com o <canvas>, de modo que precisarão ser convertidos em PXs. Podemos encontrar a taxa de conversão entre PT, PC, EM, EX, PX e assim por diante criando um elemento com as mesmas propriedades de fonte em DOM e configurando a largura de acordo com o formato a ser medido; por exemplo, para capturar a conversão EM -> PX, medimos o elemento DOM com “height: 1em”. O offsetHeight resultante seria a quantidade de PXs presentes em cada 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;
Como evitar a multiplicação alfa
Em um exemplo mais complexo, como, por exemplo, o efeito de neon encontrado em Line25, a propriedade shadowBlur deve ser usada para simular o efeito corretamente. Como o efeito de neon depende de várias sombras, há um problema. No <canvas>, cada objeto vetorial pode ter somente uma sombra. Assim, para desenhar várias sombras, é necessário desenhar várias versões do texto em cima dele mesmo. Isso resulta na multiplicação alfa e, por fim, em bordas irregulares.
Tentei executar ctx.fillStyle = “rgba(0,0,0,0)” ou “transparent” para ocultar o texto enquanto a sombra estava sendo exibida. No entanto, essa tentativa foi em vão, pois a sombra é uma multiplicação de alfa fillStyle e nunca pode ser mais opaca que fillStyle.
Felizmente, há uma solução. Podemos desenhar o desvio da sombra a partir do texto, mantendo-os separado (para não haver sobreposição) e, portanto, 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 organizar um pouco as coisas, podemos impedir que fillText seja desenhado em primeiro plano (mas permitir 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 obter a largura usando ctx.measureText().width. No entanto, ctx.measureText().height não existe.
Felizmente, por meio de especificações do CSS ( consulteMétricas tipográficas para obter mais maneiras de corrigir implementações mais antigas de <canvas> usando medidas de 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 a ser usado como um caminho de corte, envolvendo a “sombra” e removendo a forma fictícia.
ctx.rect(0, 0, width, emHeight); ctx.clip();
Juntando tudo e fazendo otimizações progressivas, se uma sombra não tiver mancha, fillText poderá ser usado para o mesmo efeito, nos livrando de configurar a máscara de corte:
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 irá inserir todos esses comandos do <canvas> manualmente, incluí um analisador de sombra de texto simples na origem da demonstração. Desse modo, é possível gerar feeds de comandos do CSS e fazê-lo gerar comandos de <canvas>. Agora, nossos elementos <canvas> têm uma variedade completa de estilos a serem associados. 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.
Visualização Sombras de texto em efeitos do <canvas>.
Intermissão (tangente ao envio de pixels)
Ao escrever esta seção do artigo, o exemplo estereoscópico me deixou curioso. Até que ponto seria difícil criar um efeito de tela de cinema 3D usando o <canvas> e duas imagens criadas a partir de perspectivas ligeiramente diferentes? Aparentemente, não seria muito difícil. O kernel a seguir combina o canal vermelho da primeira imagem (data) 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;
Consulte a Demonstração estereoscópica para ver como criar imagens a serem aprimoradas com óculos 3D (ciano/magenta). Agora, alguém só precisa conectar dois iPhones ao fone de ouvido, clicar em “gravar vídeo” ao mesmo tempo e poderíamos fazer nossos próprios filmes 3D em HTML5. Algum voluntário?
Arco-íris, reflexão de zebra - efeitos de encadeamento.
Os vários efeitos de encadeamento do <canvas> podem ser simples, mas é necessário ter um conhecimento básico de globalCompositeOperation (GCO). Para comparar as operações com o GIMP (ou Photoshop): existem 12 GCOs no <canvas>. darker e lighter podem ser considerados modos de mistura de camada; as outras dez operações são aplicadas às camadas como máscaras alfa (uma camada remove os pixels da outra camada). O globalCompositeOperation associa as “camadas” (ou, em nosso caso, strings de código), combinando-as de maneiras novas e interessantes:
O gráfico de globalCompositeOperation mostra os modos de GCO em funcionamento. Esse gráfico usa uma grande parte do espectro de cores e vários níveis de transparência alfa para ver em detalhes o que se deve esperar. Recomendo consultar a referência de globalCompositeOperation do Mozilla para obter descrições textuais. Para pesquisas adicionais, você pode saber como a operação funciona consultando Composição de Imagens Digitais de Porter Duff.
Meu modo favorito é globalCompositeOperation=”lighter”. Lighter mistura os pixels anexados de modo similar a light; quando vermelho, verde e branco estão com intensidade máxima, vemos white-light. É um recurso interessante de se usar, especialmente quando o <canvas> está definido com um valor baixo de globalAlpha, permitindo controle mais refinado e bordas mais suavizadas. Lighter tem sido usado de muitas maneiras. Meu favorito recente é um criador de plano de fundo de área de trabalho HTML5 disponível em http://weavesilk.com/. Uma de minhas demonstrações, Breathing Galaxies (JS1k), também usa o modo lighter. Desenhando padrões a partir desses dois exemplos, você começa a ver o efeito desse modo.
OBSERVAÇÃO: não há suporte para alguns modos de GCO nos navegadores que precisam ser retirados. Seis modos funcionam nos navegadores (Chrome, Safari, Firefox e Opera): source-over, source-atop, destination-over, destination-out, lighter e xor. Felizmente, isso será removido nas próximas versões. Para obter mais informações, consulte Manuseio do navegador globalCompositeOperation.
Efeito de instabilidade de arco-íris neon
Na demonstração a seguir, vamos obter um brilho de arco-íris neon como Photoshop com um contorno instável, encadeando efeitos com o uso de globalCompositeOperation (source-in, lighter e darker). Essa demonstração é uma progressão da demonstração “Sombras de texto no <canvas>”, que usa a mesma estratégia para separar a sombra do texto (consulte a seção anterior):
Visualização Efeito de instabilidade de arco-íris neon.
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 pelo excelente recurso do WebDesignerWall sobre como dividir a página com CSS. Isso leva a ideia um pouco mais além, criando uma “reflexão” para o texto, como o que você pode ver no iTunes. O efeito combina fillColor (branco), createPattern (zebra.png) e linearGradient (brilhante); isso ilustra a possibilidade de aplicar vários tipos de preenchimento a cada objeto vetorial:
Visualização Efeito de reflexão 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 do <canvas> não tocam no assunto de sombras “internas” x “externas”. Na realidade, a princípio, você talvez pense que a sombra “interna” não é permitida. Esse não é o caso. A questão é um pouco mais complicada ;) Conforme proposto em uma postagem recente de F1LT3R, você pode criar sombras internas usando as propriedades exclusivas das regras de movimentação no sentido horário x anti-horário. Para fazer isso, crie uma “sombra interna” desenhando o retângulo do contêiner e, em seguida, usando as regras de movimentação oposta, desenhe uma forma de recorte, criando o inverso da forma.
O exemplo a seguir permite que borda interna e 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 estão perpendiculares entre si. Uma máscara de corte do tamanho da caixa delimitadora é usada, eliminando a necessidade de um recipiente super grande para envolver a forma de corte e, assim, aumentando a velocidade ao impedir o processamento de partes desnecessárias da sombra.
View Efeito de sombra interna.
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 destes exemplos, usando globalCompositeOperation, podemos encadear efeitos para produzir efeitos mais elaborados (usando mascaramento e mistura). A tela é sua ostra ;)
Efeitos de geração de espaço.
No <canvas>, a partir do caractere unicode 0x2708:
... até este exemplo sombreado:
... pode ser obtido por várias chamadas para ctx.strokeText() com uma lineWidth fina (0,25), diminuindo lentamente o desvio de x e alfa e dando aos elementos vetoriais a sensação de movimento.
Mapeando a posição XY dos elementos para uma onda de seno/cosseno e percorrendo as cores com o uso da propriedade HSL, podemos criar efeitos mais interessantes, como este exemplo de “perigo biológico”:
HSL: Hue, Saturation, Lightness (Matiz, saturação e luminosidade) (1978)
HSL é um formato aceito recentemente nas especificações do CSS3. Onde HEX tiver sido desenvolvido para computadores, HSL será desenvolvido para ser legível por humanos.
Ilustração da facilidade do HSL: para percorrer o espectro de cores, podemos simplesmente aumentar a “matiz” de 360; a matiz é mapeada para o espectro de modo cilíndrico. Os controles de luminosidade mostram até que ponto a cor é escura/clara; 0% indica um pixel preto e 100% indica um pixel branco. A saturação controla até que ponto a cor é brilhante ou vívida; os cinzas são criados com uma saturação de 0%, e as cores vívidas são criadas com um valor de 100%.
Como HSL é um padrão recente, você talvez queira continuar oferecendo suporte para 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}. Aqui, você pode usar esses valores para criar a string rgb ou rgba. Para obter informações mais detalhadas, consulte o valioso artigo da Wikipedia 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
};
};
Criação de animações com requestAnimationFrame
Antigamente, para criar animações em JavaScript, havia duas opções: setTimeout e setInterval.
window.requestAnimationFrame é o novo padrão para substituir ambos. Ele economiza a energia 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 um usuário possui o quadro, a animação pode ficar mais lenta ou ser interrompida por completo para evitar o uso de recursos desnecessários.
- A taxa de quadros tem um limite de 60 FPS. Isso acontece porque ela está bem acima do nível que pode ser notado pelos humanos (a maioria dos humanos considera a animação “fluida” com uma taxa de 30 FPS).
No momento em que o artigo foi escrito, os prefixos específicos do fornecedor deviam usar requestAnimationFrame. Paul Irish criou uma camada estreita que tem suporte para vários fornecedores, disponível em 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);
};
})();
Aprofundando isso um pouco mais, se a associação for mais complexa com um poly-fill como requestAnimationFrame.js (alguns recursos devem ser removidos), o suporte para navegadores mais antigos será estendido durante a mudança para esse novo padrão.
O exemplo a seguir mostra como criar uma animação, enviando milhares de chamadas para strokeText por meio de um alfa baixo sem danificar o navegador. Os controles são um pouco estranhos, mas produzem resultados interessantes:
Visualização Efeito de espaç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);
})();
Código-fonte
Com suporte do fornecedor do navegador, não há dúvida quanto ao futuro do <canvas>. Ele pode ser transferido para executáveis do iPhone/Android/Desktop usando PhoneGap ou Titanium.
O código-fonte pode ser encontrado em CanvasTextEffects.zip
