• Facile

Ce cours est visible gratuitement en ligne.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 08/01/2013

Les cibles

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Dans tous nos scripts précédents, on était obligés de mettre des évènements onmousedown partout, sur chaque élément que l'on voulait déplacer. En règle générale, il est normalement déconseillé de mélanger le Javascript avec le code HTML à l'intérieur d'une page, car il faut séparer le contenu (HTML), de la présentation (CSS) et du comportement (Javascript).

Alors, au chargement de la page, on pourrait assigner dynamiquement chaque évènement sur chaque bloc :

function rendre_deplacable(objet)
{
  addEvent(objet,'mousedown',function (event) {
    start_drag(objet, event);
  });
}

window.onload = function ()
{
  var elements = document.getElementsByTagName('div'); //on va rendre déplaçables toutes les div
  for( var i = 0; i < elements.length; i++)
  {
    rendre_deplacable(elements.item(i));
  }
}

Mais ensuite, si on rajoute de nouveaux div dans notre page (dynamiquement), ils ne seront pas déplaçables ?

Eh non. :( À chaque nouveau div, il faudra rajouter l'évènement Javascript qui va avec. On pourrait utiliser la fonction rendre_deplacable() mais on va apprendre ici une nouvelle méthode.

La propagation des évènements

Essayons d'imbriquer deux éléments qui réagissent tous deux au même évènement :

<div onmousedown="start_drag(this,event);" style="width: 500px; height: 500px; background-color: green;">
  <div onmousedown="start_drag(this,event);" style="width: 200px; height: 200px; background-color: yellow;">
  </div>
</div>

Tester cet exemple qui contient un bogue

On a placé un évènement onmousedown sur le div jaune, pourtant, il ne se déplace jamais ! C'est toujours le vert qui bouge !?

C'est dû à la propagation des évènements. Lorsqu'on clique sur le div jaune, on clique également sur le div vert conteneur. La fonction start_drag est donc immédiatement appelée pour le div jaune puis pour le vert.

Image utilisateur

La fonctiononmousedown du bloc jaune est d'abord lue, puis celle du div vert, puis le onmousedown présent sur la page entière.
Pour éviter, cette propagation, appelée "bubbling" (bouillonnement) car l'évènement se propage comme une bulle, il faut rajouter une ligne à la fin de start_drag :

event.cancelBubble = true;

Et voilà l'exemple précédent fonctionnel

Récupérer la cible

On a vu que les évènements se propagent jusqu'à arriver sur l'objet document. Soyons fainéants (les programmeurs sont des fainéants :p ) et attendons qu'il arrive jusqu'au document plutôt que de placer des onmousedown partout.

C'est là qu'il falloir utiliser une autre propriété de l'objet event :) Sur la plupart des navigateurs, on utilisera event.target
alors que sur Internet Explorer, il faudra utiliser event.srcElement.

Pour un code qui marche partout, on va donc créer une variable universelle :

var target = event.target || event.srcElement

Grâce à elle, on peut savoir sur quel élément précis on a cliqué (ou bougé) :

document.onclick = function (event)
{
  event = event || window.event;
  var target = event.target || event.srcElement;
  
  var type;
  if(target.nodeType == 1)
    type = 'Tag: ' + target.tagName;
  else
    type = target;
   alert('Vous avez cliqué sur ' + type);
};

Ce script permet de savoir sur quelle balise on a cliqué

Grâce à cela, on peut lancer directement un déplacement de bloc, sans avoir à ajouter un évènement sur le bloc en question :

addEvent(document,'mousedown',function (event)
{
  var target = event.target || event.srcElement;
  start_drag(target, event);
});

Ce code démarre un déplacement de la balise HTML situé juste en dessous de la souris

Ce n'est pas exactement ce qu'on veut faire mais on va voir comment corriger ce bogue. ;)

Avec des classes

Il va donc falloir trouver un moyen d'identifier les objets HTML que l'on a le droit de bouger et ceux qui sont fixes. Je vous propose donc d'utiliser les classes pour identifier ce qui est déplaçable.

On va placer une classe sur les éléments de la page qui ont le droit d'être déplacés. Par exemple, on va mettre la classe deplacable.
Lorsqu'on clique sur un élément, on clique aussi sur son parent. Il va falloir tester l'existence de la classe sur tous les parents de la cible jusqu'à en trouver un qui possède la classe que l'on désire ou si on n'en trouve aucun, ne rien faire :

addEvent(document,'mousedown',function (event)
{
  var target = event.target || event.srcElement;
  
  var element = target;
  while(element)
  {
    if( element.className && element.className == 'deplacable')
    {
      start_drag(element, event);
      element = false; //On stoppe la boucle : sinon, on va se retrouver avec le même problème que le bubbling car le déplacement va aussi se déclencher pour les parents 
    }
    else
      element = element.parentNode; //Sinon, on continue à remonter dans les ancêtres de la cible
  }

});

Tester : pensez aussi à rajouter class="deplacable" sur le div de la fenêtre.

Plusieurs classes

Et si on profitait du fait qu'il est possible de mettre plusieurs classes sur un même tag HTML ? Il suffit de séparer par un espace :

<div class="classe1 classe2 classe3"></div>

Avec une regex, on peut savoir si tel ou tel objet possède telle classe :

if( bloc.className && bloc.className.match(/\bclasse1\b/g) )
{
  alert( 'Ce bloc possède bien la classe "classe1" !');
}

(Les \b sont des assertions pour indiquer un espace entre deux mots. ;) )
Comme on peut mettre plusieurs classes, on peut indiquer d'un côté le comportement Javascript de l'élément, et d'un autre côté l'apparence qu'il aura grâce au style CSS.

Fil rouge : sans ajout d'évènement

Nous allons modifier notre script de "fausses fenêtres" pour le rendre plus universel, nous n'aurons plus besoin de taper une seule ligne de code Javascript dans notre code HTML. Juste rajouter des classes pour identifier chaque élément de la fenêtre.

Je vous propose cette liste de classes :

  • window-base à placer sur la fenêtre elle-même pour indiquer que c'est elle qui bouge ;

  • window-move à placer sur les objets qui vont déclencher un déplacement (la barre des titres par exemple) ;

  • window-close à placer sur les boutons qui vont fermer la fenêtre.

(On pourrait par exemple faire un système de barre des tâches, mais honnêtement, je suis trop paresseux ; je n'ai donc pas proposé le bouton "réduire" :p .)

Correction

On possède déjà une fonction start_drag mais il nous faut aussi des fonctions pour fermer et maximiser les fenêtres.

function min_max(fenetre)
{
  if( fenetre.style.width != '100%' && fenetre.style.height != '100%') //si la fenêtre n'est pas déjà maximisée
  {
     fenetre.style.width = '100%'; //maximum largeur et hauteur
     fenetre.style.height = '100%';
     fenetre.style.position = 'absolute';
     fenetre.style.left = 0;  //à partir du coin en haut à gauche
     fenetre.style.top = 0;
  }
  else
  {
    fenetre.style.width = '';
    fenetre.style.height = '';
  }
}

function close(fenetre)
{
  fenetre.parentNode.removeChild(fenetre);  //On peut enlever le bloc du document
  //ou: fenetre.style.display = 'none';     //ou alors le cacher avec du style CSS
}

Une fois ces fonctions créées, il faut rajouter un évènement onmousedown sur la page entière pour tout récupérer. On récupère dans un premier temps la fenêtre mais aussi les boutons pour savoir quelle action on doit faire.

function drag_onmousedown (event)
{
  var target = event.target || event.srcElement;
  
  //On commence par trouver la fenêtre elle-même
  var fenetre = target;
  while( fenetre)
  {
    if( fenetre.className && fenetre.className.match(/bwindow-baseb/g))
    {
       break; //On arrête la boucle
    }
    fenetre = fenetre.parentNode;
  }
  if( !fenetre) //Si on est sortis de la boucle mais qu'on n'a trouvé aucune fenêtre, on abandonne
    return;

  //Maintenant, on part à la recherche d'un bouton déclencheur
  var element = target;
  while(element)
  {
    if( element.className)
    {
      if( element.className.match(/\bwindow-close\b/g))
      {
        close(fenetre);
        break;
      }
      else if( element.className.match(/\bwindow-min-max\b/g) )
      {
        min_max(fenetre);
        break;
      }
      else if( element.className.match(/\bwindow-move\b/g) )
      {
        start_drag(fenetre, event);
        break;
      }
    }
    element = element.parentNode;
  }
}
addEvent(document,'mousedown',drag_onmousedown);

J'ai légèrement modifié le code de la feuille de style pour ne plus faire référence à des id mais uniquement à des classes. On peut alors créer plusieurs fenêtres avec un simple copier-coller sans avoir de problèmes au niveau des id qui doivent être uniques.

En assignant un seul évènement Javascript : onmousedown sur la page entière, on a pu créer un système qui déplace juste certains blocs.
Il n'y a plus de code Javascript parasite à l'intérieur du code HTML, les attributs classes permettent de dire comment va se comporter tel ou tel élément.

Notre système sépare le contenu de la présentation et du comportement. :)

La première partie du tutoriel est terminée. On sait déplacer un objet, pour qu'il suive la souris. On a vu comment obtenir un décalage et aussi comment connaître l'élément qui est survolé par la souris.

Pour faire des scripts plus évolués, c'est surtout une histoire d'innovation (ou plutôt d'imagination ? :p ).

Exemple de certificat de réussite
Exemple de certificat de réussite