Effets typographiques des canevas

Mon arrière-plan

<canvas> m'a fait connaître en 2006, avec le lancement de la version 2.0 de Firefox. Un article sur Ajaxian, décrivant la matrice de transformation, m'a donné envie de créer ma première application Web <canvas>, Color Sphere (2007). Ce qui m'a immergé dans le monde des couleurs et des primitives graphiques, inspirant la création de Sketchpad (2007-2008) dans le but de créer une application "mieux que Paint" dans le navigateur. Ces expériences ont finalement conduit à la création de la startup Mugtug avec mon ami de longue date Charles Pritchard. Nous développons la fonctionnalité Darkroom en langage HTML5 au format <canvas>. Darkroom est une application de partage de photos non destructive, qui associe les pouvoirs des filtres basés sur les pixels à une typographie et un dessin vectoriels.

Introduction

Image de bannière Canvas.

<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 de l'écran.

Les exemples suivants traitent d'une zone 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 sous-section de ce qui est possible. Bien que nous parlions de "texte" dans cet article, les méthodes peuvent être appliquées à tous les objets vectoriels, créant ainsi des visuels attrayants dans les jeux et d'autres applications:

Ombres du texte dans <canvas>.
Effets de texte de type CSS dans <canvas>, qui créent des masques d'écrêtage, recherchent des métriques dans <canvas> et utilisent la propriété d'ombre.
Néon arc-en-ciel, reflets zébrés : effets d'enchaînement.
Effets de texte de type Photoshop dans les exemples <canvas> d'utilisation de globalCompositeOperation, createLinearGradient et createPattern.
Ombres intérieures et extérieures en <canvas>
Révélation d'une petite caractéristique connue ; utilisation du remontage dans le sens des aiguilles d'une montre ou dans le sens inverse des aiguilles d'une montre pour créer l'inverse d'une ombre projetée (l'ombre intérieure)
Effet spatial : effet génératif.
Effet de texte génératif dans <canvas> utilisant le cycle de couleurs hsl() et window.requestAnimationFrame pour créer une sensation de mouvement.

Ombres de texte dans Canvas

L'un de mes ajouts préférés aux spécifications CSS3 (comme les bordures et les dégradés Web) est la possibilité de créer des ombres. Il est important de comprendre les différences entre les ombres CSS et <canvas>, en particulier:

CSS utilise deux méthodes : box-shadow pour les éléments "box-elements", tels que div, span, et ainsi de suite, et text-shadow pour le contenu textuel.

<canvas> comporte un type d 'ombre : il est utilisé pour tous les objets vectoriels : dfa.moveTo, ctx.lineTo, ctx.bezierCurveTo, dfa.quadradicCurveTo, dfa.arc, ctx.rect, dfa.fillText, dfa.strokeText, etc. Pour créer une ombre dans <canvas>, appuyez sur les quatre propriétés suivantes:

ctx.shadowColor = "red" // chaîne
Couleur de l'ombre. Les entrées RVB, RVBA, HSL, HEX et autres sont valides.
ctx.shadowOffsetX = 0; // nombre entier
Distance horizontale de l'ombre par rapport au texte.
ctx.shadowOffsetY = 0; // nombre entier
Distance verticale de l'ombre par rapport au texte.
ctx.shadowBlur = 10; // nombre entier
Floutage de l'effet sur l'ombre : plus la valeur est élevée, plus le flou est important.

Pour commencer, voyons comment <canvas> peut émuler les effets CSS. La recherche de "text-shadow" dans Google Images a permis d'obtenir quelques démonstrations intéressantes à émuler : Line25, Stereoscopic et Shadow 3D.

Graphisme 3D CSS

L'effet 3D stéréoscopique (voir Image d'analyse pour en savoir plus) est un exemple de ligne de code simple, particulièrement utile. La ligne CSS suivante permet de créer l'illusion de profondeur avec des lunettes rouge/cyan 3D (comme celles qu'elles donnent dans les films en 3D):

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

Deux éléments sont à noter lors de la conversion de cette chaîne en <canvas>:

  1. Il n'y a pas de floutage (troisième valeur). Il n'y a donc aucune raison d'exécuter une ombre, car fillText créerait 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>
  1. Les EM ne sont pas compatibles avec <canvas>. Vous devez donc les convertir en PX. Nous pouvons trouver le ratio de conversion entre PT, PC, EM, EX, PX et autres 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. Pour capturer la conversion EM -> PX, nous mesurerions l'élément DOM avec une hauteur : "hauteur: 1Xem".
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, comme l'effet Neon de Line25, la propriété shadowBlur doit être utilisée pour émuler correctement l'effet. Étant donné que l'effet Neon repose sur plusieurs ombres, un problème est survenu. Dans <canvas>, chaque objet vectoriel ne peut avoir qu'une seule ombre. Ainsi, pour dessiner plusieurs ombres, vous devez dessiner plusieurs versions du texte par-dessus lui-même. Il en résulte une multiplication alpha et, en fin de compte, des arêtes irrégulières.

Image au néon

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 était vaine. Étant donné que l'ombre est une multiplication de la valeur alpha fillStyle, l'ombre ne peut jamais être plus opaque que fillStyle.

Heureusement, il existe un moyen de contourner ce problème:

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écoupe autour d'un bloc de texte

Pour résoudre ce problème, nous pouvons empêcher le fillText d'être dessiné en premier (tout en laissant l'ombre être dessinée) en ajoutant un chemin de découpe. Pour créer un chemin de découpe autour du texte, nous devons connaître la hauteur du texte (appelée historiquement "em-height", la hauteur de la lettre "M" sur une presse à imprimer) 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 au piratage CSS (consultez Métriques typographiques pour découvrir d'autres moyens de corriger les anciennes implémentations de <canvas> à l'aide des mesures CSS), nous pouvons trouver la hauteur du texte en mesurant la offsetHeight d'une <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à, vous pouvez créer un rectangle à utiliser comme chemin de découpe ; en englobant "l'ombre" tout en supprimant la forme factice.

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

Liez tout ensemble et optimisez-le au fur et à mesure : si une ombre n'est pas floue, fillText peut être utilisé pour le même effet, ce qui nous évite de configurer le masque de rognage :

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

Étant donné que vous ne souhaitez pas saisir toutes ces commandes <canvas> manuellement, j'ai inclus un analyseur d 'ombres de texte simple dans la source de démonstration. Ainsi, vous pouvez lui transmettre des commandes CSS et lui demander de générer des commandes <canvas>. Désormais, nos éléments <canvas> peuvent être associés à toute une gamme de styles. Ces mêmes effets d'ombre peuvent être utilisés sur n'importe quel objet vectoriel, de WebFonts aux formes complexes importées depuis SVG, en passant par les formes vectorielles génératives, etc.

Ombre du texte dans les effets de canevas

Intermission (tangente lors du déplacement de pixels)

En rédigeant cette section de l'article, l'exemple stéréoscopique a éveillé ma curiosité. Dans quelle mesure est-il difficile de créer un effet d'écran de film 3D en utilisant <canvas> et deux images prises dans des perspectives légèrement différentes ? Apparemment, pas trop difficile. Le noyau suivant combine le canal rouge de la première image (données) 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;

À présent, il suffit de coller du ruban adhésif sur son front avec deux iPhone et de cliquer sur "Enregistrer une vidéo" en même temps. Nous pourrions alors créer nos propres films en 3D en HTML5. Y a-t-il des bénévoles ?

Lunettes 3D

Néon arc-en-ciel, réflexion sur les zèbres : effets d'enchaînement

Enchaîner 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 l'plus clair peut être considéré comme des modes de combinaison des calques. Les 10 autres opérations sont appliquées aux calques en tant que masques alpha (une couche supprime les pixels de l'autre couche). GlobalCompositeOperation relie des "couches" (ou, dans notre cas, des chaînes de code) entre elles, les combinant de manière inédite et intéressante:

Enchaînement de graphiques avec effets

Le graphique globalCompositeOperation montre les modes GCO en action. Ce graphique utilise une grande partie du spectre de couleurs et plusieurs niveaux de transparence alpha pour voir en détail à quoi vous attendre. Je vous recommande de consulter la documentation de référence sur globalCompositeOperation de Mozilla pour obtenir une description textuelle. Pour approfondir la recherche, vous pouvez découvrir le fonctionnement de l 'opération dans le document Compositing Digital Images de Porter Duff.

Mon mode préféré est globalCompositeOperation="lighter". L'éclairage clair mélange les pixels ajoutés de la même manière que la lumière. Lorsque la lumière rouge, verte et blanche est à pleine intensité, nous voyons une lumière blanche. Il s'agit d'une fonctionnalité passionnante, 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 fluides. L'allumage a été utilisé de nombreuses façons. Récemment, j'ai préféré créer des arrière-plans pour ordinateur de bureau HTML5 à l'adresse http://weavesilk.com/. L'une de mes démonstrations, Breathing Galaxies (JS1k), utilise également le mode plus léger. En dessinant des modèles à partir de ces deux exemples, vous commencez à voir l'effet généré par ce mode.

Gestion du navigateur globalCompositeOperation.

La gigue arc-en-ciel au néon

Dans la démonstration suivante, nous allons créer un éclat néon-arc-en-ciel semblable à Photoshop avec un contour irrégulier, en enchaînant des effets à l 'aide de globalCompositeOperation (source-in, plus clair et plus sombre). Cette démonstration est une progression de la démo "Ombres de texte dans <canvas>", dans laquelle la même stratégie est utilisée pour séparer l'ombre du texte (voir la section précédente):

Gigue arc-en-ciel
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 réflexion zèbre

L'effet Zebra Reflection s'est inspiré de l'excellente ressource de WebDesignerWall pour pimenter votre page avec le CSS. Cela pousse l'idée un peu plus loin, créant une "réflexion" pour le texte, comme ce que vous pourriez voir dans iTunes. L'effet combine les valeurs fillColor (blanc), createPattern (zebra.png) et linearGradient (shine). Il montre la possibilité d'appliquer plusieurs types de remplissage à chaque objet vectoriel:

Effet zèbre
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> ne traitent pas du sujet des ombres "intérieures" et "externes". En fait, à la première apparence, on peut s'attendre à ce que l'ombre « intérieure » ne soit pas prise en charge. Ce n'est pas le cas. C'est un peu plus compliqué à activer. Comme indiqué dans un article récent de F1LT3R, vous pouvez créer des ombres internes à l'aide des propriétés uniques des règles de sens des aiguilles d'une montre par rapport à celles dans le sens inverse des aiguilles d'une montre. Pour ce faire, créez une "ombre intérieure" en dessinant le rectangle du conteneur, puis, à l'aide des règles d'enroulement opposées, dessinez une forme d'encoche, créant ainsi l'inverse de la forme.

L'exemple suivant permet de styliser l'ombre interne et le fillStyle avec des éléments color+dégradé+motif simultanément. Vous pouvez spécifier la rotation des motifs individuellement. Notez que les bandes de zèbres sont désormais perpendiculaires les unes aux autres. Un masque de rognage de la taille du cadre de délimitation est utilisé, ce qui évite d'avoir à utiliser un très grand conteneur pour envelopper la forme d'encoche. Cela améliore la vitesse en empêchant le traitement des parties inutiles de l'ombre.

Ombres intérieures/extérieures
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();
};

Sur la base de ces exemples, vous pouvez voir que, à l'aide de globalCompositeOperation, nous pouvons enchaîner des effets, produisant ainsi des effets plus élaborés (en utilisant le masquage et le mélange). L'écran est votre huître ;)

Effet spatial – Effets génératifs

Dans <canvas>, en partant du caractère Unicode 0x2708:

Ggfaphique Unicode

...à cet exemple ombré:

Exemple ombré

... peut être réalisé par plusieurs appels à ctx.strokeText() avec une ligne de largeur fine (0,25), tout en diminuant lentement le décalage X et la valeur alpha, ce qui donne aux éléments vectoriels une sensation de mouvement.

En mappant la position XY des éléments à une onde sinus/cosinus et en parcourant les couleurs à l'aide de la propriété HSL, nous pouvons créer des effets plus intéressants, comme cet exemple de "biohazard" :

Effet de cyclisme HSL

HSL: Teinte, saturation, luminosité (1978)

Le format HSL est un nouveau format accepté dans les spécifications CSS3. Alors que le code HEX a été conçu pour les ordinateurs, HSL est conçu pour être lisible par l'homme.

Pour illustrer la facilité du traitement, il suffit d'incrémenter la "teinte" de 360 ; pour parcourir le spectre de couleurs, il suffit d'incrémenter la "teinte" de 360 ; la teinte est mappée au spectre de manière cylindrique. La luminosité contrôle le niveau d'obscurité de la couleur. 0% indique un pixel noir, tandis que 100% indique un pixel blanc. La saturation contrôle la luminosité d 'une couleur. Les gris sont créés avec une saturation de 0 % et les couleurs vives sont créées avec une valeur de 100%.

Image HSL

Étant donné que le TSL est une norme récente, vous pouvez continuer à prendre en charge les navigateurs plus anciens, ce qui est possible via 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 }. À partir de là, vous pouvez utiliser ces valeurs pour créer votre chaîne rgb ou rgba. Pour en savoir plus, consultez l 'article pertinent de Wikipédia sur 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, il existait deux options : setTimeout et setInterval.

window.requestAnimationFrame est la nouvelle norme qui remplace les deux. Elle permet d'économiser de l'électricité (et de limiter le temps passé à 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 existe le frame, l'animation peut ralentir ou s'arrêter complètement, afin d'éviter l'utilisation de ressources inutiles.
  • La fréquence d'images est limitée à 60 FPS. En effet, elle est largement supérieure au niveau que les humains peuvent remarquer (la plupart des humains à 30 FPS voient l 'animation être "fluide").

Au moment de la rédaction de ce document, les préfixes spécifiques aux sous-revendeurs doivent utiliser requestAnimationFrame. Paul Irish a créé un calque shim compatible avec cross-vender 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);
        };
})();

Les plus ambitieux peuvent lier cela à un polyfill comme requestAnimationFrame.js (il y a quelques fonctionnalités à élaborer) qui prendrait davantage en charge les anciens navigateurs, 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);
})();
Image Flou des notes
Graphique d&#39;animation
Image de la matrice

Code source

Grâce à la prise en charge de différents fournisseurs de navigateurs, il n'est pas question de l'avenir de <canvas>. Il peut être transféré vers les exécutables iPhone/Android/Desktop à l'aide de PhoneGap, ou

Titanium