En coulisses : Bubbles

IEBlog Français

Blog de l'équipe de développement de Windows Internet Explorer

En coulisses : Bubbles

  • Comments 0

Dans le billet, nous parlons des améliorations apportées aux performances JavaScript dans IE10. Aujourd'hui, nous avons publié la démonstration Bubbles, inspirée à l'origine par la simulation BubbleMark d'Alex Gavriolov pour explorer certaines de ces avancées. La version actuelle a été nettement développée pour tirer parti des nouvelles fonctionnalités de la plateforme Web et inclut des caractéristiques communes aux jeux HTML5. Dans ce billet, nous nous intéressons à ce qui se passe en coulisses pour voir comment la démonstration s'articule et examiner les principaux facteurs qui affectent ses performances.

Capture d'écran de la démonstration Bubbles s'exécutant dans IE10 de style Metro dans Windows 8 Release Preview
Démonstration Bubbles s'exécutant dans IE10 de style Metro dans Windows 8 Release Preview

Structure de la démonstration

La démonstration se compose de plusieurs bulles animées flottant dans l'espace. À la base, on trouve un moteur physique JavaScript relativement simple. Sur chaque image de l'animation, environ 60 fois par seconde, le moteur physique recalcule les positions de toutes les bulles, ajuste la vitesse de chaque bulle en appliquant la gravité, puis calcule les collisions. Tous ces calculs impliquent un grand nombre de formules mathématiques avec valeurs à virgule flottante. Chaque bulle est représentée à l'écran par un élément d'image DOM auquel une transformation CSS est appliquée. L'image est d'abord convertie autour de son origine, puis mise à l'échelle dynamiquement pour produire l'effet de gonflement de la bulle. Dans JavaScript, chaque bulle est représentée en tant qu'objet avec des propriétés d'accesseur, tel qu'introduit dans ECMAScript 5.

Derrière les bulles flottantes se trouve une image de grande taille, qui commence à être occultée par un masque entièrement opaque, rendu dans un élément <canvas>. Lorsque deux bulles entrent en collision, une portion du masque occultant est supprimée par l'application d'un filtre gaussien au composant d'opacité du masque pour produire un effet de transparence diffuse. Ce processus implique également de nombreuses multiplications de valeurs à virgule flottante, qui sont effectuées sur des éléments de tableaux typés, s'ils sont pris en charge par le navigateur.

Sur les bulles flottantes repose une surface tactile, qui répond aux saisies tactiles si cela est pris en charge par le navigateur actif. Sinon, elle répond aux événements de souris. En réponse aux saisies tactiles, une répulsion magnétique simulée est appliquée aux bulles flottantes afin de les éparpiller.

Animation efficace

Dans IE10, nous avons introduit la prise en charge de l'API requestAnimationFrame. Lorsque vous concevez des applications JavaScript avec une animation, nous continuons à recommander l'utilisation de cette API (au lieu de setTimeout ou de setInterval) sur les navigateurs qui la prennent en charge. Comme nous l'avons indiqué dans un billet précédent détaillant comment tirer pleinement parti de votre matériel, cette API vous permet d'obtenir la fréquence d'images maximale prise en charge par l'affichage disponible tout en évitant un travail excessif qui resterait invisible à l'utilisateur. En offrant la meilleure expérience utilisateur possible en un nombre minimal d'opérations, vous pouvez préserver l'autonomie de la batterie. IE10 Release Preview prend en charge cette API sans préfixe de fournisseur, mais la version préfixée est également gérée pour des raisons de compatibilité avec les versions d'aperçu antérieures d'IE10. La démonstration Bubbles utilise cette API et revient à setTimeout lorsqu'elle n'est pas disponible.

Demo.prototype.requestAnimationFrame = function () {

var that = this;

if (window.requestAnimationFrame)

this.animationFrameTimer =

window.requestAnimationFrame(function () { that.onAnimationFrame(); });

else

this.animationFrameTimer =

setTimeout(function () { that.onAnimationFrame(); }, this.animationFrameDuration);

}

 

Demo.prototype.cancelAnimationFrame = function () {

if (window.cancelRequestAnimationFrame)

window.cancelRequestAnimationFrame(this.animationFrameTimer);

else

clearTimeout(this.animationFrameTimer);

}

Conversion de valeurs DOM de chaînes en nombres

JavaScript est très flexible et inclut une plage de conversions automatiques entre des valeurs de différents types. Par exemple, les valeurs de chaînes sont automatiquement converties en valeurs numériques lorsqu'elles sont utilisées dans des opérations arithmétiques. Dans les navigateurs modernes, le côté pratique de cette fonctionnalité peut induire un coût étonnamment élevé en termes de performances. Le code spécialisé type émis par des compilateurs JavaScript sophistiqués est très efficace en arithmétique sur les valeurs de types connus, mais entraîne une importante surcharge lorsqu'il rencontre des valeurs de types inattendus.

Lorsque la démonstration Bubbles est chargée, la propriété numberOfBubbles se lance avec la valeur 100. Sur chaque image de l'animation, la position de chaque bulle est ajustée :

function Demo() {

this.numberOfBubbles = 100;

//...

}

 

Demo.prototype.moveBubbles = function(elapsedTime) {

for (var i = 0; i < this.numberOfBubbles; i++) {

this.bubbles[i].move(elapsedTime, this.gravity);

}

}

Lorsque l'utilisateur sélectionne une valeur différente dans l'interface utilisateur, la valeur de la propriété numberOfBubbles doit être réglée en conséquence. Un gestionnaire d'événements simple peut le faire comme suit :

Demo.prototype.onNumberOfBubblesChange = function () {

this.numberOfBubbles = document.getElementById("numberOfBubblesSelector").value;

//...

}

Cette façon apparemment naturelle de lire la saisie de l'utilisateur entraîne environ 10 % de surcharge dans la portion JavaScript de la démonstration. Comme la valeur obtenue du menu déroulant et attribuée à numberOfBubbles est une chaîne (et non un nombre), elle a été convertie en nombre sur chaque itération de la boucle moveBubbles pour chaque image de l'animation.

Cela démontre qu'il est recommandé de convertir explicitement les valeurs extraites de l'objet DOM en nombres avant de les utiliser dans des opérations arithmétiques. Les valeurs des propriétés DOM sont généralement de type chaîne dans JavaScript et les conversions automatiques de chaînes en nombres peuvent avoir un coût important lorsqu'elles sont répétées fréquemment. Pour mettre à jour numberOfBubbles en fonction de la sélection de l'utilisateur, comme dans la démonstration, il convient de procéder comme suit :

Demo.prototype.onNumberOfBubblesChange = function () {

this.numberOfBubbles = parseInt(document.getElementById("numberOfBubblesSelector").value);

//...

}

Utilisation des propriétés d'accesseur ES5

Les propriétés d'accesseur ECMAScript 5 sont une méthode pratique pour l'encapsulation des données, les propriétés calculées, la validation des données ou la notification de modification. Dans la démonstration Bubbles, lorsqu'une bulle est gonflée, le rayon est ajusté, ce qui définit la propriété radiusChanged calculée de façon à indiquer que l'image de la bulle doit être redimensionnée.

Object.defineProperties(Bubble.prototype, {

//...

radius: {

get: function () {

return this.mRadius;

},

set: function (value) {

if (this.mRadius != value) {

this.mRadius = value;

this.mRadiusChanged = true;

}

}

},

//...

});

Dans tous les navigateurs, les propriétés d'accesseur ajoutent une surcharge par rapport aux propriétés des données. Le volume de surcharge exact varie selon les navigateurs.

Limitation de l'accès à ImageData du dessin

C'est une pratique bien établie de limiter le nombre d'appels à l'objet DOM dans les boucles en ce qui concerne les performances. Par exemple, si la démonstration Bubbles mettait à jour l'emplacement de chaque bulle en regardant l'élément correspondant dans le document (comme ci-dessous), les performances seraient négativement affectées.

Bubble.prototype.render = function () {

document.getElementById("bubble" + this.id).style.left = Math.round(this.x) + "px";

document.getElementById("bubble" + this.id).style.top = Math.round(this.y) + "px";

this.updateScale();

}

À la place, l'élément correspondant à chaque bulle est mis en cache une fois dans l'objet de la bulle dans JavaScript, puis est utilisé directement sur chaque image de l'animation.

Bubble.prototype.render = function () {

this.element.style.left = Math.round(this.x) + "px";

this.element.style.top = Math.round(this.y) + "px";

this.updateScale();

}

Ce qui est peut-être moins clair, c'est qu'il faut être très prudent pour éviter une surcharge similaire lors de l'utilisation de l'élément <canvas>. L'objet obtenu en appelant canvas.getContext("2D").getImageData() est également un objet DOM. Le code ci-dessous pourrait être utilisé dans la démonstration pour dessiner l'effet de collision des bulles dans le dessin. Dans cette version, imgData.data est lu sur chaque itération de la boucle, ce qui requiert un appel à l'objet DOM et renforce la surcharge.

BubbleTank.prototype.renderCollisionEffectToCanvas = function(px, py) {

var imgData = this.canvasContext.getImageData(/*...*/)

//...

for (var my = myMin; my <= myMax; my++) {

for (var mx = mxMin; mx <= mxMax; mx++) {

var i = (mx + gaussianMaskRadius) + (my + gaussianMaskRadius) * gaussianMaskSize;

imgData.data[4 * i + 3] = 255 * occlusionMask[(px + mx) + (py + my) * canvasWidth];

}

}

this.canvasContext.putImageData(imgData, px - gaussianMaskRadius, py - gaussianMaskRadius);

}

Pour mettre à jour les données d'image <canvas>, il est préférable de mettre en cache la propriété data comme dans l'extrait de code suivant. La propriété data est un réseau typé (PixelArray), qui peut être très efficacement utilisé dans JavaScript.

BubbleTank.prototype.renderCollisionEffectToCanvas = function(px, py) {

var imgData = this.canvasContext.getImageData(/*...*/)

var imgColorComponents = imgData.data;

//...

for (var my = myMin; my <= myMax; my++) {

for (var mx = mxMin; mx <= mxMax; mx++) {

var i = (mx + gaussianMaskRadius) + (my + gaussianMaskRadius) * gaussianMaskSize;

imgColorComponents[4 * i + 3] =

255 * occlusionMask[(px + mx) + (py + my) * canvasWidth];

}

}

this.canvasContext.putImageData(imgData, px - gaussianMaskRadius, py - gaussianMaskRadius);

}

Utilisation de tableaux typés pour stocker des nombres à virgule flottante

Dans IE10, nous prenons en charge les tableaux typés. Lorsque vous utilisez des nombres à virgule flottante, il est intéressant d'utiliser des tableaux typés (Float32Array ou Float64Array) au lieu des tableaux JavaScript (Array). Les tableaux JavaScript peuvent contenir des éléments de n'importe quel type, mais nécessitent généralement que les valeurs à virgule flottante soient allouées sur le tas (« heap boxing ») avant d'être ajoutées au tableau. Cela affecte les performances. Pour des performances optimales sur les navigateurs modernes, utilisez Float32Array ou Float64Array pour indiquer votre intention de stocker des valeurs à virgule flottante. Vous aidez ainsi le moteur JavaScript à éviter le « heap boxing » et à activer d'autres optimisations de compilateur, par exemple la génération d'opérations spécialisées type.

BubbleTank.prototype.generateOcclusionMask = function() {

if (typeof Float64Array != "undefined") {

this.occlusionMask = new Float64Array(this.canvasWidth * this.canvasHeight);

} else {

this.occlusionMask = new Array(this.canvasWidth * this.canvasHeight);

}

this.resetOcclusionMask();

}

L'exemple ci-dessus montre comment la démonstration Bubbles utilise Float64Arrays pour contenir et mettre à jour le masque occultant appliqué au dessin, qui masque l'image d'arrière-plan. Si le navigateur ne prend pas en charge les tableaux typés, le code revient aux tableaux standard. Les avantages de l'utilisation des tableaux typés dans la démonstration Bubbles varient selon les paramètres. Dans une fenêtre de taille moyenne d'IE10, les tableaux typés améliorent la fréquence d'images globale d'environ 10 %.

Résumé

Dans ce billet de blog, nous avons exploré la démonstration Bubbles récemment publiée et avons ��tudié comment bénéficier des énormes avantages de l'exécution de JavaScript dans IE10 Release Preview. Nous avons partagé certaines techniques importantes pour parvenir à des performances optimales dans les applications basées sur les animations. Pour obtenir plus de détails techniques sur les modifications apportées à l'exécution de JavaScript (Chakra) dans IE10 Release Preview, consultez le billet précédent. Nous sommes heureux de l'amélioration des performances et des nouvelles fonctionnalités disponibles dans IE10 Release Preview et nous espérons que cela permettra de concevoir des applications encore plus fascinantes à l'aide des normes Web et des technologies.

—Andrew Miadowicz, chef de projet, JavaScript

  • Loading...