Efectos tipográficos en lienzo

Mi experiencia

Conocí <canvas> en 2006, cuando se lanzó Firefox v2.0. Un artículo en Ajaxian, que describía la matriz de transformación, me inspiró a crear mi primera app web de <canvas>: Color Sphere (2007). Lo que me sumergió en el mundo de los colores y las primitivas gráficas, lo que inspiró la creación de Sketchpad (2007-2008) en un esfuerzo por crear una aplicación “mejor que Paint” en el navegador. Estos experimentos finalmente llevaron a la creación de la startup Mugtug con mi antiguo amigo Charles Pritchard. Estamos desarrollando Habitación oscura en <canvas> de HTML5. Darkroom es una app de uso no destructivo para compartir fotos que combina las funciones de los filtros basados en píxeles con la tipografía y el dibujo basados en vectores.

Introducción

Gráfico del banner de lienzo.

<canvas> les brinda a los programadores de JavaScript un control total sobre los colores, los vectores y los píxeles en sus pantallas, es decir, el aspecto visual del monitor.

En los siguientes ejemplos, se describe un área de <canvas> que llamó mucho la atención: la creación de efectos de texto. La variedad de efectos de texto que se pueden crear en <canvas> es tan amplia como puedas imaginar. Estas demostraciones abarcan una subsección de lo que es posible. Aunque en este artículo estamos tratando con “texto”, los métodos se pueden aplicar a cualquier objeto vectorial, lo que crea imágenes atractivas en juegos y otras aplicaciones:

Text-Shadows en <canvas>.
Efectos de texto similares a CSS en <canvas> para crear máscaras de recorte, encontrar métricas en <canvas> y usar la propiedad sombra.
Efectos de encadenamiento: arcoíris de neón, reflejo de cebra
Efectos de texto similares a Photoshop en <canvas> ejemplos de uso de globalCompositeOperation, createLinearGradient y createPattern
Sombras internas y externas en <canvas>
Revelamos una función poco conocida: usar el giro en el sentido de las manecillas del reloj o en sentido contrario para crear la inversa de una sombra (la sombra interna).
Spaceage: Efecto generativo.
Efecto de texto generativo en <canvas> con el ciclo de colores hsl() y window.requestAnimationFrame para crear la sensación de movimiento.

Sombras de texto en Canvas

Una de mis incorporaciones favoritas a las especificaciones de CSS3 (junto con border-radius, web-gradients y otras) es la capacidad de crear sombras. Es importante conocer las diferencias entre las sombras de CSS y <canvas>, en particular:

CSS usa dos métodos: box-shadow para elementos de cuadro, como div, span, etcétera, y text-shadow para el contenido de texto.

<canvas> tiene un tipo de sombra que se usa para todos los objetos vectoriales: ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.strokeText, etcétera. Para crear una sombra en <canvas>, presiona estas cuatro propiedades:

ctx.shadowColor = "red" // cadena
Color de la sombra. Son válidas las entradas RGB, RGBA, HSL, HEX y otras.
ctx.shadowOffsetX = 0; // número entero
Distancia horizontal de la sombra en relación con el texto.
ctx.shadowOffsetY = 0; // integer
Distancia vertical de la sombra en relación con el texto.
ctx.shadowBlur = 10; // número entero
El efecto de desenfocar la sombra es mayor, cuanto más grande es el valor, mayor es el desenfoque.

Para comenzar, veamos cómo <canvas> puede emular los efectos de CSS. Buscar "text-shadow css" en Google Imágenes nos llevó a algunas demos geniales que pudimos emular: Line25, Stereoscopic y Shadow 3D.

Gráfico 3D de CSS

El efecto 3D estereoscópico (consulta Imagen anaglifa para obtener más información) es un ejemplo de una línea de código simple que se usa mucho. Con la siguiente línea de CSS, podemos crear la ilusión de profundidad cuando se ve con lentes 3D rojos/cian (del tipo que te dan en las películas 3D):

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

Hay dos aspectos que debes tener en cuenta cuando conviertes esta cadena en <canvas>:

  1. No hay un desenfoque de sombra (el tercer valor), por lo que no hay razón para ejecutar la sombra, ya que fillText crearía los mismos 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. Los EM no son compatibles con <canvas>, por lo que se deben convertir a PX. Podemos encontrar la proporción de conversión para convertir entre PT, PC, EM, EX, PX, etcétera, si creamos un elemento con las mismas propiedades de fuente en el DOM y configuramos el ancho en el formato que se medirá. Por ejemplo, para capturar la conversión de EM a PX, mediríamos el elemento DOM con un "height: 1em", y el offsetHeight resultante sería la cantidad de PX que hay en 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;</pre>

Evita la multiplicación alfa

En un ejemplo más complejo, como el efecto neón que se encuentra en Line25, se debe usar la propiedad shadowBlur para emular el efecto correctamente. Como el efecto neón depende de varias sombras, nos encontramos con un problema: en <canvas>, cada objeto vectorial solo puede tener una sombra. Por lo tanto, para dibujar varias sombras, debes dibujar varias versiones del texto una encima de la otra. Esto genera una multiplicación alfa y, en última instancia, bordes irregulares.

Gráfico de neón

Intenté ejecutar ctx.fillStyle = "rgba(0,0,0,0)" o "transparent" para ocultar el texto mientras se mostraba la sombra, pero este intento fue inútil, ya que la sombra es una multiplicación de la alfa de fillStyle, por lo que nunca puede ser más opaca que fillStyle.

Por suerte, hay una forma de evitar esto. Podemos dibujar el desplazamiento de la sombra desde el texto, mantenerlos separados (para que no se superpongan) y, de esta manera, ocultar el texto del costado de la pantalla:

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

Recorte alrededor de un bloque de texto

Para ordenar un poco esto, podemos evitar que se dibuje fillText en primer lugar (mientras se permite que se dibuje la sombra) agregando un camino de recorte. Para crear una ruta de recorte que rodee el texto, necesitamos conocer la altura del texto (históricamente llamada "em-height" es la altura de la letra "M" en una imprenta) y el ancho del texto. Podemos obtener el ancho con ctx.measureText().width, sin embargo, ctx.measureText().height no existe.

Por suerte, a través de la hackeabilidad de CSS (consulta Métricas tipográficas para obtener más formas de corregir implementaciones anteriores de <canvas> con mediciones de CSS), podemos encontrar la altura del texto midiendo el offsetHeight de un <span> con las mismas propiedades de fuente:

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

A partir de ahí, podemos crear un rectángulo para usarlo como ruta de recorte, que encierra la "sombra" y quita la forma simulada.

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

Si un elemento de sombra no tiene desenfoque, se puede usar fillText para lograr el mismo efecto, lo que nos evita configurar la 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 no querrás ingresar todos estos comandos <canvas> de forma manual, incluí un analizador de sombras de texto simple en la fuente de demostración; de esta manera, puedes proporcionarle comandos de CSS y hacer que genere comandos <canvas>. Ahora, nuestros elementos <canvas> tienen una amplia variedad de estilos a los que se pueden vincular. Estos mismos efectos de sombras se pueden usar en cualquier objeto vectorial, desde WebFonts hasta formas complejas importadas de SVG, formas vectoriales generativas, etcétera.

Efectos de sombra del texto en lienzo

Intermisión (tangente en el envío de píxeles)

Mientras escribía esta sección del artículo, me llamó la atención el ejemplo estereoscópico. ¿Qué tan difícil sería crear un efecto de pantalla de película en 3D con <canvas> y dos imágenes tomadas desde perspectivas ligeramente diferentes? Al parecer, no es muy difícil. El siguiente kernel combina el canal rojo de la primera imagen (data) con el canal cian de la segunda imagen (data2):

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

Ahora, solo queda que alguien pegue dos iPhones a su frente con cinta adhesiva, haga clic en “Grabar video” al mismo tiempo y podamos hacer nuestras propias películas en 3D en HTML5. ¿Hay algún voluntario?

Gafas 3D

Arco iris de neón y reflejo de cebras: efectos encadenados

Encadenar varios efectos en <canvas> puede ser sencillo, pero se requiere un conocimiento básico de globalCompositeOperation (GCO). Para comparar las operaciones con GIMP (o Photoshop), hay 12 GCO en <canvas> más oscuro, y más claro se pueden considerar como modos de combinación de capas; las otras 10 operaciones se aplican a las capas como máscaras alfa (una capa quita los píxeles de la otra capa). globalCompositeOperation une "capas" (o, en nuestro caso, cadenas de código) y las combina de nuevas y emocionantes maneras:

Cadena de gráficos de efectos

El gráfico globalCompositeOperation muestra los modos de GCO en funcionamiento. Este gráfico usa una gran parte del espectro de colores y varios niveles de transparencia alfa para ver en detalle qué esperar. Te recomiendo que consultes la referencia de globalCompositeOperation de Mozilla para obtener descripciones textuales. Para realizar una investigación más detallada, puedes aprender cómo funciona la operación en Compositing Digital Images de Porter Duff.

Mi modo favorito es globalCompositeOperation="lighter". Los tonos más claros combinan los píxeles agregados de manera similar a la forma en que se mezcla la luz. Cuando la luz roja, verde y blanca está a máxima intensidad, vemos luz blanca. Es una función emocionante con la que jugar, en especial cuando <canvas> se establece en un valor bajo de globalAlpha, lo que permite un control más preciso y bordes más lisos. Lighter se ha usado para muchos fines, y mi favorito reciente es un creador de fondos de pantalla para computadoras HTML5 que se encuentra en http://weavesilk.com/. Una de mis demostraciones, Breathing Galaxies (JS1k), también usa el modo más ligero. Si dibujas patrones a partir de estos dos ejemplos, comienzas a ver qué efecto produce este modo.

Manejo del navegador de globalCompositeOperation.

Efecto de parpadeo de arcoíris neón

En la siguiente demostración, lograremos un brillo de arco iris neón similar a Photoshop con un contorno inestable encadenando efectos con globalCompositeOperation (source-in, lighter y darker). Esta demostración es una progresión de la demostración “Sombras de texto en <canvas>”, que usa la misma estrategia para separar la sombra del texto (consulta la sección anterior):

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

Efecto de reflejo de cebra

El efecto de reflexión de cebra se inspiró en el excelente recurso de WebDesignerWall sobre cómo darle vida a tu página con CSS. Esto lleva la idea un poco más allá y crea una "reflexión" para el texto, como lo que podrías ver en iTunes. El efecto combina fillColor (blanco), createPattern (zebra.png) y linearGradient (brillo). Esto ilustra la capacidad de aplicar varios tipos de relleno a cada objeto vectorial:

Efecto cebra
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 o externas en Canvas

Las especificaciones de <canvas> no abordan el tema de las sombras “internas” en comparación con las “externas”. De hecho, a primera vista, es posible que no se admita la sombra "interna". Este no es el caso. Es un poco más complicado habilitarlas ;). Como se propuso en una publicación reciente de F1LT3R, puedes crear sombras internas con las propiedades únicas de las reglas de giro en el sentido de las manecillas del reloj y en el sentido contrario. Para ello, debes crear una "sombra interna" dibujando un rectángulo de contenedor y, luego, con reglas de giro opuestas, dibujar una forma recortada, lo que crea la inversa de la forma.

En el siguiente ejemplo, se permite que la sombra interna y el fillStyle se estilicen con color, gradiente y patrón de forma simultánea. Puedes especificar la rotación del patrón individualmente. Observa que las rayas de cebra ahora son perpendiculares entre sí. Se usa una máscara de recorte del tamaño del cuadro de límite, lo que elimina la necesidad de un contenedor súper grande para encerrar la forma recortada, lo que mejora la velocidad, ya que evita que se procesen las partes innecesarias de la sombra.

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

En estos ejemplos, puedes ver que, con globalCompositeOperation, podemos encadenar efectos para producir efectos más elaborados (con enmascaramiento y combinación). La pantalla es tuya ;)

Spaceage: efectos generativos

En <canvas>, se pasa del carácter Unicode 0x2708:

Gráfico Unicode

…a este ejemplo sombreado:

Ejemplo sombreado

…se puede lograr con varias llamadas a ctx.strokeText() con un lineWidth delgado (0.25), mientras se reduce lentamente el desplazamiento en X y la alfa, lo que le da a nuestros elementos vectoriales la sensación de movimiento.

Al asignar la posición XY de los elementos a una onda seno/coseno y alternar los colores con la propiedad HSL, podemos crear efectos más interesantes, como este ejemplo de "riesgo biológico":

Efecto de ciclo de HSL

HSL: matiz, saturación y luminosidad (1978)

HSL es un formato nuevo admitido en las especificaciones de CSS3. Mientras que HEX se diseñó para computadoras, HSL se diseñó para ser legible por humanos.

Para ilustrar la facilidad del modelo HSL, para recorrer el espectro de colores, simplemente deberíamos incrementar el "tono" de 360. El tono se asigna al espectro de forma cilíndrica. La luminosidad controla qué tan oscuro o claro es el color. El 0% indica un píxel negro, mientras que el 100% indica un píxel blanco. La saturación controla qué tan brillante o vívido es un color. Los tonos grises se crean con una saturación del 0%, y los colores vivos se crean con un valor del 100%.

Gráfico HSL

Dado que HSL es un estándar reciente, te recomendamos que continúes admitiendo navegadores más antiguos, lo que es posible mediante la conversión de espacio de color. El siguiente código acepta un objeto HSL { H: 360, S: 100, L: 100} y genera un objeto RGB { R: 255, G: 255, B: 255 }. Desde allí, puedes usar esos valores para crear tu cadena RGB o RGBA. Para obtener información más detallada, consulta el artículo de 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
};
};

Cómo crear animaciones con requestAnimationFrame

Antes, para crear animaciones en JavaScript, era necesario usar dos opciones: setTimeout y setInterval.

window.requestAnimationFrame es el nuevo estándar que reemplazará a ambos, lo que ahorrará electricidad en el mundo (y a tu computadora algunos latidos) ya que permite que el navegador regule las animaciones en función de los recursos disponibles. Estas son algunas funciones importantes:

  • Cuando un usuario sale del marco, la animación puede ralentizar o detenerse por completo para evitar el uso de recursos innecesarios.
  • Existe un límite de 60 FPS para la velocidad de fotogramas. Esto se debe a que se encuentra muy por encima del nivel que los humanos pueden notar (la mayoría de los humanos a 30 FPS ven que la animación es "fluida").

En el momento de escribir este artículo, se requieren prefijos específicos del proveedor para usar requestAnimationFrame. Paul Irlandés creó una capa de corrección de compatibilidad para compatibilidad con vennders cruzados, en requestAnimationFrame para la animación inteligente:

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

Si lo llevas un poco más lejos, los más ambiciosos podrían vincular esto con un relleno de varios elementos, como requestAnimationFrame.js (hay algunas funciones que se deben resolver), que admitiría navegadores más antiguos en mayor medida, mientras se cambia a este nuevo estándar.

(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 desenfoque de notas
Gráfico de animación
Gráfico de matriz

Código fuente

Con la compatibilidad de todos los proveedores de navegadores, no hay dudas sobre el futuro de <canvas>, ya que se puede portar a los ejecutables de iPhone, Android y computadoras de escritorio con PhoneGap, o

Titanio.