Animation SVG de base

Cette rubrique aborde l’animation SVG de base et constitue un élément préalable requis pour l’animation SVG de niveau intermédiaire. Vous devez disposer de connaissances élémentaires de HTML et de JavaScript. Prévoyez de consacrer environ une heure d’étude pour comprendre parfaitement le contenu de cette rubrique.

Remarque  Pour afficher les exemples de la rubrique, vous devez utiliser un navigateur, tel que Windows Internet Explorer 9 ou versions ultérieures, qui prend en charge l’élément SVG.

Bien qu’elle ne soit pas prise en charge dans Windows Internet Explorer (comme cela est décrit ci-dessous), l’animation de base est très simple quand vous utilisez des constructions d’animation déclaratives de SVG (SMIL). Par exemple, le code suivant permet d’effectuer la rotation d’un carré de 90 degrés en cinq secondes :

Exemple 1 : animation déclarative de base (SMIL)

Lien direct : Exemple 1 (cet exemple SMIL ne fonctionne pas dans Internet Explorer)


<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg width="800px" height="800px" viewBox="0 0 800 800"
     version="1.1" xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"> <!-- Note that this is required in order to use xlink in the <use> element. -->

  <!-- THIS EXAMPLE NOT SUPPORTED IN INTERNET EXPLORER -->
  
  <title>Simplest SVG Animation</title>
  <desc>SVG declarative animation is used to rotate a square.</desc>

  <!-- Create a Cartesian coordinate system (with the y-axis flipped) for the animated square. 
       That is, place the origin at the center of the 800 x 800 SVG viewport. -->
  <g transform="translate(400, 400)"> 
  
    <!-- A 200 x 200 square with the upper left-hand corner at (-100, -100). This places the center 
         of the square at the origin (0, 0): -->  
    <rect x="-100" y="-100" width="200" height="200" rx="5" ry="5" 
          style=" fill: orange; stroke: black; stroke-width: 3; stroke-dasharray: 10, 5;">
      <animateTransform 
        attributeType="xml"
	    attributeName="transform" type="rotate"
	    from="0" to="90"
	    begin="0" dur="5s" 
	    fill="freeze"
	  />
    </rect>
    
    <line x1="-400" y1="0" x2="400" y2="0" style="stroke: black;" /> <!-- Represents the x-axis. -->
    
    <line x1="0" y1="-400" x2="0" y2="400" style="stroke: black;" /> <!-- Represents the y-axis (although up is negative and down is positive). -->  
        
  </g>
</svg>

L’exemple précédent (ainsi que les suivants) sont entièrement commentés. Cependant, quelques points utiles méritent d’être soulignés :

  • L’élément animateTransform, enfant de l’objet que nous souhaitons animer (c’est-à-dire l’élément rect), se charge d’une grande partie du travail et est relativement explicite.

  • Étant donné que le centre du carré coïncide avec l’origine de la fenêtre d’affichage qui se situe aux coordonnées (400, 400), la rotation du carré de 90 degrés s’effectue à partir de son centre. Si, par exemple, les coordonnées du carré sont définies selon x=”0” et y=”0”, comme dans l’exemple suivant :

    
    <rect x="0" y="0" width="200" height="200" rx="5" ry="5" style=" fill: orange; stroke: black; stroke-width: 3; stroke-dasharray: 10, 5;">
    
    

    La rotation de 90 degrés s’effectue alors à partir du coin supérieur gauche du carré (et non du centre). Essayez vous-même.

Même si l’animation déclarative est simple, elle peut aussi être limitée. David Eisenberg, auteur de l’ouvrage SVG nous fait part de son point de vue sur le sujet : "Si vous utilisez un script pour réaliser une animation, vous bénéficiez alors de toutes les fonctionnalités d’interactivité qu’offrent les scripts : vous pouvez guider l’animation d’après la position du pointeur de la souris ou de conditions complexes faisant appel à plusieurs variables."

En d’autres termes, l’animation par script offre des possibilités d’animation simples comme complexes. Pour cette raison, mais aussi pour d’autres (telles que les animations CSS), Internet Explorer ne prend pas en charge l’animation déclarative. Incontestablement, l’animation par script représente un travail plus important, mais une fois les techniques de script maîtrisées, vous pouvez implémenter des animations, auparavant impossibles, à l’aide de techniques d’animation purement déclaratives. L’exemple suivant, qui correspond à la version JavaScript (en HTML5) de l’Exemple 1, présente certaines de ces techniques :

Exemple 2 : animation JavaScript de base

Lien direct : Exemple 2


<!DOCTYPE html>
<html>

<head>  
  <title>JavaScript SVG Animation</title>
  <meta http-equiv="X-UA-Compatible" content="IE=Edge"/> <!-- Remove this line in production. -->
</head>

<body>
  <svg width="800px" height="800px" viewBox="0 0 800 800">
    <g transform="translate(400, 400)"> <!-- Create a Cartesian coordinate system (with the y-axis flipped) for the animated square. That is, place the origin at the center of the 800 x 800 SVG viewport: -->
  
      <!-- A 200 x 200 square with the upper left-hand corner at (-100, -100). This places the center of the square 
      at the origin (0, 0): -->  
      <rect id="mySquare" x="-100" y="-100" width="200" height="200" rx="5" ry="5" 
            style=" fill: orange; stroke: black; stroke-width: 3; stroke-dasharray: 10, 5;" />
            
      <!-- Represents the x-axis: -->
      <line x1="-400" y1="0" x2="400" y2="0" style="stroke: black;" /> 
    
      <!-- Represents the y-axis (although up is negative and down is positive): -->  
      <line x1="0" y1="-400" x2="0" y2="400" style="stroke: black;" /> 
                
    </g>
  </svg>
  <script>
    "use strict";

    /* CONSTANTS */
    var initialTheta = 0; // The initial rotation angle, in degrees.
    var thetaDelta = 0.3; // The amount to rotate the square about every 16.7 milliseconds, in degrees.
    var angularLimit = 90; // The maximum number of degrees to rotate the square.
    var theSquare = document.getElementById("mySquare");

    theSquare.currentTheta = initialTheta; // The initial rotation angle to use when the animation starts, stored in a custom property.

    var requestAnimationFrameID = requestAnimationFrame(doAnim); // Start the loop.
    function doAnim() {
      if (theSquare.currentTheta > angularLimit) {
        cancelAnimationFrame(requestAnimationFrameID); // The square has rotated enough, instruct the browser to stop calling the doAnim() function.
        return; // No point in continuing, bail now.
      }

      theSquare.setAttribute("transform", "rotate(" + theSquare.currentTheta + ")"); // Rotate the square by a small amount.
      theSquare.currentTheta += thetaDelta;  // Increase the angle that the square will be rotated to, by a small amount.
      requestAnimationFrameID = requestAnimationFrame(doAnim); // Call the doAnim() function about 60 times per second (60 FPS), or about once every 16.7 milliseconds until cancelAnimationFrame() is called.
    }
  </script>
</body>
</html>

Important  Au lieu d’insérer <meta http-equiv-"X-UA-Compatible" content="IE9" /> ou <meta http-equiv-"X-UA-Compatible" content="Edge" /> dans le bloc <head>, vous pouvez configurer votre serveur de développement Web pour qu’il envoie l’en-tête HTTP X-UA-Compatible avec IE=Edge afin de vérifier que vous exécutez le mode le plus récent des normes, si vous développez sur un réseau intranet.

L’animation par script est relativement simple si vous comprenez les bases de l’animation traditionnelle des « dessins animés ». L’animation, comme vous le savez probablement, est composée d’une série d’images fixes qui sont modifiées progressivement. Les images sont présentées successivement et rapidement.

Images fixes composant l’animation

Lorsque les six images sont présentées successivement et à une vitesse suffisamment élevée, l’œil perçoit alors une balle qui rebondit.

Fichier GIF animé d’une balle qui rebondit

Dans cet exemple, l’animation de la balle qui rebondit est produite grâce à la répétition des six images. Chaque image s’affiche pendant 100 millisecondes avant de passer à l’image suivante. Le même concept est utilisé pour l’animation par script. Dans le code de l’Exemple 2, nous appelons tout simplement une fonction qui modifie progressivement une image à intervalles réguliers de quelques millisecondes. La fonction requestAnimationFrame est notamment utilisée pour indiquer au navigateur qu’il doit appeler une fonction, doAnim, environ toutes les 16,7 millisecondes (c’est-à-dire, environ 60 images par seconde). La fonction doAnim fait pivoter légèrement le carré chaque fois qu’elle est appelée. Étant donné que la fonction doAnim est appelée à intervalles répétés de quelques millisecondes, le carré semble pivoter en douceur. Après avoir fait pivoter le carré d’un certain nombre de degrés grâce au paramètre angularLimit (90 degrés pour cet exemple), nous demandons au navigateur de cesser d’appeler la fonction doAnim en appelant cancelAnimationFrame et l’animation s’arrête (voir les commentaires sur le code pour plus d’informations).

Avant de passer à des exemples plus complexes, il est important de souligner deux styles de codages JavaScript :

  • les scripts DOM de niveau 2 ;
  • les scripts DOM SVG.

L’écriture de scripts DOM de niveau 2 représente la méthode traditionnelle. Elle consiste à construire des chaînes de valeurs qui permettent de définir différents éléments, par exemple :


theSquare.setAttribute("transform", "rotate(" + theSquare.currentTheta + ")");

Les scripts DOM SVG se caractérisent par l’absence de ces chaînes de valeurs, et définissent généralement les valeurs des éléments numériquement.


mySquare.transform.baseVal.getItem(0).setRotate(mySquare.currentTheta, 0, 0);

Les deux lignes permettent d’effectuer la même chose. La seule différence mineure se trouve dans la méthode setRotate qui oblige à indiquer deux valeurs pour le point central à partir duquel la rotation de l’objet doit s’effectuer (dans cet exemple, le point (0, 0)). L’avantage de la méthode de scripts DOM SVG est qu’elle ne nécessite pas la construction de chaînes de valeurs et qu’elle peut s’avérer plus performante que les scripts DOM de niveau 2.

L’exemple suivant correspond à la version de script DOM SVG de l’Exemple 2 :

Exemple 3 : script DOM SVG

Lien direct : Exemple 3


<!DOCTYPE html>
<html>

<head>  
  <title>JavaScript SVG Animation</title>
  <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> <!-- Remove this line in production. -->
</head>

<body>
  <svg id="svgElement" width="800px" height="800px" viewBox="0 0 800 800"> <!-- Give the svg element a name so that we can easily access it via JavaScript. -->

    <g transform="translate(400, 400)"> <!-- Create a Cartesian coordinate system (with the y-axis flipped) for the animated square. That is, place the origin at the center of the 800 x 800 SVG viewport: -->
  
      <!-- A 200 x 200 square with the upper left-hand corner at (-100, -100). This places the center of the square 
      at the origin (0, 0). Give the square a name so we can easily access it via JavaScript: -->  
      <rect id="mySquare" x="-100" y="-100" width="200" height="200" rx="5" ry="5"
            style=" fill: orange; stroke: black; stroke-width: 3; stroke-dasharray: 10, 5;" />
            
      <!-- Represents the x-axis: -->
      <line x1="-400" y1="0" x2="400" y2="0" style="stroke: black;" /> 
    
      <!-- Represents the y-axis (although up is negative and down is positive): -->  
      <line x1="0" y1="-400" x2="0" y2="400" style="stroke: black;" /> 
                
    </g>
  </svg>
  <script>
    "use strict";

    /* CONSTANTS */
    var initialTheta = 0; // The initial rotation angle, in degrees.
    var thetaDelta = 0.3; // The amount to rotate the square about every 16.7 milliseconds, in degrees.
    var angularLimit = 90; // The maximum number of degrees to rotate the square.

    /* GLOBALS */
    var requestAnimationFrameID;
    var mySquare = document.getElementById("mySquare");
    var transformObject;

    mySquare.currentTheta = initialTheta; // The initial rotation angle to use when the animation starts, stored in a custom property.
    transformObject = svgElement.createSVGTransform(); // Create a generic SVG transform object so as to gain access to its methods and properties, such as setRotate().
    mySquare.transform.baseVal.appendItem(transformObject); // Append the transform object to the square object, now the square object has inherited all the transform object's goodness.

    requestAnimationFrameID = requestAnimationFrame(doAnim); // Start the animation loop.
    function doAnim() {
      var transformObject;

      if (mySquare.currentTheta > angularLimit) {
        cancelAnimationFrame(requestAnimationFrameID); // Instruct the browser to stop calling requestAnimationFrame()'s callback.
        return;
      }

      mySquare.transform.baseVal.getItem(0).setRotate(mySquare.currentTheta, 0, 0); // Access the transform object (that was appended to mySquare in the init() function) and use its setRotate method to rotate the square about the point (0, 0) (which is at the center of the SVG viewport).
      mySquare.currentTheta += thetaDelta; // Place this line here so that the square isn't over rotated on the last call to doAnim().
      requestAnimationFrameID = requestAnimationFrame(doAnim); // Call the doAnim() function about every 60 times per second (i.e., 60 FPS).
    }
  </script>
</body>
</html>

La partie la plus difficile de l’exemple 3 concerne sans doute les deux lignes suivantes :


transformObject = svgElement.createSVGTransform(); 
mySquare.transform.baseVal.appendItem(transformObject);

La première ligne crée un objet transform générique. La seconde ligne joint cet objet transform au nœud mySquare. Ceci permet à l’objet mySquare d’hériter de toutes les méthodes et propriétés associées à l’objet transform, en particulier la méthode setRotate. Ceci étant fait, nous pouvons faire pivoter le carré mySquare en appelant simplement sa méthode setRotate nouvellement acquise de la manière suivante :


mySquare.transform.baseVal.getItem(0).setRotate(mySquare.currentTheta, 0, 0);

Vous pouvez au passage éviter de créer et d’ajouter l’objet transform en ajoutant un attribut transform no-op à l’élément rect, par exemple transform="matrix(1 0 0 1 0 0)" :


<rect id="mySquare" x="-100" y="-100" width="200" height="200" rx="5" ry="5" 
          transform="matrix(1 0 0 1 0 0)"
          style=" fill: orange; stroke: black; stroke-width: 3; stroke-dasharray: 10, 5;" />

Ceci crée un objet transform sur l’élément rectangle que vous pouvez ensuite manipuler, sans avoir besoin d’en créer ou d’en ajouter un « à la volée ». Voici l’exemple complet :

Exemple 4 : objet transform no-op

Lien direct : Exemple 4


<!DOCTYPE html>
<html>

<head>  
  <title>JavaScript SVG Animation</title>
  <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> <!-- Remove this line in production. -->
</head>

<body>
  <svg width="800px" height="800px" viewBox="0 0 800 800">

    <g transform="translate(400, 400)"> <!-- Create a Cartesian coordinate system (with the y-axis flipped) for the animated square. That is, place the origin at the center of the 800 x 800 SVG viewport: -->
  
      <!-- A 200 x 200 square with the upper left-hand corner at (-100, -100). This places the center of the square 
      at the origin (0, 0). Note that the no-op transform attribute is necessary to generate a transform object 
      such that the setRotate() method can be utilized in the doAnim() function: -->  
      <rect id="mySquare" x="-100" y="-100" width="200" height="200" rx="5" ry="5" 
            transform="matrix(1 0 0 1 0 0)"
            style=" fill: orange; stroke: black; stroke-width: 3; stroke-dasharray: 10, 5;" />

      <!-- Represents the x-axis: -->
      <line x1="-400" y1="0" x2="400" y2="0" style="stroke: black;" /> 
    
      <!-- Represents the y-axis (although up is negative and down is positive): -->  
      <line x1="0" y1="-400" x2="0" y2="400" style="stroke: black;" /> 
            
    </g>
  </svg>
  <script>
    "use strict";

    /* CONSTANTS */
    var initialTheta = 0; // The initial rotation angle, in degrees.
    var thetaDelta = 0.3; // The amount to rotate the square about every 16.7 milliseconds, in degrees.
    var angularLimit = 90; // The maximum number of degrees to rotate the square.

    /* GLOBALS */
    var mySquare = document.getElementById("mySquare");
    var requestAnimationFrameID;

    mySquare.currentTheta = initialTheta; // The initial rotation angle to use when the animation starts.
    requestAnimationFrameID = requestAnimationFrame(doAnim); // Start the animation loop.

    function doAnim() {
      if (mySquare.currentTheta > angularLimit) {
        clearInterval(requestAnimationFrameID);
        return;
      }

      mySquare.transform.baseVal.getItem(0).setRotate(mySquare.currentTheta, 0, 0); // Rotate the square about the point (0, 0), which is now at the center of the SVG viewport. Assumes a no-op transform attribute has been applied to the mySquare element, such as transform="matrix(1 0 0 1 0 0)".
      mySquare.currentTheta += thetaDelta; // Increase the angle that the square will be rotated to, by a small amount.
      requestAnimationFrameID = requestAnimationFrame(doAnim); // Call the doAnim() function about 60 time per second, or about once every 16.7 milliseconds until cancelRequestAnimation() is called.
    }
  </script>
</body>
</html>

Dans l’Exemple 4, nous utilisons une matrice d’identités en tant qu’attribut transform « no-op » pour générer un objet transform. Nous aurions très bien pu utiliser le code transform=”rotate(0)” à la place.

L’un des avantages de l’Exemple 3 par rapport à l’Exemple 4 est qu’il n’est pas nécessaire de se rappeler d’ajouter un attribut « no-op » à l’élément rectangle.

Maintenant que nous connaissons ces deux styles de scripts (DOM de niveau 2 et DOM SVG), nous pouvons passer à des animations plus évoluées ; l’exemple suivant tente de modéliser la rotation de roues en contact. Imaginez la gomme de deux roues d’une bicyclette qui tournent l’une contre l’autre, en sens contraire. Le coefficient élevé du contact de la gomme permet à la première roue d’entraîner la seconde. Voici un schéma théorique d’un système de roues en contact :

Deux roues en contact

Le code permettant de générer cette image est présenté dans l’exemple suivant :

Exemple 5 : deux roues

Lien direct : Exemple 5


<!DOCTYPE html>
<html>

<head>  
  <title>Two Animated Gears</title>
  <meta http-equiv="X-UA-Compatible" content="IE=Edge"/> <!--  Remove this line in production. -->
</head>

<body>
  <div align="center"> <!-- An inexpensive way to center everything. -->
    <div style=" margin-bottom: 8px;">
      <button id="startButton" type="button" onclick="startAnim();">
        Start Animation
      </button> 
    </div> 
    <svg id="svgElement" width="800px" height="800px" viewBox="0 0 800 800"> <!-- Give the svg element a name so that we can easily access it via JavaScript. -->
      <rect x="0" y="0" width="100%" height="100%" rx="16" ry="16" 
            style="fill: none; stroke: black; stroke-dasharray: 10, 5;" />
  
      <defs> <!-- Do not render the gear template, just define it. -->
        <g id="gearTemplate"> <!-- Give this group of graphic elements a name so that it can be "called" from the <use> element. -->
          <circle cx="0" cy="0" r="150" style="stroke: black;" />
          <line x1="0" y1="-150" x2="0" y2="150" style="stroke: white;"/> <!-- From top to bottom, draw the vertical wheel "spoke". -->        
          <line x1="-150" y1="0" x2="0" y2="0" style="stroke: white;"/> <!-- Draw left half of the horizontal "spoke". -->
          <line x1="0" y1="0" x2="150" y2="0" style="stroke: darkGreen;"/> <!-- Draw right half of the horizontal "spoke". -->
        </g>
      </defs>

      <g transform="translate(400, 400)"> <!-- Create a Cartesian coordinate system (with the y-axis flipped) for the animated gears. That is, place the origin at the center of the 800 x 800 SVG viewport: -->
        <use id="gear0" x="-150" y="0" xlink:href="#gearTemplate" style="fill: orange;" /> <!-- Use the previously defined gear template and position it appropriately. -->
        <use id="gear1" x="150" y="0" xlink:href="#gearTemplate" style="fill: mediumPurple;" /> <!-- Same as the previous line but give this circle a different color. -->                
      </g>
    </svg>
  </div>
  <script>
    "use strict";

    /* CONSTANTS */
    var initialTheta = 0; // The initial rotation angle, in degrees.
    var currentTheta = initialTheta; // The initial rotation angle to use when the animation starts.
    var thetaDelta = 0.5; // The amount to rotate the gears every ~16.7 milliseconds or so, in degrees.
    var angularLimit = 360; // The maximum number of degrees to rotate the gears.

    /* GLOBALS */
    var requestAnimationFrameID;
    var transformObject = svgElement.createSVGTransform(); // Create a generic SVG transform object so as to gain access to its methods and properties, such as setRotate().
    var gear0 = document.getElementById('gear0');
    var gear1 = document.getElementById('gear1');

    gear0.transform.baseVal.appendItem(transformObject); // Append the transform object to gear0, now the gear0 object has inherited all the transform object's goodness.
    gear1.transform.baseVal.appendItem(transformObject); // Append the same generic transform object to gear1 - we just want gear1 to inherit all of it's goodness.

    function startAnim() {
      if (!startButton.startButtonClicked) // Don't allow multiple instance of the function specified by requestAnimationFrame to be invoked by the browser. Note that button.startButtonClicked will be undefined on first use, which is effectively the same as false.
      {
        /* Only do the following once per full animation: */
        startButton.startButtonClicked = true; // A custom property is attached to the button object to track whether the button has been clicked or not.
        requestAnimationFrameID = requestAnimationFrame(doAnim); // Start the animation loop.
      }
    }

    function doAnim() {
      if (currentTheta > angularLimit) {
        startButton.startButtonClicked = false; // Let the user run the animation again if they choose.
        currentTheta = initialTheta; // If we let the user run the animation multiple times, be sure to set currentTheta back to an appropriate value.
        cancelAnimationFrame(requestAnimationFrameID); // Instruct the browser to stop calling requestAnimationFrame()'s callback.
        return; // We have completed our animation, time to quit.
      }

      gear0.transform.baseVal.getItem(0).setRotate(currentTheta, -150, 0); // Rotate the 0th gear about the point (-150, 0).
      gear1.transform.baseVal.getItem(0).setRotate(-currentTheta, 150, 0); // Rotate the 1st gear, note the minus sign on currentTheta, this rotates the gear in the opposite direction.
      // gear0.setAttribute("transform", "rotate(" + currentTheta + ", -150, 0)"); // More cross-browser friendly, slightly less performant. Note that you don't technically need to append a transform object to each gear object, in init(), when using this line.
      // gear1.setAttribute("transform", "rotate(" + -currentTheta + ", 150, 0)"); // More cross-browser friendly, slightly less performant. Note that you don't technically need to append a transform object to each gear object, in init(), when using this line.
      currentTheta += thetaDelta; // Place this line here so that the gears are not over rotated on the last call to doAnim().
      requestAnimationFrameID = requestAnimationFrame(doAnim); // Call the doAnim() function about every 16.7 milliseconds (i.e., about 60 frames per second).
    }
  </script>
</body>
</html>

Dans l’Exemple 5, nous avons animé deux objets graphiques et ajouté un bouton Start Animation (Démarrer l’animation), ce qui a entraîné une refactorisation du code en ce qui concerne le carré pivotant de l’Exemple 4. Plus particulièrement :

  • Au lieu de créer du code SVG pour chaque roue, nous avons créé un modèle de roue que nous pouvons réutiliser autant de fois que nous le souhaitons à l’aide de l’élément use.
  • Pour simplifier les choses, nous avons créé une variable globale currentTheta dont la valeur peut être appliquée aux deux roues.
  • Une nouvelle fonction startAnim a été ajoutée. Un clic sur le bouton Start Animation appelle cette fonction qui démarre l’animation et le parcours en boucle de requestAnimationFrame.
  • Si l’utilisateur clique plusieurs fois sur le bouton Start Animation, ceci entraîne (sans protection) l’appel de plusieurs instances de la fonction doAnim et une exécution bien plus rapide que prévue de l’animation. Afin d’empêcher ce comportement indésirable, nous devons ajouter la propriété personnalisée startButtonClicked à l’objet du bouton et lui attribuer la valeur true lorsque le bouton est cliqué la première fois.
  • Afin de permettre à l’utilisateur de réexécuter l’animation (une fois celle-ci terminée), les deux lignes suivantes ont été ajoutées à la fonction doAnim :

    
    startButton.startButtonClicked = false; 
    currentTheta = initialTheta; 
    
    

    Ces lignes sont exécutées uniquement lorsque l’animation est terminée (c’est-à-dire quand la valeur currentTheta est supérieure à la valeur angularLimit).

L’un des problèmes de l’Exemple 5 est que vous devez appeler la méthode setRotate pour chaque roue, mais aussi mémoriser le centre de chacune. Ceci peut être fastidieux si vous avez un nombre élevé de roues. Une solution à ce problème consiste à utiliser un tableau de roues, comme dans l’Exemple 6 (étant donné sa longueur, le code de l’exemple n’est pas présenté dans ce document. Utilisez la fonctionnalité Afficher la source d’Internet Explorer pour afficher le code source de l’exemple en action). Voici une capture d’écran de l’exemple :

Exemple 6 : dix-sept roues

Lien direct : Exemple 6

Dix-sept roues en contact

Dans le code de l’Exemple 6, 17 roues sont animées. Au lieu de taper le code des 17 éléments use, chacun doté d’une couleur et de valeurs (x, y) uniques (comme dans l’Exemple 5 avec les deux roues), nous construisons par le biais du code un tableau de roues qui contient ces informations ainsi que d’autres données utiles :

  • le rayon de chaque roue (de sorte que nous puissions, entre autres aspects, calculer une vitesse de rotation appropriée pour la roue) ;
  • la position angulaire (en degrés) de chaque roue (c’est-à-dire, currentAngle) ;
  • le nom de la roue, etc.

De plus, nous utilisons le groupe de roues pour ajouter, par programmation, les éléments adéquats au DOM afin que les roues s’affichent à l’écran. Le code JavaScript ajoute ainsi les éléments « roues » à l’élément g « coordinateFrame » suivant :


<g id="coordinateFrame" transform="translate(400, 400)"> 
  <!-- Gear <g> elements will be appended here via JavaScript. -->
</g>

La roue la plus grande (n°0 ci-dessus) est la roue motrice. Elle entraîne toutes les autres. Étant donné qu’il n’y a qu’une seule roue motrice, nous définissons sa vitesse de rotation (constants.driveGearSpeed = 0.3) et calculons en fonction de cette dernière les vitesses requises des autres roues de la manière suivante :


gears[i].currentAngle += (gears[i].clockwise * constants.driveGearSpeed * (gears[constants.driveGearIndex].r/gears[i].r));

Tout d’abord, chaque roue effectue le suivi de sa propre position angulaire via sa propriété gears[i].currentAngle. Le sens de rotation de chaque roue est déterminé par la propriété gears[i].clockwise (1 ou – 1). La position angulaire d’une roue donnée correspond tout simplement à la vitesse angulaire de la roue motrice multipliée par le radius de cette dernière, divisée par le radius de la roue. Attention : pour la roue motrice, la propriété gears[constants.driveGearIndex].r/gears[i].r doit avoir comme prévu la valeur 1.

Un autre point important concerne l’utilisation de la propriété de bouton personnalisé startButtonClicked qui permet à l’objet du bouton d’effectuer le suivi de son propre état (cliqué ou non).

Si vous avez d’autres questions relatives à l’Exemple 6, passez les commentaires en revue dans l’exemple : toutes les lignes importantes sont entièrement décrites.

L’exemple suivant étend l’Exemple 6 en remplaçant le bouton Start Animation par des éléments d’interface utilisateur complémentaires.

Exemple 7 : dix-sept roues avec interface utilisateur étendue

Lien direct : Exemple 7

Cet exemple prolonge l’Exemple 6 en ajoutant les boutons d’interface suivants :

Image des boutons d’interface utilisateur ajoutés

Les boutons Start, Pause et Reset servent respectivement à démarrer, mettre en pause et réinitialiser l’animation, tandis que les boutons « + » et « – » permettent d’accélérer ou de ralentir l’animation. Ces nouveaux éléments d’interface utilisateur imposent un certain nombre d’adaptations du code, dont les suivantes :

  • Une limite angulaire n’est plus nécessaire. Nous laissons l’animation s’exécuter jusqu’à ce que l’utilisateur clique sur le bouton Pause ou Reset. Pour éviter tout dépassement de capacité de variable éventuel, le code suivant a été utilisé :
    
    if (gears[i].currentAngle >= 360)
      gears[i].currentAngle -= 360;
    
    

    Ceci permet de toujours avoir une valeur currentAngle réduite sur chaque roue sans impact sur la signification de la valeur currentAngle. Si l’on fait pivoter un cercle de 362 degrés ou de 2 degrés, on obtient le même résultat (car 362 – 360 = 2 degrés).

Exemple 8 : dix-sept roues avec effet sonore

Lien direct : Exemple 8

Grâce à l’élément HTML5 audio, cet exemple reprend celui précédent en y ajoutant un effet sonore qui peut être accéléré ou ralentit de façon linéaire en cliquant sur les boutons « + » et « – ». De plus, l’effet sonore pouvant entraîner une certaine gêne, nous avons également ajouté un bouton Sound Off (Muet) qui peut s’avérer utile :

Image des boutons d’interface utilisateur ajoutés

Le code audio de l’Exemple 8 est relativement simple et inclut des commentaires. La seule fonction qui peut nécessiter des explications supplémentaires est la fonction calculatePlaybackRate qui retourne une vitesse de lecture du fichier audio adaptée à la vitesse de rotation de la roue motrice. Pour comprendre comment cela est possible, prenons le graphique suivant :

Graphique représentant la vitesse de lecture en fonction de la vitesse de la roue motrice

L’axe x représente la vitesse de rotation actuelle de la roue motrice (qui peut être positive ou négative). L’axe y représente une vitesse de lecture du fichier audio appropriée (qui peut uniquement être positive). Nous savons que lorsque la vitesse de la roue motrice est nulle, la vitesse de lecture du fichier audio l’est également (aucun son n’est émis). Ensuite, d’après nos constantes d’initialisation, nous savons que lorsque la vitesse de la roue motrice a la valeur constants.initialDriveGearSpeed, la vitesse de lecture du fichier audio est alors égale à constants.initialPlaybackRate. Nous avons maintenant deux points : (00) et (constants.initialDriveGearSpeed, constants.initialPlaybackRate). Sachant que deux points définissent une ligne (dans la mesure où nous voulons une réponse linéaire), nous pouvons alors dériver facilement l’équation utilisée dans la fonction calculatePlaybackRate en calculant la pente de la ligne (m) et en la multipliant par la valeur absolue de la vitesse de la roue motrice pour obtenir la bonne vitesse de lecture du fichier audio (voir les commentaires dans calculatePlaybackRate pour plus d’informations).

Une étape suivante logique serait d’ajouter des roues à l’Exemple 8. Comme le code de l’exemple utilise un tableau d’objets représentant les roues, il suffit d’ajouter des roues d’une taille appropriée et de les positionner de manière adéquate pour augmenter leur nombre dans l’animation. Nous vous proposons de réaliser cet exercice qui vous aidera certainement à comprendre mieux les techniques présentées dans cette rubrique.

Rubriques connexes

Animation SVG de niveau intermédiaire
Animation SVG avancée
Graphiques HTML5
Scalable Vector Graphics (SVG)

 

 

Afficher:
© 2014 Microsoft