Mi fondo
<canvas> entró en mi radar en 2006, cuando se lanzó Firefox v2.0. Un artículo en Ajaxian, en el que se describía la matriz de transformación, me inspiró para crear mi primera app web de <canvas>: Color Sphere (2007). Esto 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 llevaron a la creación de la startup Mugtug con
mi amigo de toda la vida, Charles Pritchard. Estamos desarrollando Darkroom en HTML5 <canvas>. Darkroom
es una app para compartir fotos no destructiva que combina las capacidades de los filtros basados en píxeles con
la tipografía y el dibujo basados en vectores.
Introducción
<canvas> brinda a los programadores de JavaScript control total de los colores,
los vectores y los píxeles en sus pantallas, es decir, la composición visual del monitor.
En los siguientes ejemplos, se aborda un área de <canvas> que no recibió
mucha 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 tratamos con "texto", los métodos se pueden aplicar a cualquier objeto vectorial, lo que crea imágenes atractivas en juegos y otras aplicaciones:
- Sombras de texto en
<canvas>. - Efectos de texto similares a CSS en
<canvas>, creación de máscaras de recorte, búsqueda de métricas en<canvas>y uso de la propiedad de sombra. - Neon-rainbow, zebra-reflection: encadenamiento de efectos.
- Efectos de texto similares a los de Photoshop en
<canvas>ejemplos de uso de globalCompositeOperation, createLinearGradient y createPattern. - Sombras interiores y exteriores en
<canvas> - Revelar una función poco conocida: usar el sentido de las manecillas del reloj o el sentido contrario para crear el inverso de una sombra paralela (la sombra interior).
- Spaceage: Efecto generativo.
- Efecto de texto basado en IA generativa en
<canvas>con el ciclo de colores hsl() ywindow.requestAnimationFramepara crear la sensación de movimiento.
Sombras de texto en Canvas
Una de mis adiciones favoritas a las especificaciones de CSS3 (junto con border-radius, web-gradients y otras)
es la capacidad de crear sombras. Es importante comprender las diferencias entre las sombras de CSS y las de <canvas>, específicamente:
CSS usa dos métodos: box-shadow para elementos de cuadro, como div, span, etcétera, y text-shadow para 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>, aprovecha estas cuatro propiedades:
- ctx.shadowColor = "red" // string
- Color de la sombra; se admiten RGB, RGBA, HSL, HEX y otras entradas.
- ctx.shadowOffsetX = 0; // integer
- Es la distancia horizontal de la sombra en relación con el texto.
- ctx.shadowOffsetY = 0; // integer
- Es la distancia vertical de la sombra en relación con el texto.
- ctx.shadowBlur = 10; // integer
- Efecto de desenfoque en la sombra. Cuanto mayor sea el valor, mayor será el desenfoque.
Para comenzar, veamos cómo <canvas> puede emular efectos de CSS.
La búsqueda en Google Imágenes de "css text-shadow" nos llevó a algunas demostraciones excelentes para emular: Line25, Stereoscopic y Shadow 3D.
El efecto 3D estereoscópico (consulta Imagen de anaglifo para obtener más información) es un ejemplo de una línea de código simple que se usa de manera excelente. Con la siguiente línea de CSS, podemos crear la ilusión de profundidad cuando se ve con lentes 3D rojos y cian (del tipo que te dan en las películas en 3D):
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
Hay dos aspectos que debes tener en cuenta cuando conviertas esta cadena en <canvas>:
- No hay 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>
- Las unidades EM no se admiten en
<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, creando un elemento con las mismas propiedades de fuente en el DOM y estableciendo el ancho en el formato que se medirá; por ejemplo, para capturar la conversión de EM a PX, mediríamos el elemento del DOM con un "height: 1em", 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>
Cómo evitar la multiplicación alfa
En un ejemplo más complejo, como el efecto de neón que se encuentra en Line25, se debe usar la propiedad shadowBlur para emular el efecto correctamente. Dado que el efecto de neón se basa en 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.
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 del 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 esa 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 limpiar un poco esto, podemos evitar que se dibuje fillText en primer lugar (mientras permitimos que se dibuje la sombra) agregando una ruta de recorte.
Para crear un trazado de recorte que rodee el texto, necesitamos conocer la
altura del texto (llamada "altura em", históricamente la altura
de la letra "M" en una imprenta) y el ancho del texto.
Podemos obtener el ancho con ctx.measureText().width, pero ctx.measureText().height no existe.
Por suerte, con un truco de CSS (consulta Métricas tipográficas para conocer 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 trazado de recorte, encerrando la "sombra" y quitando la forma ficticia.
ctx.rect(0, 0, width, emHeight);
ctx.clip();
Para unir todo y optimizarlo sobre la marcha, si una sombra no tiene desenfoque, se puede usar fillText para lograr el mismo efecto, lo que nos evita tener que 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 de <canvas> de forma manual, incluí un analizador de sombras de texto simple en la fuente de la demostración. De esta manera, puedes proporcionarle comandos de CSS y hacer que genere comandos de <canvas>.
Ahora, nuestros elementos <canvas> tienen una amplia variedad de estilos con los que se pueden vincular.
Estos mismos efectos de sombra se pueden usar en cualquier objeto vectorial, desde WebFonts hasta
formas complejas importadas de SVG, formas vectoriales generativas y mucho más.
Intermedio (tangente sobre el ajuste de píxeles)
Mientras escribía esta sección del artículo, me dio curiosidad el ejemplo de Stereoscopic. ¿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, alguien solo necesita pegar dos iPhones con cinta adhesiva a su frente, hacer clic en "grabar video" al mismo tiempo, y podríamos hacer nuestras propias películas en 3D en HTML5. ¿Hay algún voluntario?
Arcoíris de neón, reflejo de cebra: encadenamiento de efectos
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>
darker, y lighter se puede considerar como modos de fusió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). La propiedad globalCompositeOperation une "capas"
(o, en nuestro caso, cadenas de código) y las combina de formas nuevas y emocionantes:
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 obtener más información, puedes consultar cómo funciona la operación en Compositing Digital Images de Porter Duff.
Mi modo favorito es globalCompositeOperation="lighter". Lighter mezcla
los píxeles agregados de forma similar a como se mezcla la luz; cuando la luz roja, verde y blanca están
en su máxima intensidad, vemos luz blanca. Es una función interesante para experimentar, sobre todo cuando el <canvas> se establece en un valor bajo de globalAlpha, lo que permite un control más preciso
y bordes más suaves. Lighter se ha utilizado para muchos fines, mi favorito reciente es un creador de fondos de escritorio HTML5 que se encuentra en http://weavesilk.com/.
Una de mis demostraciones, Breathing Galaxies (JS1k),
también usa el modo más claro. Si dibujas patrones a partir de estos dos ejemplos, comenzarás a
ver qué efecto produce este modo.
Manejo del navegador de globalCompositeOperation.
Efecto de vibración de arcoíris de neón
En la siguiente demostración, lograremos un brillo de arcoíris de neón similar al de Photoshop con un contorno con vibración, 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>",
en la que se usa la misma estrategia para separar la sombra del texto (consulta la sección anterior):
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 Zebra Reflection se inspiró en el excelente recurso de WebDesignerWall sobre cómo mejorar tu página con CSS. Esto lleva la idea un poco más allá, ya que crea un "reflejo" para el texto, como el 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:
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 interiores y exteriores en el lienzo
Las especificaciones de <canvas> no abordan el tema de las sombras "interiores"
frente a las "exteriores". De hecho, a primera vista, es posible que esperes
que no se admita la sombra "interior". Este no es el caso.
Solo es un poco más difícil de habilitar ;) Como se propuso en una publicación reciente de F1LT3R, puedes crear sombras interiores con las propiedades únicas de las reglas de devanado en el sentido de las manecillas del reloj y en sentido contrario. Para ello, crea una "sombra interior" dibujando un rectángulo contenedor y, luego, con reglas de devanado opuestas, dibuja una forma de recorte, lo que creará la inversa de la forma.
En el siguiente ejemplo, se permite que inner-shadow y fillStyle se estilicen con color, gradiente y patrón de forma simultánea. Puedes especificar la rotación del patrón de forma individual. Observa que las rayas de cebra ahora son perpendiculares entre sí. Se usa una máscara de recorte del tamaño del cuadro delimitador, lo que elimina la necesidad de un contenedor muy grande para encerrar la forma recortada, lo que mejora la velocidad, ya que evita que se procesen las partes innecesarias de la sombra.
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 y 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:
…a este ejemplo sombreado:
…se puede lograr con varias llamadas a ctx.strokeText() con un lineWidth delgado (0.25),
mientras se disminuye lentamente el desplazamiento x y el alfa, lo que les da a nuestros elementos vectoriales la sensación de movimiento.
Si asignas la posición XY de los elementos a una onda sinusoidal o cosenoidal, y recorres los colores con la propiedad HSL, puedes crear efectos más interesantes, como este ejemplo de "riesgo biológico":
HSL: Tono, saturación, luminosidad (1978)
HSL es un formato que se agregó recientemente a las especificaciones de CSS3. Mientras que HEX se diseñó para computadoras, HSL se diseñó para que sea legible por humanos.
Para ilustrar la facilidad de HSL, para recorrer el espectro de colores, simplemente incrementamos el "tono" desde 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 el brillo o la intensidad de un color. Los grises se crean con una saturación del 0%, y los colores intensos se crean con un valor del 100%.
Dado que HSL es un estándar reciente, es posible que quieras seguir admitiendo navegadores más antiguos, lo que es posible a través de 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 }. A partir de ahí, 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
En el pasado, para crear animaciones en JavaScript, había dos opciones: setTimeout y setInterval.
window.requestAnimationFrame, es el nuevo estándar que reemplaza a ambos, lo que ahorra electricidad al mundo (y algunos latidos a tu computadora) permitiendo que el navegador regule las animaciones según los recursos disponibles.
Estas son algunas de las funciones importantes:
- Cuando un usuario sale del fotograma, la animación puede ralentizarse o detenerse por completo para evitar el uso de recursos innecesarios.
- Hay un límite para la velocidad de fotogramas de 60 FPS. Esto se debe a que está 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 Irish creó una capa de corrección que tiene compatibilidad entre proveedores, en requestAnimationFrame para
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);
};
})();
Para ir un poco más allá, los más ambiciosos podrían vincular esto con un polyfill, 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);
})();
Código fuente
Con la asistencia de toda la esfera de proveedores de navegadores, no hay dudas sobre el
futuro de <canvas>, ya que se puede portar a los ejecutables de iPhone, Android o computadoras
con PhoneGap o