Programmation Javascript (NFA041)

Bases de programmation illustrées en javascript,

Manipulation du Dom

Olivier Pons
(olivier.pons@lecnam.net)

2022

Le Dom

Le Modèle object de Document est :

Remarques :

  1. L'Extension showDomTree de chrome, permet (pour de petits exemples) de visualiser l'arbre de balise graphiquement.

  2. Dans cette partie, nous utiliserons intensivement les outils de développement du navigateur (généralement accessibles par F12).

Exemple d'arbre DOM

L'arbre est composé de noeuds. Il y a différents types de noeud.

Ce qui nous intéresseront le plus étant les noeuds élément (les rectangles sur l'image) et les noeuds texte (ellipse sur l'image).

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>exemple DOM</title>
    <style>
        #lediv {
            height:10em;width:50%;border:solid;
            background-color: orange;
        }
    </style>
</head>
<body>
    <h1>Exemple pour la manipulation du DOM</h1>
    <p>Un <em>petit</em> paragraphe</p>
    <p>Un autre paragraphe</p>
    <div id="lediv">un div</div>
    
</body>
</html>

l'arbre correspondant

Les méthode d'accès:

L'objet document (défini dans l’environnement hôte , ie. le navigateur) donne accès à l'arbre du document courant (c'est-à-dire à votre page).

Il dispose d'un certain nombre de méthodes, notamment

Les méthodes historiques

document.getElementById(id)

Sur l'exemple précédent , on peut ainsi récupérer le noeud d'id lediv

let mondiv=document.getElementById("lediv");

exemple avec getElementById

Ce noeud est un object construit avec le constructeur HTMLDivElement

Remarque:

getElementByIdrenvoie un noeud unique ou null si l'id n'existe pas !

document.getElementsByTagName(nomDeBalise)

Sur l'exemple précédent , on peut ainsi récupérer les noeud correspondants à des paragraphes (balise p)

let les_p=document.getElementsByTagName("p");

exemple avec getElementsByTagName

Le résultats est un ensemble de noeuds (techniquement une HTMLCollection).
il peut être convertie en tableau de noeuds par :[...les_p]

document.getElementsByName(name) et [document.getElementsByClassName(nom_de_class)]https://developer.mozilla.org/fr/docs/Web/API/Document/getElementsByClassName) fonctionnent sur le meme modèle pour récupérer des ensembles de noeuds à partir d'un name (pour les formulaires) ou d'un nom de classe.

### Remarque :

Notez dans getElementById il n'y a pas de s à Elementcar il renvoie un noeud unique. tout les autres ont un s car ils renvoient un ensemble de noeud.

en utilisant les sélecteurs css

On peut aussi récupérer les noeuds à partir de sélecteur css.

Pour récupérer le premier noeud correspondant au sélecteur donc ici le premier paragraphe qui suit un paragraphe (rappel + est le combinateur de voisin direct)

let secondp=document.querySelector("p+p")

Pour récupérer tous les noeuds correspondant au sélecteur donc ici les paragraphes qui suivent un paragraphe.

let les_p_qui_suivent_un_p=document.querySelectorAll("p+p");

exemple querySelector et querySelectorAll

Le résultat du premier est bien un noeud.
Le résultat du second est un ensemble de noeuds (qui là encore n'est pas un tableau mais peut être converti). Ici l'ensemble de type NodeList ne contient qu'un noeud.

Remarque

HTMLCollection et NodeList sont tres similaire et ne sont pas des tableaux.
voir ici pour une comparaison.

Les Événements

On a présenté javascript comme un langage événementiel et on a déjà présenté brièvement avec les timers le modèle d'execution et la notion de callback (ou fonction de rappel).

Les événements sont des actions ou des occurrences qui se produisent dans le système.

Il y a des « événements généraux » comme un temps écroulé dans les timers et des événement associé à des object (généralement window, document ou un noeud Élément).

Javascript permet de définir du code attaché au déclenchement d'un un événement sur un object.

Attaché à un élément peut se faire en html

<p id="unid" onclick="alert('on  a cliqué')" >cliquez moi </p>

c'est simple pour débuter,

mais il est mieux pour la separation des rôles (description en html, présentation en css, comportement en js) de le faire dans le code javascript en attachant une gestion d'événement sur un object l'aide de addEventListener et d'une fonction de callback.

let p=document.getElementByid("unid");
p.addEventListener("click",function (){alert("on a cliqué")});

On peut aussi ajouter des événements sur l'object window, par exemple un événement load. qui est déclenché lorsque la page et toutes ses ressources dépendantes (telles que des feuilles de style et des images) sont complètement chargées.

window.addEventListener("load",
  function (){alert("fini de charge la page...")});

L'évènement load

Cet événement est souvent utilisé; En effet pour pouvoir récupérer et manipuler un noeud (ou une ressource (par exemple une image)), il faut que ce noeud (ou cette ressource) existe, donc qu'il aie été créer et donc que le chargement et le traitement (analyse du source et creation du dom) soit terminé.

C'est ce que permet d'attendre l'événement load.

par exemple:


window.addEventListener("load",
  function (){alert("fini de charge la page...")});

Quelques événements intéressants

Lors du déclenchement de l'événement, les caractéristiques de l'évènement sont transmises à la fonction de rappel. On peut par exemple récupérer la position du click:

Dans l'exemple ci-dessous on affiche les coordonnées du click grace au propriétes clientX et clientY.

<div id="exDomClickEvent" style="width=50%; height: 5em; border:solid 2px;"></div>
<script>
  document.getElementById("exDomClickEvent").addEventListener("click",testClick)
function testClick(ev) {
          alert("souris en " + ev.clientX + " " + ev.clientY);
}
</script>
cliquez dans ce carré

La encore des caractéristiques de l’événement sont disponible notamment la touche pressée.

La propriété key de l’objet évènement permet d’obtenir un caractère, tandis que la propriété code de l’objet évènement objet permet d’obtenir le code de la touche physique (il y a un code de touche car il peut y avoir plusieurs touches pour le même caractère , par exemple pour les chiffres).

Il faut parfois combiné les 2, par exemple, la même touche A peut être appuyée avec ou sans Shift. Cela nous donne deux caractères différents : minuscule a et majuscule A.

<p>Tapez  sur des touches ...
<input id=exDomKeyEvent type="text">
</p>
<script>
document.getElementById("exDomKeyEvent").addEventListener("keydown",montreTouche)
function montreTouche(e){
   alert("code de touche "+e.code);
   alert("caractère "+e.key);  
}
</script>

Tapez sur des touches ...

A noter que si la fonction de rappel fait appel a preventDefault (ou si elle est lié dans le html par un onkeydown et renvoie false), le caractère taper n'est pas inséré.

Dans l'exemple ci dessous on interdit de taper des chiffres

<p id="exKeyFiltre">Tapez du texte (mais pas de chiffre)
<input type="text" >
</p>
<script>
let i=document.querySelector("#exKeyFiltre input");
i.addEventListener("keydown",filtreNombre);

function filtreNombre(e)
{
console.log(e.key);
if( "0123456789".indexOf(e.key) != -1)
  {e.preventDefault()};
}
<script>

Tapez du texte (mais pas de chiffre)

Manipulation du style

Une fois un (ou des noeuds) « récupérés » on peut récupérer, positionner ou modifier leur style (inline).

On pourra aussi (c'est moins simple) récupérer, ajouter , supprimer des feuille de style, et y ajouter ou supprimer des règles

les styles inline

let monnoeud=document.getElementById("unId");
monnoeud.style.color; //lecture de la couleur
autrenoeud=document.querySelector("monId")
autrenoeud.display="block"
autrenoeud.width="100px"
autrenoeud.color="red"

Le nom des propriétés est généralement le meme qu'en CSS,
sauf s'il contient des tirets -, auquel cas on supprime le tiret et capitalise la premiere lettre du mot suivant.

Par exemple: background-color devient backgroundColor

Exemple: faire alterner des couleurs à chaque click


<div id="ex1" style="border:solid">
  <p id="dutext"> Du texte </p>
  <button id="changer">Changer la couleur</button>
</div>

<script>
   let i=0;
   //tableau des couleurs
   let colors=["green","yellow","red"];
   let t=document.querySelector("#ex1 #dutext");
   let b=document.querySelector("#ex1 #changer");
   // a chaque appel i augment et l'indice de la couleur est i%3
   b.addEventListener("click",()=>{t.style.backgroundColor=colors[++i%3]})
</script>

Du texte

Manipuler les feuille de style

Modifier l'attribut style d'un élément nous limite à l'élément.

On peut aussi accéder aux styles définis dans les feuilles de style.

le style de feuilles de style (interne ou externe)

On peut acceder à la liste des feuille de style par la propriété stylesSheets du document qui renvoie une collection de feuille de style.

On accède aux feuille de style par leur indice dans cette collection.

Puis dans chaque feuille de style à la liste des règles par la propriété cssRules qui renvoie une liste de règle.

On accède à chaque règle par son indice.

Puis pour chaque règle on a les propriétés: - cssText qui correspond au texte de la règle dans la feuille de style - selectorText qui correspond au sélecteur utilisé dans la feuille de style - style qui donne une liste des propriétés positionnées, suivie des couples (clef/valeur)

On peut ajouter des réglés( méthode addRule ou insertRule) ou supprimer une règle à partir de son indice grace à deleteRule

exemple

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="exempleDomStyleSheet.css">
    <style>
        #deux {background-color:yellow}
    </style>
</head>
<body>
    <p id="un">Un </p>
    <p id="deux">Deux </p>
    <p style="background-color:red">Trois</p>
    <button onclick="voir()">voir feuille</button>
</body>
<script>
function voir(){
    //récupérer toute les feuilles
    // de style (externe et interne)
    lesfeuilles=document.styleSheets;
    alert(lesfeuilles)
    //recuperer la feuille interne 
    // indice 1 car c'est la seconde
    let interne= document.styleSheets[1];
    alert(interne);
    //les regle de cette page
    lesregles=interne.cssRules;
    alert(lesregles);
    //le texte de la regle 0
    alert(lesregles[0].cssText);
    //ajouter une regle
    interne.insertRule("#un {color:red}",0)
}
</script>
</html>

Remarque

Vous ne pouvez accéder aux règles d'une feuille externe que si elle est sur le meme serveur que votre page html. En particulier certain navigateur refuse cette accès si vous lisez la page en locale (file://)

Le style calculé

Quand un style est hérité ou peut provenir de plusieurs réglés on peut vouloir savoir quelle valeur a (au moment de l'execution) une certaine propriété , c'est le style calculé qu'on peut acceder par la propriété getComputedStyle

Manipulation d'autre attributs

On peut aussi modifier d'autres attribut que le style par exemple la source (src) sur une image.

exemple de la source d'une image

<p>passez la souris sur l'image </p>
<img id="styleImg" src="soleil.png" all="">
<script>
let img=document.querySelector("#styleImg");
img.addEventListener("mouseover",()=>img.src="pluie.png",true)
img.addEventListener("mouseout",()=>img.src="soleil.png",true)
</script>

passez la souris sur l'image

Manipulation des attributs class et id

Lorsque vous récupérez un noeud n.
Les propriétés id et className vous permettent de lire ou de positionner les attributs id ou class du noeud.

Attention :

c'est bien className car le mot clef class est réservé la definition de classe en programmation objet ).

La propriété classList (en lecture seule) renvoie une liste des attributs class de l'objet.
Pour modifier cette liste on dispose des méthodes add et remove

Exemple :


<style>
#exempleDomClassId .rouge {
  color:red
}
#exempleDomClassId .gros {
  font-size:200%;
}
#exempleDomClassId #mondiv {
  background-color:yellow;
}
</style>
<div id="exempleDomClassId">
  <div class="rouge" style="height:50px;">
  cliquer un  bouton
</div>
<button onclick="montreClasse()">voir classe</button>
<button onclick="ajouteClasseGros()">ajouter classe cadre</button>
<button onclick="addId()">ajouter id</button>
<button onclick="reinitialiser()">réinitialise</button>
</div>  
<script>
  let mondiv=document.querySelector("#exempleDomClassId div");
  function montreClasse(){alert(mondiv.className);}
  function ajouteClasseGros(){mondiv.className="gros";}
  function addId(){mondiv.id="mondiv";}
  function reinitialiser(){
    mondiv.classList.remove("gros");
    mondiv.classList.add("rouge");
    mondiv.id=""; 
    }
</script>

cliquer un bouton

Manipulation générale des attributs.

On a vu que les attributs (style, id, class, src ...) peuvent être lu et positionné a partir de leur nom. Pour les supprimer on peut positionner la valeur a "" mais ca ne supprime pas l'attribut.

On dispose aussi de méthode de manipulation générale d'attribut :

Manipulation de la structure du document

Il y a essentiellement deux méthodes:

  1. innerHTML

Lorsque vous récupérez un noeud n.
innerHTML vous permet de a. lire le code html correspondant au sous arbre issus de n:

```js
n.innerHTML;
```js

b. remplacer le sous arbre issus de `n` par l'arbre obtenu en analysant la chaîne, contenant du code html , passée en  argument :

```js
n.innerHTML="du <b>code <em> html </em> <b>."
```

Cela peut pauser problème si le code html que vous passez en argument n'est pas valide.
  1. les méthode du DOM

creation de noeuds

avec createTextNode

exemple

let unNoeudText=document.createTextNode('le contenu textuel');

exemple

let unDiv = document.createElement("div");

Une fois les noeuds crées ils n'apparaissent pas pour autant car il n'est pas encore rattaché a l'arbre du document qui est le seul visible.

Pour cela il faut les rattaché à un noeud existant de l'arbre au moyen d'une des méthodes:

Exemple :creation du document au chargement d'une page

<html>
  <head>
    <script>
       // 
       //
       window.onload = function() {
         //créer un noeud element h1
         const h1 = document.createElement("h1");
         //creer un noeud texte
         const texte_du_h1 = document.createTextNode("Ajout d'un titre H1");
         //liaison  du texte au h1
         h1.appendChild(texte_du_h1);
         //liaison du h1 sur l'arbre du document
         //comme dernier enfant du noeud body
         document.body.appendChild(h1);
      }
    </script>
  </head>
  <body>
  </body>
</html>

Se déplacer dans l'arbre

Pour « descendre » depuis un noeud : 1. Les noeuds enfants d'un noeud: - childNodes renvoie la liste des fils d'un noeud.

On a deux cas particulier:

  1. Les noeuds frère d'un noeud:

Pour remonter:

parentNode pour remonter sur le parent.

Exemple de Parcours d'arbre

<ol id="maListeOrdonnee">
<li> Récupérer l'élément racine à partir duquel on fera le parcours.</li>
<li> Utiliser childNodes pour parcourir les fils des éléments en question.</li>
<li> Pour les dit fils, on regardera quel est leur type, et on agira en conséquence.</li>
</ol> 
<p>Parcours d'<strong>arbre</strong></p>

Exemple: on veut afficher le <em>texte</em> de la liste précédente dans l'élément ci-dessous.
<button onclick="copierListe()">copier</button>
<div id="ciblePourCopie" style="border:solid">
texte quelconque
</div>

<script type="text/javascript">
function copierListe() {
    // On récupère l'élément de base
    var eltDeBase= document.getElementById('maListeOrdonnee');
    var txt= getTexteDans(eltDeBase);
    document.getElementById('ciblePourCopie').firstChild.nodeValue= txt;
}

function getTexteDans(noeud) {
    var result= "";
    var i;
    // Le texte contenu dans un noeud
    // est celui contenu dans ses enfants, si c'est un élément
    // et est nodeValue si c'est un noeud texte.
    switch (noeud.nodeType) {
        case 1: // element
            for (i=0; i< noeud.childNodes.length; i++) {
                result+= getTexteDans(noeud.childNodes[i]);
            }
        break;
        case 3: // texte
            result= noeud.nodeValue;
        break;
    }
    return result;
}

</script>
  1. Récupérer l'élément racine à partir duquel on fera le parcours.
  2. Utiliser childNodes pour parcourir les fils des éléments en question.
  3. Pour les dit fils, on regardera quel est leur type, et on agira en conséquence.

Parcours d'arbre

Exemple: on veut afficher le texte de la liste précédente dans l'élément ci-dessous.
texte quelconque

Verification de formulaires

Javascript permet de valider les formulaires côté client.

Attention :

Cela ne dispense pas de la validation côté serveur, qui est indispensable pour des raisons de sécurité; en effet
le serveur n'a a priori aucune raison de faire confiance au client.

On peut accéder facilement aux formulaires et au champs par leurs id si on leur en donne.
On peut toujours accéder aux formulaires et au champs via querySelector ou une autre méthode de selection.

Principe :

On lie des fonctions javascript à certains événements (on soumet le formulaire, on quitte un champ, on valide le formulaire, on presse une touche...)

Vérification à l'expédition

On veut tester que le nom et le prénom fournis par l'utilisateur ne sont pas vides.

<div>
  <form id="ExForm1" action=https://cedric.cnam.fr/~pons/NFA040/echo.php>
  <label for='nom'> Nom </label> 
  <input type='text' id='nom' name='nom'>

  <label for='prenom'> prenom </label> 
  <input type='text' id='prenom' name='prenom'>
  <input type='submit' id='envoyerEx1'>
</form>
<script>
function verifNomPrenom(){
  let nom=document.getElementById('nom').value;
  let prenom=document.getElementById('prenom').value;
  if  (nom !="" && prenom !=""){
    alert("c'est bon on envoi");return true;
  }
  else {
    alert("c'est pas bon on n'envoi pas ");return false;
  }
}
document.getElementById('ExForm1').addEventListener("submit",function (e){
    if (!verifNomPrenom()){
        e.preventDefault();
        return false;
        }
    return true}) 
</script>
</div>

Tester des champs en cours d'édition,

on peut utiliser les événements blur ou change, (onblur et onchange si vous les prositionner comme attribut dans le html) pour tester des champs en cours ou en fin d'editions.

change s'utilise aussi pour un champ de type select, auquel cas il permet tout simplement de savoir que la sélection a été modifiée.

Ils s'utilise en complément de submit

On n'a pas besoin de retourner une valeur

Exemple

cliquez dans nom, puis dans prenom, taper des lettre, cliquez en dehors...


<div>
    <form id="Ex2Form" action="https://cedric.cnam.fr/~pons/NFA040/echo.php">
    <label for='Ex2nom'> Nom </label> 
    <input type='text' id='Ex2nom' name='Ex2nom'>
    <span id="Ex2ErreurNom" class="error"></span> <br>
    
    <label for='Ex2prenom'> prenom </label> 
    <input type='text' id='Ex2prenom' name='Ex2prenom'>
    <span id="Ex2ErreurPrenom" class="error"></span> <br>
    
    <input type='submit' id='envoyerEx2'>
    </form>
    <script>
    function verifNomPrenom(){
      let nom=document.getElementById('Ex2nom').value;
      let prenom=document.getElementById('Ex2prenom').value;
      if  (nom !="" && prenom !=""){
        alert("c'est bon on envoi");return true;
      }
      else {
        alert("c'est pas bon on n'envoi pas ");return false;
      }
    }
    document.getElementById('Ex2Form').addEventListener("submit",function (e){
        if (!verifNomPrenom()){
            e.preventDefault();
            return false;
            }
        return true}) 
    let Ex2nom=document.getElementById('Ex2nom') ;   
    let Ex2ErreurNom=document.getElementById('Ex2ErreurNom') ;  
    let Ex2prenom=document.getElementById('Ex2prenom') ;   
    let Ex2ErreurPrenom=document.getElementById('Ex2ErreurPrenom') ;  
    
    alert(Ex2nom.id)
    Ex2nom.addEventListener("blur",function (e){

        alert("sortie du champ Nom");
        if(Ex2nom.value===""){
          Ex2ErreurNom.innerHTML="nom ne doit pas être vide !";
        }    
        e.preventDefault();
    });

    Ex2prenom.addEventListener("change",function (e){
        
        alert("changement dans prenom");
        if(Ex2prenom.value===""){
          Ex2ErreurPrenom.innerHTML="prenom ne doit pas être vide !";
        }    
        e.preventDefault();
    });
    </script>
</div>



Récupérer et tester radio, checkbox select/option

Propriétés de et

Exemple:

<div id="checkbox1Code"><p>Checkbox <input type="checkbox" value="checkcheck" id="checkbox1"/></p>
<p><button onclick="alert(document.getElementById('checkbox1').checked)">montrer   valeur</button></p>
<p><button onclick="document.getElementById('checkbox1').checked=!
              document.getElementById('checkbox1').checked">
changer valeur</button>
</p>
</div>

Checkbox

Attention :

il n'y pas d'événement change sur les checkbox ou radio !
Utilisez l'évènement click

<div id="checkbox2Code">
  <p>Checkbox <input type="checkbox" value="checkcheck" id="checkbox2" onclick="montreCheckBox()"/></p>
  <script type="text/javascript">
    function montreCheckBox() {
      var elt= document.getElementById('checkbox2');
      alert("cochée : "+ elt.checked); 
    }
  </script>
</div>

Checkbox

Propriétés des éléments de formulaire: select

Un élément select contient des éléments option.

Si le select ne permet qu'une sélection (ie. n'a pas l'attribut multiple), on utilisera la propriété value du select :

let  monSelect= document.getElementById("idSelect");
let  monOpt= monSelect.value;

Sinon, on récupérera le tableau des options (options[]) ou on attaquera directement l'option désirée.

Le code suivant donne la première option sélectionnée:

let  monOpt= monSelect.options[monSelect.selectedIndex].value;

Dans le cas où il y a plusieurs options sélectionnables, le code suivant les range les options sélectionnées dans un tableau:

<select id="Exmonselect" multiple="multiple" size="3">
<option selected="selected">un</option>
<option>deux</option>
<option selected="selected">trois</option>
<option>quatre</option>
</select>
<button onclick="voirLesSelect()">voir les valeurs sélectionnées</button>
<script>
function voirLesSelect(){
let ExmonSelect=document.getElementById("Exmonselect");
let o= ExmonSelect.options;
let resultat=[];
for (let i= 0; i < o.length; i++) {
    if (o[i].selected) {
  resultat.push(o[i].value);
        }
    }
alert(resultat)  
}
</script>

Le select a aussi une propriété selectedIndex qui donne la position du premier élément sélectionné.

Notez que value, selectedIndex (pour les select) et selected pour les options peuvent être modifiés.

Jouer avec les API html5

Canvas

<canvas> est une balise qui permet de définir une zone graphique ou l'on peut dessiner des graphiques, placer et manipuler des images ou des trame video.
Le contenu visuel à l'intérieur du canevas peut être scripté pour changer au fil du temps (d'où l'animation) et en réponse à des événement.

Il faut d'abord récupérer le canvas et le context de dessin 2D associé.

canvas = document.getElementById("idDuCanvas");
ctx = canvas.getContext("2d");

Il est aussi possible de récupérer un context 3D et de le manipulé avec la bibliothèque three.js mais cela dépasse le cadre de ce cours.

On peut alors dessiner dans la zone à l'aide des méthodes suivant:

ctx.strokeRect(10, 10, 50, 60)
ctx.fillRect(100, 100, 60, 50)
ctx.arc(100, 100, 30, 0, Math.PI, false);
ctx.fill();

On peut aussi dessiner diverse forme grace à cette notion de chemin.

Un chemin est une liste d'action. L'idée et de lister des déplacements et des tracés qui ne seront effectués qu'après par un appel a stroke ou fill.

On peut aussi définir des style, essentiellement des couleurs d'écriture ou de remplissage. par les méthodes ctx.fillStyle et ctx.strokeStyle.

Exemple :

Exemple d'un jeu de raquette :


<html>
  
<head>

    <script>
   // les objects
    let  balle = {
        x:150,                  //  position de la balle en x.
        y: 150,                 //  position de la balle en y.
        dX: 2,                  //   pas de déplacement en x 
        dY: 4,                   //   pas de déplacement en y
        move(terrain,pallet){

        }
    }
    
    let terrain ={
        l: 300,                  // largeur du  terrain de jeu.
        h:300                    // hauteur du terrain de jeu
     }

    let palet ={
        x: 150,                   // position de de la raquette  
        h: 10,                    // épaisseur de la raquette
        l: 150,                   // largeur de la raquette.
        y:function() {return terrain.h - this.h}  // position de la raquette en y (coin sup gauche).

    }

    
    let canvas;                            // l'element Canvas.
    let ctx;                               // le context du Canvas.
    let gameLoop;                         //  nom du timer  utilise dans la boucle de jeu (via setInterval)

    
      // fonction d'initialisation sera appellé  sur l'evenement onLoad 
      function initGame() {

        // recuperer le canvas.
        canvas = document.getElementById("gameBoard");

        // verifier qu'il existe.
        if (canvas.getContext) {
          // choisir le context 2d
          ctx = canvas.getContext("2d");

          // jouer jusqu'a ce qu'on perde 
          gameLoop = setInterval(drawBall, 16);

          // Add keyboard listener.
          window.addEventListener('keydown', quelleTouche, true);

        }
      }


    //fonction de dessin
      function drawBall() {

        // Effacer le terrain.
        ctx.clearRect(0, 0, terrain.l, terrain.h);

        // Remplir le terrain.
        ctx.fillStyle = "green";
        ctx.beginPath();
        ctx.rect(0, 0, terrain.l, terrain.h);
        ctx.closePath();
        ctx.fill();

        // Dessinner la balle.
        ctx.fillStyle = "red";
        ctx.beginPath();
        ctx.arc(balle.x, balle.y, 15, 0, Math.PI * 2, true);
        ctx.closePath();
        ctx.fill();

        // Dessinner la raquette
        ctx.fillStyle = "blue";
        ctx.beginPath();
        ctx.rect(palet.x, palet.y(), palet.l, palet.h);
        ctx.closePath();
        ctx.fill();

        // Changer la position de la balle.
        balle.x += balle.dX;
        balle.y += balle.dY;

    // Rebond sur la gauche ou la droite  (inversion du DX)     
        if (balle.x + balle.dX > terrain.l - 15 || 
            balle.x + balle.dX < 15) {
              balle.dX = -balle.dX;
            }

    // Rebond au sommet  (inversion du DY)
        if (balle.y + balle.y < 15) balle.dY = -balle.dY;

    // Si la balle touche le bas, verifier si elle touche la raquette
          else if (balle.y + balle.dY > terrain.h - 15) {
              // Si elle touche la raquette, rebondir (inversion du DY)
        if (balle.x > palet.x && balle.x < palet.x + palet.l) balle.dY = -balle.dY;
              // Sinon c'est perdu
          else {
            clearInterval(gameLoop);
            alert("Game over!");
          }
        }
      }
      /* Traitement des événements */
      function quelleTouche(evt) {

        switch (evt.code) {
          // fleche gauche.
        case "ArrowLeft":
          palet.x = palet.x - 20;
          if (palet.x < 0) palet.x = 0;
          break;

          // fleche droite.
        case "ArrowRight":
          palet.x = palet.x + 20;
          if (palet.x > terrain.l - palet.l) palet.x = terrain.l - palet.l;
          break;
        }
      }
    </script>
  </head>
  
  <body onload="initGame()">
    <h1>
      jeux de raquette
    </h1>
    <div>
      <canvas id="gameBoard" width="300" height="300">
      </canvas>
    </div>
  </body>

</html>

Que vous pouvez tester ici