Valentin Dupas

💡 If this is the first course you read from me, please read this small thing : about my courses

Timing

window

window est une variable gérée par le navigateur, tout comme document. window est un objet javascript qui représente la fenêtre et donc contient essentiellement des fonctions qui ne touchent pas à votre HTML.

Et puis comme d'hab si vous voulez voir ce qu'il y a dedans vous pouvez toujours le logger.

console.log(window);

Une petite note un peu particulière, propre à window, il est possible d'utiliser ses propriétés et fonctions directement

comme ceci:

setTimeout();

plutôt que comme ceci

window.setTimeout();

et vous croiserez essentiellement la version courte si vous lisez des tutos ou du code en dehors de ce cours. Mais on va considérer la forme courte comme interdite pour la durée de ce cours. Je vais me faire violence pour remettre tous les window et vous allez faire un effort pour ne pas les oublier. Parce que je veux que vous sachiez d'où ces fonctions sortent. La version courte est juste une fonction qui marche sans que vous l'ayez definie, tandis qu'avec la version longue vous saurez sans aucun doute à la lecture qu'il s'agit de la fonction setTimeout de window.

window.setTimeout()

Element.addEventListener() permet de specifier une fonction qui s'exécutera quand l'événement surviendra. window.setTimeout() prend une fonction en paramètre ainsi qu'un nombre de millisecondes que l'on va attendre avant d'exécuter la fonction qu'on lui a donnée.

// 1000ms = 1s	donc 10 * 1000 = 10s
// j'aurais pu écrire 10000 directement
// mais c'est plus lisible comme ça
window.setTimeout(hello, 10 * 1000);

function hello() {
  console.log("hello :3");
}

Exécutez ce code. Vous verrez le message dans la console 10 secondes après avoir (re)chargé la page.

Exemple d'intéraction avec du délai

Mmmhhhh, à la base j'était parti pour vous montrer directement comment faire ça pour ensuite m'en servir comme support d'explication, mais finalement c'est un bon exercice.

Exercice

Faites une page sur laquelle il y a un bouton. Quand on clique sur ce bouton on voit un message, de votre choix, dans la console au bout de 3 secondes après le clic.

Solution

html

<button>Click me</button>

js

document.querySelector("button").addEventListener("click", delayedHello);

function delayedHello() {
  window.setTimeout(hello, 3 * 1000);
}

function hello() {
  console.log("hello :3");
}

Fonction en tant que valeur

Ça commence à m'ennuyer de créer des fonctions pour ne les utiliser qu'une seule fois.

On a vu précédemment la différence entre une variable et une valeur.

js

const a = 5; // je créer une variable nommée `a` qui contient la valeur 5

console.log(a); // on donne un nom qui correspond à une valeur
console.log(5); // on donne une valeur

Pour utiliser une valeur, il suffit de directement la mettre là où on en a besoin.

Eh bien on peut faire pareil pour les fonctions.

js

// je créer une fonction nommée `hello` qui contient un console.log()
function hello() {
  console.log("hello :3");
}

// on donne un nom qui correspond à une fonction
window.setTimeout(hello, 1000);

// on donne un bloc de code en tant que valeur
// on ne pourra pas l'appeller ailleurs dans le reste du code
window.setTimeout(() => {
  console.log("hello :3");
}, 1000);

Ci-dessous une autre paire d'exemples avec Element.addEventListener().

js

// on donne un nom qui correspond à une fonction
document.querySelector("button").addEventListener("click", hello);

// je créer une fonction nommée `hello` qui contient un console.log()
function hello() {
  console.log("hello :3");
}

js

// on insère une fonction anonyme pour le paramètre qui demande une fonction
document.querySelector("button").addEventListener("click", () => {
  console.log("hello :3");
});

"Eh attend une minute, ...

... dans le chapitre 3 on nous avait dit que c'était bien de créer plein de variables, mais créer plein de fonctions c'est pas bien?"

Une valeur est vraiment trop peu de contexte (très souvent, pas toujours), donc lui coller un nom en la mettant dans une variable c'est cool. Une fonction anonyme () => {} est toujours utilisée dans un contexte, le plus souvent dans le contexte d'une autre fonction, comme on vient de le voir avec window.setTimeout() et Element.addEventListener(). Donc ça communique déjà pas mal puisque c'est pas juste une fonction anonyme, c'est "la fonction qu'on exécutera quand on cliquera sur ci ou qu'on aura fini d'attendre ça". Aussi la foncion anonyme en question est une ou plusieurs lignes de code ce qui fournis beaucoup plus d'information sur ce qu'il se passe si on compare avec juste une valeur tel que 5, true, ou même {firstname: "Margaret", name: "Hamilton"}.

window.setInterval()

C'est à peu de choses près la même que window.setTimeout() à ceci près que le chrono se relance quand la fonction donnée est exécutée.

Donc:

  • window.setTimeout() : on exécute la fonction donnée une fois après X millisecondes
  • window.setInterval() : on exécute la fonction donnée tous les X millisecondes
let count = 0;

window.setInterval(() => {
  count = count + 1;
  console.log(count);
}, 1000);

Essayez ce code et commentez le. (expliquer avec des //)

Problème : Button mashing avec décompte des secondes

Faites une page qui présente un bouton "GO". Quand on clique sur ce bouton il disparait et affiche un bouton "+" ainsi qu'un décompte de 10 secondes. Chaque seconde, le décompte est mis à jour. Une fois le décompte fini, cachez le bouton et présentez combien de fois le bouton "+" à été cliqué ainsi qu'un bouton "rejouer".

exemple


Bonus: changez la couleur du fond de la page pour refléter à quel point l'utilisateur clique vite pendant la phase de "jeu".

Astuce pour le bonus 🎶: La couleur dans Element.style.backgroundColor est à fournir sous forme de string, mais cette string peut être un nom de couleur "red", "blue", ou une valeur RGB, rgb(255, 255, 255) étant la valeur max, ou une valeur HSL, hsl(360, 100%, 100%) étant la valeur max.

Solution
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      body {
        margin: 0;
        min-height: 100vh;
      }

      main {
        width: 100%;
        min-height: 100vh;

        display: grid;
        place-items: center;
      }
    </style>
  </head>
  <body>
    <main>
      <section id="go-container">
        <button>GO</button>
      </section>
      <section id="game-container">
        <p id="countdown"></p>
        <button>+</button>
      </section>
      <section id="score-container">
        <p id="score"></p>
        <button>Rejouer</button>
      </section>
    </main>

    <script>
      // je récupere les objets javascript correspondant à toutes mes sections
      // pour pouvoir les montrer/cacher plus tard
      const goContainer = document.querySelector("#go-container");
      const gameContainer = document.querySelector("#game-container");
      const scoreContainer = document.querySelector("#score-container");

      // les éléments dans lesquels je vais injecter des infos pour l'utilisateur
      const countdownEl = document.querySelector("#countdown");
      const scoreEl = document.querySelector("#score");

      // on se cache le jeu et le score
      gameContainer.style.display = "none";
      scoreContainer.style.display = "none";

      // on défini des constantes globales qui nous serviront d'unitées
      // on verra par la suite que ça rend les choses plus sypmas
      // et si je voulais changer la longeur de la partie 
      // j'aurais juste à changer la valeur de LENGTH_OF_GAME
      const ONE_SECOND = 1000;
      const LENGTH_OF_GAME = 10 * ONE_SECOND;

      // les variables dans lesquelles je vais garder l'état du jeu
      let countdown = 0;
      let clicks = 0;

      // quand je clique sur le bouton du go-container ...
      document.querySelector("#go-container button").addEventListener("click", () => {
        goContainer.style.display = "none"; // je cache le go-container

        // et je redonne a game-container sa valeur de disploy d'origine (une string vide)
        // donc on le voit
        gameContainer.style.display = ""; 

        // et le décompte vaux la longueur d'une partie
        countdown = LENGTH_OF_GAME / 1000;
      });

      // quand je clique sur le bouton du game-container ...
      document.querySelector("#game-container button").addEventListener("click", () => {
        clicks = clicks + 1; // on augmente la quantité de clics enregistrés
      });

      // quand je clique sur le bouton du score-container ...
      document.querySelector("#score-container button").addEventListener("click", () => {
        scoreContainer.style.display = "none"; // je cache le score-container
        goContainer.style.display = ""; // je montre le go-container

        // on remet à 0 les variables qui gèrent l'état du jeu
        countdown = 0;
        clicks = 0;
      });

      // pour les setInterval() ça peut se comprendre
      // si vous restez sur une fonction nommée 
      // plutôt que d'utiliser une fonction anonyme () => {}
      // parce que la fonction donnée est le premier paramètre
      // donc la longueur de l'interval de temps est tout en bas
      window.setInterval(() => {
        // si le décompte n'est pas fini ...
        if (countdown > 0) {
          countdown = countdown - 1; // on décompte 
          countdownEl.textContent = countdown; // et on affiche
        }

        // si le décompte est fini 
        if (countdown <= 0) {
          if(clicks != 0){ // et qu'on a joué un peu
            gameContainer.style.display = "none"; // on cache le jeu
            scoreContainer.style.display = ""; // on montre le score

            // on affiche le score
            scoreEl.textContent = "Bien joue vous avez clique: " + clicks + " fois.";
          }
        }
      }, ONE_SECOND); // <- longueur de l'interval de temps ici
    </script>
  </body>
</html>
Solution avec bonus
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      body {
        margin: 0;
        min-height: 100vh;
      }

      main {
        width: 100%;
        min-height: 100vh;

        display: grid;
        place-items: center;
      }
    </style>
  </head>
  <body>
    <main>
      <section id="go-container">
        <button>GO</button>
      </section>
      <section id="game-container">
        <p id="countdown"></p>
        <button>+</button>
      </section>
      <section id="score-container">
        <p id="score"></p>
        <button>Rejouer</button>
      </section>
    </main>

    <script>
      const goContainer = document.querySelector("#go-container");
      const gameContainer = document.querySelector("#game-container");
      const scoreContainer = document.querySelector("#score-container");

      const countdownEl = document.querySelector("#countdown");
      const scoreEl = document.querySelector("#score");

      gameContainer.style.display = "none";
      scoreContainer.style.display = "none";

      const ONE_SECOND = 1000;
      const LENGTH_OF_GAME = 10 * ONE_SECOND;

      // comme LENGTH_OF_GAME, j'en fais une constante globale
      // comme ça je peux rapidement changer le comportement du jeu
      // sans trifouiller dans le reste du programme
      const MAX_INTENSITY = 30; 

      let countdown = 0;
      let clicks = 0;
      let intensity = 0; // on se rajoute une variable qui traque l'intensité

      document.querySelector("#go-container button").addEventListener("click", () => {
          goContainer.style.display = "none"; 
          gameContainer.style.display = "";

          countdown = LENGTH_OF_GAME / 1000;
        });

      document.querySelector("#game-container button").addEventListener("click", () => {
          clicks = clicks + 1; 

          // on augmente l'intensité, qui n'est pas la même chose que les clics
          intensity = intensity + 1;
          // on vient de toucher à l'intensité donc on met à jour le background
          // j'en ai fait une fonction parce que plus bas on touchera aussi à l'intensité
          updateBackgroundColor();
        });

      document.querySelector("#score-container button").addEventListener("click", () => {
          scoreContainer.style.display = "none"; 
          goContainer.style.display = ""; 

          countdown = 0;
          clicks = 0;
        });

      window.setInterval(() => {
        if (countdown > 0) {
          countdown = countdown - 1; 
          countdownEl.textContent = countdown; 
        }

        if (countdown <= 0) {
          if(clicks != 0){
            gameContainer.style.display = "none"; 
            scoreContainer.style.display = ""; 

            scoreEl.textContent = "Bien joue vous avez clique: " + clicks + " fois.";
          }
        }
      }, ONE_SECOND);

      // l'intensité descend continuellement
      // pour donner une résistance tactile à la progression du joueur
      // et aussi pour que ça retombe si il s'arrête en pleine partie
      window.setInterval(() => {
        if (intensity > 0) {
          intensity = intensity - 1;
        }

        // on vient de toucher à l'intensité donc on met à jour le background
        updateBackgroundColor();
      }, ONE_SECOND / 4);

      function updateBackgroundColor() {
        const saturation = (intensity / MAX_INTENSITY) * 100;

        document.querySelector("body").style.backgroundColor =
          "hsl(0, " + saturation + "% , 33%)";
      }
    </script>
  </body>
</html>

&& et ||

Ça n'est pas raccord avec le thème du chapitre, mais c'est le bon moment de voir ça.

Vous avez remarqué que dans la solution précédente j'ai enchainé deux if() d'affilé pour vérifier que le décompte était fini et que le joueur avait intéragit avec le jeu. En vrai c'est dégueux comme façon de l'écrire, mais c'est tout ce que l'on avait.

&& est un symbole au même titre que <= ou + et veux dire "et". Donc j'aurais pu écrire:

// est-ce que `countdown` est inférieur ou égal à 0 
// ET est-ce que `clicks` est différent de 0
if (countdown <= 0 && clicks != 0) {
  // [...]
}

// plutôt que 
if (countdown <= 0) { // `countdown` est inférieur ou égal à 0?
  if(clicks != 0){  // `clicks` est différent de 0?
    // [...]
  }
}

|| est similaire mais veux dire "ou". Donc si j'écrivais :

if (countdown <= 0 || clicks != 0) {
  // [...]
}

... on rentrerait dans le if() si countdown vaux 0 ou moins, OU si clicks est différent de 0.