Mon parcours
<canvas>
est devenu ma notoriété en 2006, lorsque Firefox v2.0 est sorti. Un article sur Ajaxian, qui décrit la matrice de transformation, m'a inspiré pour créer ma première application Web <canvas>
: Color Sphere (2007). Ce qui 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 tests ont finalement conduit à la création de la start-up Mugtug avec mon ami de longue date Charles Pritchard. Nous développons la fonctionnalité Darkroom en HTML5 <canvas>
. Darkroom est une application de partage de photos non destructive qui associe la puissance des filtres basés sur des pixels à une typographie et un dessin vectoriels.
Introduction
<canvas>
offre aux programmeurs JavaScript un contrôle total sur les couleurs, les vecteurs et les pixels de leurs écrans, c'est-à-dire la composition visuelle de l'écran.
Les exemples suivants concernent un aspect 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 des possibilités. Bien que nous traitions du "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 du texte dans
<canvas>
. - Effets de texte similaires à CSS dans
<canvas>
, création de masques de découpe, recherche de métriques dans<canvas>
et utilisation de la propriété "ombre". - Effets de chaîne (arc-en-ciel néon, réflexion zébrée, etc.)
- Effets de texte de type Photoshop dans
<canvas>
exemples d'utilisation de globalCompositeOperation, createLinearGradient et createPattern. - Ombres intérieures et extérieures en
<canvas>
- Révélation d'une caractéristique peu connue ; utiliser le sens des aiguilles d'une montre plutôt que le sens inverse des aiguilles d'une montre pour créer l'inverse d'une ombre projetée (l'ombre interne).
- Spaceage : effet génératif.
- Effet de texte basé sur la génération dans
<canvas>
à l'aide du cycle de couleurs hsl() et dewindow.requestAnimationFrame
pour créer une sensation de mouvement.
Ombres de texte dans Canvas
L'une des nouveautés que je préfère dans les spécifications CSS3 (avec border-radius, les dégradés Web et d'autres) est la possibilité de créer des ombres. Il est important de connaître les différences entre les ombres CSS et <canvas>
, en particulier:
CSS utilise deux méthodes : box-shadow pour les éléments de type "box-elements" tels que div, span, etc., et text-shadow pour le contenu textuel.
<canvas>
comporte 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>
, appuyez sur ces quatre propriétés:
- ctx.shadowColor = "red" // chaîne
- Couleur de l'ombre. Les valeurs RVB, RVBA, HSL, HEX et autres sont acceptées.
- ctx.shadowOffsetX = 0; // entier
- Distance horizontale de l'ombre par rapport au texte.
- ctx.shadowOffsetY = 0; // entier
- Distance verticale de l'ombre par rapport au texte.
- ctx.shadowBlur = 10; // nombre entier
- Effet de floutage de l'ombre. Plus la valeur est élevée, plus le floutage est important.
Pour commencer, voyons comment <canvas>
peut émuler les effets CSS.
En recherchant "css text-shadow" dans Google Images, nous avons trouvé quelques démonstrations intéressantes à émuler : Line25, Stereoscopic et Shadow 3D.
L'effet 3D stéréoscopique (pour en savoir plus, consultez Image anaglyphe) est un exemple de ligne de code simple, très utile. Avec la ligne de code CSS suivante, nous pouvons créer l'illusion de profondeur lorsqu'elle est vue avec des lunettes 3D rouge/cyan (celles que l'on vous donne pour les films 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 (troisième valeur). Il n'y a donc aucune raison d'exécuter une ombre, car fillText générerait 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>
. Vous devrez donc les convertir 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 mesurerions l'élément DOM avec une valeur "height: 1em". La valeur offsetHeight résultante 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 trouvé sur Line25, la propriété shadowBlur doit être utilisée pour émuler correctement l'effet. Étant donné que 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 par-dessus lui-même.
Cela entraîne une multiplication alpha et, par conséquent, des bords dentelés.
J'ai essayé d'exécuter ctx.fillStyle = "rgba(0,0,0,0)"
ou "transparent"
pour masquer le texte, tout en affichant l'ombre... Cependant, cette tentative a été vaine. Étant donné que l'ombre est une multiplication de fillStyle alpha, l'ombre ne peut jamais être plus opaque que fillStyle.
Heureusement, il existe un moyen de contourner ce problème. Nous pouvons dessiner le décalage d'ombre à partir du texte, en les maintenant séparés (afin qu'ils ne se chevauchent pas) et en masquant ainsi 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écouper un bloc de texte
Pour nettoyer un peu cela, nous pouvons empêcher que le fillText s'affiche en premier lieu (tout en autorisant le dessin de l'ombre) en ajoutant un chemin de découpe.
Pour créer un tracé de détourage entourant le texte, nous devons connaître la hauteur du texte (appelée "hauteur en em", historiquement 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 à un hack CSS (voir Typographic Metrics 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 la 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;
Nous pouvons ensuite créer un rectangle à utiliser comme tracé de découpe, en l'englobant "ombre" tout en supprimant la forme factice.
ctx.rect(0, 0, width, emHeight);
ctx.clip();
Pour résumer et optimiser au fur et à mesure, si une ombre n'est pas floutée, vous pouvez utiliser fillText pour obtenir le même effet, ce qui vous évite de configurer le masque de découpe:
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 souhaitez pas saisir toutes ces commandes <canvas>
manuellement, j'ai inclus un analyseur d'ombre de texte simple dans la source de démonstration. De cette façon, vous pouvez l'alimenter en commandes CSS et lui demander de 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, qu'il s'agisse de WebFonts, de formes complexes importées à partir de SVG, de formes vectorielles génératives, etc.
Intermède (tangente sur le pixel-pushing)
En rédigeant cette section de l'article, l'exemple stéréoscopique m'a intrigué. 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;
Il ne reste plus qu'à coller deux iPhones sur le front, appuyer sur "Enregistrer une vidéo" en même temps et nous pourrons créer nos propres films 3D en HTML5. Des bénévoles ?
Effets de chaîne (arc-en-ciel néon, réflexion zébrée)
Associer 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>
. plus sombre et plus clair 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 (une couche supprime les pixels de l 'autre couche). globalCompositeOperation lie les "couches" (ou, dans notre cas, les chaînes de code) entre elles, en les combinant de manière nouvelle et intéressante:
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 afin de voir en détail à 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 cette opération dans Compositing Digital Images (Compositing Digital Images) de Porter Duff.
Mon mode préféré est globalCompositeOperation="lighter". Le mode Lumière 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. 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 fois. Mon outil préféré est un outil de création d'arrière-plans HTML5 pour ordinateur, disponible sur http://weavesilk.com/.
L'une de mes démonstrations, Breathing Galaxies (JS1k), utilise également le mode plus clair. À partir de ces deux exemples, vous commencez à voir l'effet produit par ce mode.
Gestion du navigateur globalCompositeOperation
Effet de gigue fluo-arc-en-ciel
Dans la démonstration suivante, nous allons obtenir une lueur fluo-arc-en-ciel de type Photoshop avec un contour irrégulier, en associant les effets à l'aide de globalCompositeOperation (source-in, lighter et darker).
Cette démo est une progression de la démo "Text-Shadows in <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 de zèbre
L'effet de réflexion zébré a été inspiré par l'excellente ressource de WebDesignerWall sur la façon d'épicer votre page avec CSS. Cette idée va un peu plus loin en créant une "réflexion" pour le texte, comme vous pourriez le voir dans iTunes. L'effet combine fillColor (blanc), createPattern (zebra.png) et linearGradient (brillance). Il 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 Canvas
Les spécifications <canvas>
ne traitent pas de la différence entre les ombres "internes" et "externes". En fait, à la première apparition, vous pouvez vous attendre à ce que l'ombre "interne" ne soit pas prise en charge. Ce n'est pas le cas.
Il est juste un peu plus difficile à activer ;) Comme proposé dans un post récent de F1LT3R, vous pouvez créer des ombres intérieures à l'aide des propriétés uniques des règles de bobinage dans le sens des aiguilles d'une montre et dans le sens inverse. Pour ce faire, créez une "ombre intérieure" en dessinant un rectangle de conteneur, puis, à l'aide de règles de bobinage opposées, dessinez une forme découpée, créant l'inverse de la forme.
L'exemple suivant permet de styliser simultanément l'ombre intérieure et le style fillStyle avec la combinaison couleur + dégradé + motif. Vous pouvez spécifier la rotation du motif individuellement. Notez que les rayures de zèbre sont maintenant perpendiculaires les unes aux autres. Un masque de découpe de la taille de la zone de délimitation est utilisé, ce qui élimine le besoin d'un conteneur très volumineux pour englober 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();
};
À partir de ces exemples, vous pouvez voir qu'en utilisant globalCompositeOperation, nous pouvons enchaîner des effets, ce qui produit des effets plus élaborés (à l'aide du masquage et du mélange). L'écran est à vous ! ;)
Espacement : effets génératifs
Dans <canvas>
, à partir du caractère Unicode 0x2708:
...à cet exemple ombré:
... peut être réalisé en appelant plusieurs appels de ctx.strokeText()
avec une ligne lineWidth fine (0,25), tout en diminuant lentement le décalage x et 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 "biologique" :
HSL (1978) : teinte, saturation, luminosité
Le format HSL est un nouveau format accepté dans 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.
Illustration de la facilité du TSL : pour faire défiler le spectre de couleurs, il suffit d 'incrémenter la "teinte" à partir de 360 ; la teinte est mappée sur le spectre de manière cylindrique. La luminosité contrôle l'intensité de la couleur. 0% correspond à un pixel noir, tandis que 100% correspond à un pixel blanc. La saturation contrôle la luminosité ou l'intensité 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%.
Étant donné que TSL est une norme récente, vous pouvez continuer à prendre en charge les navigateurs plus anciens, ce qui est possible via la conversion d'espace colorimétrique. Le code suivant accepte un objet HSL {H: 360, S: 100, L: 100} et produit un objet RVB {R: 255, G: 255, B: 255}. Vous pouvez ensuite 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, vous aviez le choix entre setTimeout
et setInterval
.
window.requestAnimationFrame
est la nouvelle norme qui remplace les deux : elle permet d'économiser de l'électricité dans le monde (et de faire gagner quelques battements de cœur à 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 frame, 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, il est bien au-dessus du niveau que les humains peuvent remarquer (la plupart des humains considèrent que l 'animation est "fluide" à 30 FPS).
Au moment de la rédaction de ce document, les préfixes propres au fournisseur sont requis pour utiliser requestAnimationFrame
.
Paul Irish a créé une couche de shim compatible avec l'animation croisée, dans requestAnimationFrame pour l'animation intelligente:
// 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 plus loin, les plus ambitieux peuvent associer cela à un remplissage multiple tel que requestAnimationFrame.js (il y a quelques fonctionnalités à mettre au point) qui serait compatible avec les anciens navigateurs dans une plus grande 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 tous les fournisseurs de navigateurs, il n'y a aucun doute sur l'avenir de <canvas>
. Vous pourrez le transférer vers les exécutables iPhone, Android et de bureau à l'aide de PhoneGap.