Mon expérience
<canvas> est entré dans mon champ de conscience en 2006, lors de la sortie de Firefox v2.0. Un article sur Ajaxian, décrivant la matrice de transformation, m'a inspiré pour créer ma première application Web <canvas> : Color Sphere (2007). Cela m'a plongé dans le monde des couleurs et des primitives graphiques, et m'a inspiré la création de Sketchpad (2007-2008) dans le but de créer une application "meilleure que Paint" dans le navigateur.
Ces expériences ont finalement conduit à la création de la start-up Mugtug avec mon ami de longue date Charles Pritchard. Nous développons Darkroom en HTML5 <canvas>. Darkroom est une application de partage de photos non destructive, qui combine la puissance des filtres basés sur les pixels avec la typographie et le dessin vectoriels.
Introduction
<canvas> permet aux programmeurs JavaScript de contrôler entièrement les couleurs, les vecteurs et les pixels de leurs écrans, c'est-à-dire la composition visuelle du moniteur.
Les exemples suivants concernent un domaine de <canvas> qui n'a pas reçu beaucoup d'attention : la création d'effets de texte. La variété des effets de texte pouvant être créés dans <canvas> est aussi vaste que vous pouvez l'imaginer. Ces démonstrations couvrent une partie de ce qu'il est possible de faire. Bien que nous parlions de "texte" dans cet article, les méthodes peuvent être appliquées à tous les objets vectoriels, ce qui permet de créer des visuels attrayants dans les jeux et d'autres applications :
- Ombres de texte dans
<canvas>. - Effets de texte de type CSS dans
<canvas>, création de masques d'écrêtage, recherche de métriques dans<canvas>et utilisation de la propriété d'ombre. - Arc-en-ciel néon, reflet zébré : enchaînez les effets.
- Effets de texte de type Photoshop dans
<canvas>exemples d'utilisation de globalCompositeOperation, createLinearGradient et createPattern. - Ombres intérieures et extérieures dans
<canvas> - Découvrez une fonctionnalité peu connue : l'utilisation de l'enroulement dans le sens des aiguilles d'une montre ou dans le sens inverse pour créer l'inverse d'une ombre portée (l'ombre intérieure).
- Spaceage : effet génératif.
- Effet de texte génératif dans
<canvas>à l'aide du cycle de couleurs hsl() et dewindow.requestAnimationFramepour créer une impression de mouvement.
Ombres du texte dans Canvas
L'un de mes ajouts préférés aux spécifications CSS3 (avec border-radius, web-gradients et d'autres) est la possibilité de créer des ombres. Il est important de comprendre les différences entre les ombres CSS et <canvas>, en particulier :
Le CSS utilise deux méthodes : box-shadow pour les éléments de boîte, tels que div, span, etc., et text-shadow pour le contenu textuel.
<canvas> n'a qu'un seul type d'ombre, qui est utilisé pour tous les objets vectoriels : ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.strokeText, etc.
Pour créer une ombre dans <canvas>, utilisez ces quatre propriétés :
- ctx.shadowColor = "red" // string
- Couleur de l'ombre. Les entrées RVB, RVBA, HSL, HEX et autres sont valides.
- ctx.shadowOffsetX = 0; // integer
- Distance horizontale de l'ombre par rapport au texte.
- ctx.shadowOffsetY = 0; // integer
- Distance verticale de l'ombre par rapport au texte.
- ctx.shadowBlur = 10; // integer
- Effet de flou sur l'ombre. Plus la valeur est élevée, plus le flou est important.
Pour commencer, voyons comment <canvas> peut émuler des effets CSS.
En recherchant "css text-shadow" dans Google Images, nous avons trouvé d'excellentes démonstrations à imiter : Line25, Stereoscopic et Shadow 3D.
L'effet 3D stéréoscopique (voir Image anaglyphe pour en savoir plus) est un exemple de ligne de code simple, très utile. Avec la ligne de CSS suivante, nous pouvons créer l'illusion de profondeur lorsqu'on regarde l'image avec des lunettes 3D rouge/cyan (le type de lunettes qu'on vous donne pour les films en 3D) :
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
Deux points sont à noter lors de la conversion de cette chaîne en <canvas> :
- Il n'y a pas de flou d'ombre (la troisième valeur). Il n'y a donc aucune raison d'exécuter l'ombre, car fillText donnerait les mêmes résultats :
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>
- Les EM ne sont pas compatibles avec
<canvas>. Ils doivent donc être convertis en PX. Nous pouvons trouver le ratio de conversion entre PT, PC, EM, EX, PX, etc. en créant un élément avec les mêmes propriétés de police dans le DOM et en définissant la largeur sur le format à mesurer. Par exemple, pour capturer la conversion EM -> PX, nous mesurons l'élément DOM avec "height: 1em". La valeur offsetHeight obtenue correspond au nombre de PX dans chaque 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>
Empêcher la multiplication alpha
Dans un exemple plus complexe, tel que l'effet néon de Line25, la propriété shadowBlur doit être utilisée pour émuler correctement l'effet. Comme l'effet néon repose sur plusieurs ombres, nous rencontrons un problème : dans <canvas>, chaque objet vectoriel ne peut avoir qu'une seule ombre. Par conséquent, pour dessiner plusieurs ombres, vous devez dessiner plusieurs versions du texte les unes sur les autres.
Cela entraîne une multiplication alpha et, au final, des bords irréguliers.
J'ai essayé d'exécuter ctx.fillStyle = "rgba(0,0,0,0)" ou "transparent" pour masquer le texte tout en affichant l'ombre, mais cette tentative a été vaine. Comme l'ombre est une multiplication de l'alpha fillStyle, elle ne peut jamais être plus opaque que le fillStyle.
Heureusement, il existe un moyen de contourner ce problème. Nous pouvons dessiner l'ombre décalée par rapport au texte, en les gardant séparés (afin qu'ils ne se chevauchent pas), et ainsi masquer le texte sur le côté de l'écran :
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);
Découpage autour d'un bloc de texte
Pour nettoyer un peu ce résultat, nous pouvons empêcher le dessin de fillText (tout en permettant le dessin de l'ombre) en ajoutant un tracé de détourage.
Pour créer un tracé de détourage autour du texte, nous devons connaître la hauteur du texte (appelée "hauteur em", historiquement la hauteur de la lettre "M" sur une presse d'imprimerie) et la largeur du texte.
Nous pouvons obtenir la largeur à l'aide de ctx.measureText().width, mais ctx.measureText().height n'existe pas.
Heureusement, grâce à des astuces CSS (consultez Métriques typographiques pour découvrir d'autres façons de corriger les anciennes implémentations de <canvas> à l'aide de mesures CSS), nous pouvons trouver la hauteur du texte en mesurant le offsetHeight d'un <span> avec les mêmes propriétés de police :
var d = document.createElement("span");
d.font = "20px arial"
d.textContent = "Hello world!"
var emHeight = d.offsetHeight;
À partir de là, nous pouvons créer un rectangle à utiliser comme tracé de détourage, en englobant l'"ombre" tout en supprimant la forme factice.
ctx.rect(0, 0, width, emHeight);
ctx.clip();
En combinant le tout et en optimisant au fur et à mesure, si une ombre n'a pas de flou, fillText peut être utilisé pour obtenir le même effet, ce qui nous évite de configurer le masque d'écrêtage :
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);
Comme vous ne voudrez pas saisir toutes ces commandes <canvas> manuellement, j'ai inclus un analyseur d 'ombre de texte simple dans la source de la démonstration. De cette façon, vous pouvez lui envoyer des commandes CSS et lui faire générer des commandes <canvas>.
Nos éléments <canvas> peuvent désormais être associés à toute une gamme de styles.
Ces mêmes effets d'ombre peuvent être utilisés sur n'importe quel objet vectoriel, des polices Web aux formes vectorielles génératives, en passant par les formes complexes importées à partir de fichiers SVG.
Intermission (tangent on pixel-pushing)
En rédigeant cette section de l'article, l'exemple stéréoscopique a piqué ma curiosité. Serait-il difficile de créer un effet d'écran de cinéma 3D à l'aide de <canvas> et de deux images prises sous des angles légèrement différents ? Apparemment, pas trop difficile. Le noyau suivant combine le canal rouge de la première image (data) avec le canal cyan de la deuxième image (data2) :
data[i] = data[i] * 255 / 0xFF;
data[i+1] = 255 * data2[i+1] / 0xFF;
data[i+2] = 255 * data2[i+2] / 0xFF;
Maintenant, il suffit de scotcher deux iPhone sur son front, de cliquer sur "Enregistrer une vidéo" en même temps, et on peut créer ses propres films en 3D au format HTML5. Des volontaires ?
Arc-en-ciel néon, reflet zébré, enchaînement d'effets
L'enchaînement de plusieurs effets dans <canvas> peut être simple, mais une connaissance de base de globalCompositeOperation (GCO) est requise. Pour comparer les opérations à GIMP (ou Photoshop) : il existe 12 GCO dans <canvas>. darker et lighter peuvent être considérés comme des modes de fusion de calques. Les 10 autres opérations sont appliquées aux calques en tant que masques alpha (un calque supprime les pixels de l 'autre calque). La propriété globalCompositeOperation lie les "couches" (ou, dans notre cas, les chaînes de code) entre elles, en les combinant de manière innovante et intéressante :
Le graphique globalCompositeOperation montre les modes GCO en action. Il utilise une grande partie du spectre de couleurs et plusieurs niveaux de transparence alpha pour vous permettre de voir en détail ce à quoi vous attendre. Je vous recommande de consulter la documentation de référence sur globalCompositeOperation de Mozilla pour obtenir des descriptions textuelles. Pour en savoir plus, vous pouvez découvrir le fonctionnement de l'opération dans l'article Compositing Digital Images de Porter Duff.
Mon mode préféré est globalCompositeOperation="lighter". "Plus clair" mélange les pixels ajoutés de la même manière que la lumière se mélange. Lorsque la lumière rouge, verte et blanche est à pleine intensité, nous voyons de la lumière blanche. C 'est une fonctionnalité intéressante à tester, en particulier lorsque <canvas> est défini sur une valeur globalAlpha faible, ce qui permet un contrôle plus précis et des bords plus lisses. Lighter a été utilisé à de nombreuses fins, ma préférée étant un créateur d'arrière-plan de bureau HTML5 disponible sur http://weavesilk.com/.
L'une de mes démonstrations, Breathing Galaxies (JS1k), utilise également le mode plus clair. En dessinant des motifs à partir de ces deux exemples, vous commencez à voir l'effet de ce mode.
Gestion de globalCompositeOperation par le navigateur
Effet de scintillement néon arc-en-ciel
Dans la démonstration suivante, nous allons obtenir un effet de lueur néon arc-en-ciel semblable à celui de Photoshop avec un contour tremblant, en enchaînant les effets à l 'aide de globalCompositeOperation (source-in, lighter et darker).
Cette démonstration est une évolution de la démonstration "Ombres de texte dans <canvas>", qui utilise la même stratégie pour séparer l'ombre du texte (voir la section précédente) :
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();
};
Effet de reflet zébré
L'effet de réflexion Zebra a été inspiré par l'excellente ressource de WebDesignerWall sur la façon d'améliorer votre page avec CSS. Cette technique va un peu plus loin en créant un "reflet" pour le texte, comme ce que vous pouvez voir dans iTunes. L'effet combine fillColor (blanc), createPattern (zebra.png) et linearGradient (shine). Cela illustre la possibilité d'appliquer plusieurs types de remplissage à chaque objet vectoriel :
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();
};
Ombres intérieures/extérieures dans le canevas
Les spécifications <canvas> n 'abordent pas le sujet des ombres "intérieures" et "extérieures". En fait, à première vue, vous pourriez penser que l'ombre "intérieure" n'est pas prise en charge. Ce n'est pas le cas.
C'est juste un peu plus difficile à activer ;) Comme proposé dans un article récent de F1LT3R, vous pouvez créer des ombres intérieures en utilisant les propriétés uniques des règles d'enroulement dans le sens des aiguilles d'une montre et dans le sens inverse. Pour ce faire, vous devez créer une "ombre intérieure" en dessinant un rectangle de conteneur, puis, en utilisant des règles d'enroulement opposées, dessiner une forme découpée, ce qui crée l'inverse de la forme.
L'exemple suivant permet de styliser l'ombre intérieure et le fillStyle avec une couleur, un dégradé et un motif simultanément. Vous pouvez spécifier la rotation du motif individuellement. Notez que les rayures de zèbre sont désormais perpendiculaires les unes aux autres. Un masque d'écrêtage de la taille du cadre de délimitation est utilisé, ce qui évite d'avoir à utiliser un conteneur très grand pour entourer la forme découpée. Cela améliore la vitesse en empêchant le traitement des parties inutiles de l'ombre.
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();
};
Ces exemples montrent qu'avec globalCompositeOperation, nous pouvons enchaîner les effets pour produire des effets plus élaborés (en utilisant le masquage et le mélange). L'écran est à vous ;)
Spaceage : effets génératifs
Dans <canvas>, en partant du caractère Unicode 0x2708 :
…à cet exemple ombré :
…peut être obtenu en appelant plusieurs fois ctx.strokeText() avec une faible épaisseur de trait (0,25), tout en diminuant lentement le décalage x et l'alpha, ce qui donne une impression de mouvement à nos éléments vectoriels.
En mappant la position XY des éléments sur une onde sinusoïdale/cosinusoïdale et en faisant défiler les couleurs à l'aide de la propriété HSL, nous pouvons créer des effets plus intéressants, comme cet exemple de "danger biologique" :
TSL : teinte, saturation, luminosité (1978)
Le format HSL est désormais compatible avec les spécifications CSS3. Alors que le format HEX a été conçu pour les ordinateurs, le format HSL est conçu pour être lisible par l'humain.
Pour illustrer la simplicité du modèle TSL, pour parcourir le spectre de couleurs, il suffit d 'incrémenter la "teinte" à partir de 360. La teinte est mappée au spectre de manière cylindrique. La luminosité contrôle l'intensité de la couleur : 0 % indique un pixel noir, tandis que 100 % indique un pixel blanc. La saturation contrôle la luminosité ou l'éclat d'une couleur. Les gris sont créés avec une saturation de 0 % et les couleurs vives avec une valeur de 100 %.
Comme HSL est une norme récente, vous pouvez continuer à prendre en charge les anciens navigateurs, ce qui est possible grâce à la conversion de l'espace colorimétrique. Le code suivant accepte un objet HSL { H: 360, S: 100, L: 100} et génère un objet RVB { R: 255, G: 255, B: 255 }. Vous pouvez ensuite utiliser ces valeurs pour créer votre chaîne RVB ou RVBA. Pour en savoir plus, consultez l 'article Wikipédia sur le 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
};
};
Créer des animations avec requestAnimationFrame
Auparavant, pour créer des animations en JavaScript, deux choix s'offraient à vous : setTimeout et setInterval.
window.requestAnimationFrame est la nouvelle norme qui remplace les deux autres. Elle permet d'économiser de l'électricité (et de soulager un peu votre ordinateur) en permettant au navigateur de réguler les animations en fonction des ressources disponibles.
Voici quelques-unes des fonctionnalités importantes :
- Lorsqu'un utilisateur quitte le cadre, l'animation peut ralentir ou s'arrêter complètement pour éviter d'utiliser des ressources inutiles.
- La fréquence d'images est limitée à 60 FPS. En effet, ce taux est bien supérieur à ce que l 'œil humain peut percevoir (la plupart des humains voient une animation "fluide" à 30 FPS).
Au moment de la rédaction de ce document, des préfixes spécifiques au fournisseur sont requis pour utiliser requestAnimationFrame.
Paul Irish a créé une couche shim compatible avec plusieurs fournisseurs, dans 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);
};
})();
Pour aller un peu plus loin, les plus ambitieux d'entre vous peuvent associer cela à un polyfill tel que requestAnimationFrame.js (quelques fonctionnalités restent à développer) qui permettrait de prendre en charge les anciens navigateurs dans une plus large mesure, tout en passant à cette nouvelle norme.
(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);
})();
Code source
Grâce à la prise en charge de l'ensemble des fournisseurs de navigateurs, l'avenir de <canvas> ne fait aucun doute. Il peut être porté sur les exécutables iPhone/Android/ordinateur à l'aide de PhoneGap ou