Le JavaScript est un langage permettant de rendre une page Web dynamique du côté du client. Seulement, quand on pense à « dynamique », on pense aussi à « animations ». Or, pour faire des animations, il faut savoir accéder au CSS et le modifier. C'est ce que nous allons étudier dans ce chapitre.
Au programme, l'édition du CSS et son analyse. Pour terminer le chapitre, nous étudierons comment réaliser un petit système de drag & drop : un sujet intéressant !
Avant de s'attaquer à la manipulation du CSS, rafraîchissons-nous un peu la mémoire :
CSS est l'abréviation de Cascading Style Sheets, c'est un langage qui permet d'éditer l'aspect graphique des éléments HTML et XML. Il est possible d'éditer le CSS d'un seul élément comme nous le ferions en HTML de la manière suivante :
style="color:red;"Le CSS de cet élément a été modifié avec l'attribut STYLE. Il n'y a donc que lui qui possède un texte de couleur rouge.
Mais on peut tout aussi bien éditer les feuilles de style qui se présentent de la manière suivante :
div {
color: red; /* Ici on modifie la couleur du texte de tous les éléments <div> */
}
Il est de bon ton de vous le rappeler
: les propriétés CSS de l'attributstyle
sont prioritaires sur les propriétés
d'une feuille de style ! Ainsi, dans le code d'exemple suivant, le texte n'est pas rouge mais bleu :
div {
color: red;
}
style="color:blue;"I'm blue ! DABADIDABADA !
Voilà tout pour les rappels sur le CSS. Oui, c'était très rapide, mais il suffisait simplement d'insister sur cette histoire de priorité des styles CSS, parce que ça va vous servir !
Comme nous venons de le voir, il y a deux manières de modifier le CSS
d'un élément HTML, nous allons ici aborder la méthode la plus simple et la plus utilisée : l'utilisation de la
propriétéstyle
. L'édition des feuilles de style ne sera pas abordée,
car elle est profondément inutile en plus d'être mal gérée par de nombreux navigateurs.
Alors comment accéder à la
propriétéstyle
de notre élément ? Eh bien de la même manière que pour
accéder à n'importe quelle propriété de notre élément :
element.style; // On accède à la propriété « style » de l'élément « element »
Une fois que l'on a accédé à notre
propriété, comment modifier les styles CSS ? Eh bien tout simplement en écrivant leur nom et en leur attribuant une
valeur,width
(pour la largeur) par exemple :
element.style.width = '150px'; // On modifie la largeur de notre élément à 150px
Maintenant, une petite question pour vous : comment accède-t-on à une propriété CSS qui possède un nom composé ? En JavaScript, les tirets sont interdits dans les noms des propriétés, ce qui fait que ce code ne fonctionne pas :
element.style.background-color = 'blue'; // Ce code ne fonctionne pas, les tirets sont interdits
La solution est simple : supprimer les tirets et chaque mot suivant normalement un tiret voit sa première lettre devenir une majuscule. Ainsi, notre code précédent doit s'écrire de la manière suivante pour fonctionner correctement :
element.style.backgroundColor = 'blue'; // Après avoir supprimé le tiret et ajouté une majuscule au deuxième mot, le code fonctionne !
Comme vous pouvez le constater, l'édition du CSS d'un élément n'est pas bien compliquée. Cependant, il y a une limitation de taille : la lecture des propriétés CSS !
Prenons un exemple :
type="text/css"
#myDiv {
background-color: orange;
}
id="myDiv"Je possède un fond orange.
var myDiv = document.getElementById('myDiv');
alert('Selon le JavaScript, la couleur de fond de ce <div> est : ' + myDiv.style.backgroundColor); // On affiche la couleur de fond
Et on n'obtient rien ! Pourquoi ?
Parce que notre code va lire uniquement les valeurs contenues dans la
propriétéstyle
. C'est-à-dire, rien du tout dans notre exemple, car
nous avons modifié les styles CSS depuis une feuille de style, et non pas depuis
l'attributstyle
.
En
revanche, en modifiant le CSS avec l'attributstyle
, on retrouve sans
problème la couleur de notre fond :
id="myDiv" style="background-color: orange"Je possède un fond orange.
var myDiv = document.getElementById('myDiv');
alert('Selon le JavaScript, la couleur de fond de ce DIV est : ' + myDiv.style.backgroundColor); // On affiche la couleur de fond
C'est gênant n'est-ce pas ?
Malheureusement, on ne peut pas y faire grand-chose à partir de la propriétéstyle
,
pour cela nous allons devoir utiliser la méthodegetComputedStyle()
!
getComputedStyle()
Comme vous avez pu le constater, il n'est pas possible de
récupérer les valeurs des propriétés CSS d'un élément par le biais de la
propriétéstyle
vu que celle-ci n'intègre pas les propriétés CSS des
feuilles de style, ce qui nous limite énormément dans nos possibilités d'analyse… Heureusement, il existe une
fonction permettant de remédier à ce problème :getComputedStyle()
!
Cette fonction va se charger de récupérer, à notre place, la
valeur de n'importe quel style CSS ! Qu'il soit déclaré dans la propriétéstyle
,
une feuille de style ou bien même encore calculé automatiquement, cela importe peu
:getComputedStyle()
la récupérera sans problème.
Son fonctionnement est très simple et se fait de cette manière :
#text {
color: red;
}
id="text"
var text = document.getElementById('text'),
color = getComputedStyle(text).color;
alert(color);
offset
Certaines valeurs de positionnement ou de taille des éléments ne pourront pas être obtenues de façon simple
avecgetComputedStyle()
, pour pallier ce problème il existe les
propriétésoffset
qui sont, dans notre cas, au nombre de cinq :
Nom de l'attribut |
Contient… |
---|---|
|
Contient la largeur complète
( |
|
Contient la hauteur complète
( |
|
Surtout utile pour les éléments en position absolue. |
|
Surtout utile pour les éléments en position absolue. |
|
Utile
que pour un élément en position absolue ou relative ! |
Leur utilisation ne se fait pas de la même manière que
n'importe quel style CSS, tout d'abord parce que ce ne sont pas des styles CSS ! Ce sont juste des propriétés (en
lecture seule) mises à jour dynamiquement qui concernent certains états physiques d'un élément.
Pour les
utiliser, on oublie la propriétéstyle
vu qu'il ne s'agit pas de styles
CSS et on les lit directement sur l'objet de notre élément HTML :
alert(el.offsetHeight); // On affiche la hauteur complète de notre élément HTML
offsetParent
Concernant la propriétéoffsetParent
,
elle contient l'objet de l'élément parent par rapport auquel est positionné votre élément actuel. C'est bien, mais
qu'est-ce que ça veut dire ?
Ce que nous allons vous expliquer concerne des connaissances en HTML et en CSS et non pas en JavaScript ! Seulement, il est fort possible que certains d'entre vous ne connaissent pas ce fonctionnement particulier du positionnement absolu, nous préférons donc vous le rappeler.
Lorsque vous décidez de mettre un de vos éléments HTML en positionnement absolu, celui-ci est sorti du positionnement par défaut des éléments HTML et va aller se placer tout en haut à gauche de votre page Web, par-dessus tous les autres éléments. Seulement, ce principe n'est applicable que lorsque votre élément n'est pas déjà lui-même placé dans un élément en positionnement absolu. Si cela arrive, alors votre élément se positionnera non plus par rapport au coin supérieur gauche de la page Web, mais par rapport au coin supérieur gauche du précédent élément placé en positionnement absolu, relatif ou fixe.
Ce système de positionnement est clair ? Bon,
nous pouvons alors revenir à notre propriétéoffsetParent
! Si elle
existe, c'est parce que les
propriétésoffsetTop
etoffsetLeft
contiennent
le positionnement de votre élément par rapport à son précédent élément parent et non pas par rapport à la
page ! Si nous voulons obtenir son positionnement par rapport à la page, il faudra alors aussi ajouter les valeurs
de positionnement de son (ses) élément(s) parent(s).
Voici le problème mis en pratique ainsi que sa solution :
#parent,
#child {
position: absolute;
top: 50px;
left: 100px;
}
#parent {
width: 200px;
height: 200px;
background-color: blue;
}
#child {
width: 50px;
height: 50px;
background-color: red;
}
id="parent"
id="child"
var parent = document.getElementById('parent');
var child = document.getElementById('child');
alert("Sans la fonction de calcul, la position de l'élément enfant est : \n\n" +
'offsetTop : ' + child.offsetTop + 'px\n' +
'offsetLeft : ' + child.offsetLeft + 'px');
function getOffset(element) { // Notre fonction qui calcule le positionnement complet
var top = 0,
left = 0;
do {
top += element.offsetTop;
left += element.offsetLeft;
} while (element = element.offsetParent); // Tant que « element » reçoit un « offsetParent » valide alors on additionne les valeurs des offsets
return { // On retourne un objet, cela nous permet de retourner les deux valeurs calculées
top: top,
left: left
};
}
alert("Avec la fonction de calcul, la position de l'élément enfant est : \n\n" +
'offsetTop : ' + getOffset(child).top + 'px\n' +
'offsetLeft : ' + getOffset(child).left + 'px');
Comme vous pouvez le constater, les valeurs seules de positionnement de notre élément enfant ne sont pas correctes si nous souhaitons connaître son positionnement par rapport à la page et non pas par rapport à l'élément parent. Nous sommes finalement obligés de créer une fonction pour calculer le positionnement par rapport à la page.
Concernant cette fonction, nous allons insister sur la boucle qu'elle contient car il est probable que le principe ne soit pas clair pour vous :
do {
top += element.offsetTop;
left += element.offsetLeft;
} while (element = element.offsetParent);
Si on utilise ce code HTML :
id="parent" style="position:absolute; top:200px; left:200px;"
id="child" style="position:absolute; top:100px; left:100px;"
son schéma de fonctionnement sera le
suivant pour le calcul des valeurs de positionnement de l'élément#child
:
La boucle
s'exécute une première fois en ajoutant les valeurs de positionnement de
l'élément#child
à nos deux
variablestop
etleft
.
Le calcul effectué est donc :
top = 0 + 100; // 100
left = 0 + 100; // 100
Ligne 4, on
attribue àelement
l'objet de l'élément parent
de#child
. En gros, on monte d'un cran dans l'arbre DOM. L'opération est
donc la suivante :
element = child.offsetParent; // Le nouvel élément est « parent »
Toujours
ligne 4,element
possède une référence vers un objet valide (qui
est l'élément#parent
), la condition est donc vérifiée (l'objet est
évalué àtrue
) et la boucle s'exécute de nouveau.
La boucle se
répète en ajoutant cette fois les valeurs de positionnement de
l'élément#parent
à nos variablestop
etleft
.
Le calcul effectué est donc :
top = 100 + 200; // 300
left = 100 + 200; // 300
Ligne 4,
cette fois l'objet parent de#parent
est
l'élément<body>
. La boucle va donc se répéter
avec<body>
qui est un objet valide. Comme nous n'avons pas touché
à ses styles CSS il ne possède pas de valeurs de positionnement, le calcul effectué est donc :
top = 300 + 0; // 300
left = 300 + 0; // 300
Ligne
4,<body>
a une propriétéoffsetParent
qui
est àundefined
, la boucle s'arrête donc.
Voilà tout pour cette boucle ! Son fonctionnement n'est pas bien compliqué mais peut en dérouter certains, c'est pourquoi il valait mieux vous l'expliquer en détail.
Avant de terminer : pourquoi avoir écrit « hauteur complète
(width
+padding
+border
)
» dans le tableau ? Qu'est-ce que ça veut dire ?
Il faut savoir qu'en HTML, la largeur (ou hauteur) complète d'un élément correspond à la valeur de width + celle du padding + celle des bordures.
Par exemple, sur ce code :
#offsetTest {
width: 100px;
height: 100px;
padding: 10px;
border: 2px solid black;
}
id="offsetTest"
la largeur complète de notre
élément<div>
vaut :100
(width
)
+10
(padding-left
)
+10
(padding-right
)
+2
(border-left
)
+2
(border-right
) = 124px.
Et il s'agit bien de la valeur retournée
paroffsetWidth
:
var offsetTest = document.getElementById('offsetTest');
alert(offsetTest.offsetWidth);
Soyons francs : les exercices que nous avons fait précédemment
n'étaient quand même pas très utiles sans une réelle interaction avec l'utilisateur.
Lesalert()
,confirm()
etprompt()
c'est
sympa un moment, mais on en a vite fait le tour ! Il est donc temps de passer à quelque chose de plus intéressant :
un système de drag & drop ! Enfin… une version très simple !
Il s'agit ici d'un mini-TP, ce qui veut dire qu'il n'est pas très long à réaliser, mais il demande quand même un peu de réflexion. Ce TP vous fera utiliser les événements et les manipulations CSS.
Tout d'abord, qu'est-ce que le drag & drop ? Il s'agit d'un système permettant le déplacement d'éléments par un simple déplacement de souris. Pour faire simple, c'est comme lorsque vous avez un fichier dans un dossier et que vous le déplacez dans un autre dossier en le faisant glisser avec votre souris.
Et je suis vraiment capable de faire ça ?
Bien évidemment ! Bon, il faut avoir suivi attentivement le cours et se démener un peu, mais c'est parfaitement possible, vous en êtes capables !
Avant de se lancer dans le code, listons les étapes de fonctionnement d'un système de drag & drop :
L'utilisateur
enfonce (et ne relâche pas) le bouton gauche de sa souris sur un élément. Le drag & drop s'initialise alors
en sachant qu'il va devoir gérer le déplacement de cet élément. Pour information, l'événement à utiliser ici estmousedown
.
L'utilisateur, tout en laissant le bouton de sa souris enfoncé, commence à déplacer son curseur, l'élément ciblé
suit alors ses mouvements à la trace. L'événement à utiliser estmousemove
et
nous vous conseillons de l'appliquer à l'élémentdocument
, nous
vous expliquerons pourquoi dans la correction.
L'utilisateur
relâche le bouton de sa souris. Le drag & drop prend alors fin et l'élément ne suit plus le curseur de la
souris. L'événement utilisé estmouseup
.
Alors ? Ça n'a pas l'air si tordu que ça, n'est-ce pas ?
Maintenant que vous savez à peu près ce qu'il faut faire, nous allons vous fournir le code HTML de base ainsi que le CSS, vous éviterez ainsi de vous embêter à faire cela vous-mêmes :
class="draggableBox"1
class="draggableBox"2
class="draggableBox"3
.draggableBox {
position: absolute;
width: 80px; height: 60px;
padding-top: 10px;
text-align: center;
font-size: 40px;
background-color: #222;
color: #CCC;
cursor: move;
}
Juste deux dernières petites choses. Il serait bien :
Que vous utilisiez une IIFE dans laquelle vous allez placer toutes les fonctions et variables nécessaires au bon fonctionnement de votre code, ce sera bien plus propre. Ainsi, votre script n'ira pas polluer l'espace global avec ses propres variables et fonctions ;
Que votre
code ne s'applique pas à tous les<div>
existants mais uniquement à
ceux qui possèdent la classe.draggableBox
.
Sur ce, bon courage !
Vous avez terminé l'exercice ? Nous espérons que vous l'avez réussi, mais si ce n'est pas le cas ce n'est pas grave ! Regardez attentivement la correction, et tout devrait être plus clair.
(function() { // On utilise une IIFE pour ne pas polluer l'espace global
var storage = {}; // Contient l'objet de la div en cours de déplacement
function init() { // La fonction d'initialisation
var elements = document.querySelectorAll('.draggableBox'),
elementsLength = elements.length;
for (var i = 0; i < elementsLength; i++) {
elements[i].addEventListener('mousedown', function(e) { // Initialise le drag & drop
var s = storage;
s.target = e.target;
s.offsetX = e.clientX - s.target.offsetLeft;
s.offsetY = e.clientY - s.target.offsetTop;
});
elements[i].addEventListener('mouseup', function() { // Termine le drag & drop
storage = {};
});
}
document.addEventListener('mousemove', function(e) { // Permet le suivi du drag & drop
var target = storage.target;
if (target) {
target.style.top = e.clientY - storage.offsetY + 'px';
target.style.left = e.clientX - storage.offsetX + 'px';
}
});
}
init(); // On initialise le code avec notre fonction toute prête.
})();
Concernant la
variablestorage
, il ne s'agit que d'un espace de stockage dont nous
allons vous expliquer le fonctionnement au cours de l'étude de la fonctioninit()
.
Commençons !
Notre fonctioninit()
commence
par le code suivant :
var elements = document.querySelectorAll('.draggableBox'),
elementsLength = elements.length;
for (var i = 0; i < elementsLength; i++) {
// Code...
}
Dans ce code, nous avons
volontairement caché les codes d'ajout d'événements, car ce qui nous intéresse c'est cette boucle. Cette boucle
couplée à la méthodequerySelectorAll()
— vous l'avez déjà
vue dans le chapitre sur la manipulation du code HTML — elle permet de parcourir tous les éléments HTML filtrés par
un sélecteur CSS. Dans notre cas, nous parcourons tous les éléments avec la
classe .draggableBox
.
mousedown
etmouseup
Dans notre boucle qui parcourt le code HTML, nous avons deux ajouts d'événements que voici :
elements[i].addEventListener('mousedown', function(e) { // Initialise le drag & drop
var s = storage;
s.target = e.target;
s.offsetX = e.clientX - s.target.offsetLeft;
s.offsetY = e.clientY - s.target.offsetTop;
});
elements[i].addEventListener('mouseup', function() { // Termine le drag & drop
storage = {};
});
Comme vous pouvez le voir, ces deux
événements ne font qu'accéder à la variablestorage
. À quoi nous sert
donc cette variable ? Il s'agit tout simplement d'un objet qui nous sert d'espace de stockage, il permet de
mémoriser l'élément actuellement en cours de déplacement ainsi que la position du curseur par rapport à notre
élément (nous reviendrons sur ce dernier point plus tard).
Bref, dans notre événementmousedown
(qui
initialise le drag & drop), nous ajoutons l'événement ciblé dans la
propriétéstorage.target
puis les positions du curseur par
rapport à notre élément
dansstorage.offsetX
etstorage.offsetY
.
En ce qui concerne notre
événementmouseup
(qui termine le drag & drop), on attribue juste
un objet vide à notre variablestorage
, comme ça tout est vidé !
Jusqu'à présent, notre code ne fait qu'enregistrer dans la
variablestorage
l'élément ciblé pour notre drag & drop. Cependant,
notre but c'est de faire bouger cet élément. Voilà pourquoi notre événementmousemove
intervient
!
document.addEventListener('mousemove', function(e) { // Permet le suivi du drag & drop
var target = storage.target;
if (target) {
target.style.top = e.clientY - storage.offsetY + 'px';
target.style.left = e.clientX - storage.offsetX + 'px';
}
});
Pourquoi notre événement est-il appliqué à
l'élémentdocument
?
Réfléchissons ! Si nous appliquons cet événement à l'élément ciblé, que va-t-il se passer ? Dès que l'on bougera la souris, l'événement se déclenchera et tout se passera comme on le souhaite, mais si je me mets à bouger la souris trop rapidement, le curseur va alors sortir de notre élément avant que celui-ci n'ait eu le temps de se déplacer, ce qui fait que l'événement ne se déclenchera plus tant que l'on ne replacera pas notre curseur sur l'élément. La probabilité pour que cela se produise est plus élevée que l'on ne le pense, autant prendre toutes les précautions nécessaires.
Un
autre problème peut aussi surgir : dans notre code actuel, nous ne gérons pas le style
CSSz-index
, ce qui fait que lorsqu'on déplace le premier élément et que l'on
place notre curseur sur un des deux autres éléments, le premier élément se retrouve alors en dessous d'eux. En quoi
est-ce un problème ? Eh bien si on a appliqué lemousemove
sur notre
élément au lieu dudocument
alors cet événement ne se déclenchera pas
vu que l'on bouge notre curseur sur un des deux autres éléments et non pas sur notre élément en cours de
déplacement.
La solution est donc de mettre
l'événementmousemove
sur notredocument
.
Vu que cet événement se propage aux enfants, nous sommes sûrs qu'il se déclenchera à n'importe quel déplacement du
curseur sur la page.
Le reste du code n'est pas bien sorcier :
On utilise
une condition qui vérifie qu'il existe bien un indicetarget
dans
notre espace de stockage. Si il n'y en a pas c'est qu'il n'y a aucun drag & drop en cours d'exécution.
On assigne à
notre élément cible (target
) ses nouvelles coordonnées par
rapport au curseur.
Alors revenons sur un point important du précédent code : il nous a fallu enregistrer la position du curseur par rapport au coin supérieur gauche de notre élément dès l'initialisation du drag & drop :
Pourquoi ? Car si vous ne le faites pas, à chaque fois que vous déplacerez votre élément, celui-ci placera son bord supérieur gauche sous votre curseur et ce n'est clairement pas ce que l'on souhaite.
Essayez donc par vous-mêmes pour vérifier !
Comme vous l'avez peut-être constaté, il est possible que l'utilisateur sélectionne le texte contenu dans vos éléments déplaçables, cela est un peu aléatoire. Heureusement, il est possible de résoudre simplement ce problème avec quelques propriétés CSS appliquées aux éléments déplaçables :
/* L'utilisateur ne pourra plus sélectionner le texte de l'élément qui possède cette propriété CSS */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
Essayer une adaptation de ce
code
Et voilà pour ce mini-TP, nous espérons qu'il vous a plu !
Pour modifier
les styles CSS d'un élément, il suffit d'utiliser la
propriétéstyle
. Il ne reste plus qu'à accéder à la bonne
propriété CSS, par exemple :element.style.height = '300px'
.
Le nom des
propriétés composées doit s'écrire sans tiret et avec une majuscule pour débuter chaque mot, à l'exception du
premier.
Ainsi,border-radius
devientborderRadius
.
La
fonctiongetComputedStyle()
récupère la valeur de n'importe quelle
propriété CSS. C'est utile, car la propriétéstyle
n'accède pas aux
propriétés définies dans la feuille de style.
Les
propriétés de typeoffset
, au nombre de cinq, permettent de
récupérer des valeurs liées à la taille et au positionnement.
Le
positionnement absolu peut poser des problèmes. Voilà pourquoi il faut savoir utiliser la
propriétéoffsetParent
, combinée aux autres
propriétésoffset
.