Valentin Dupas

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

Styles ✨

Maintenant qu'on a vu le chapitre 3, on devrait être en capacité de voir pleins du trucs intéressants.

Si jamais vous sentez que vous avez du mal dans les prochains chapitres, je vous encourage à relire le chapitre 3, noter des questions, et de tester vos idées en esseyant d'exécuter du code qui correspond aux idées que vous vous faites. C'est 100% ok d'exécuter du code pourri, au pire vous ferez planter l'onglet de votre navigateur. (Attention, copier/coller du code que vous ne comprennez pas depuis un autre site est autrement plus dangereux!)

On a vu comment manipuler le contenu textuel d'un élément via Element.textContent et ce chapitre-ci on va voir comment manipuler le style d'un élément.

Element.classList

En javascript, les éléments de la page possèdent une propriété classList qui elle même possède quelques fonctions pour vous aider à manipuler les classes d'un élément.

Element.classList.add()

Cette fonction prend une string en paramètre et ajoute cette string en tant que classe à l'élément.

exemple:

css

.danger {
  color: #f54254;
}

html

<h1>Pas de classe ... et pourtant!</h1>

js

document.querySelector("h1").classList.add("danger");

Si vous essayez ceci vous devriez avoir un titre rouge, bien qu'il n'ai pas de classe dans le fichier HTML. La classe étant ajoutée lorsque le code javascript s'exécute.

Element.classList.remove()

Cette fonction prend une string en paramètre et retire la classe correspondant à cette string (si elle fait partie de ses classes).

exemple:

css

.danger {
  color: #f54254;
}

html

<h1 class="danger">Une classe ... et pourtant!</h1>

js

document.querySelector("h1").classList.remove("danger");

Si vous essayez ceci vous devriez avoir un titre de la couleur par défaut(noir). Bien qu'il y ai une classe dans le fichiers HTML. La classe est retirée lorsque le code javascript s'exécute.

Notez que pendant un très bref moment, entre la création de l'élément HTML et l'exécution du JS, le titre est rouge.

Element.classList.contains()

Cette fonction nous rend un booléen indiquant si l'elément à la classe correspondant à la string fournie en paramètre.

exemple:

css

.danger {
  color: #f54254;
}

html

<h1 class="danger">I'm red da ba di da ba da</h1>

js

const heading = document.querySelector("h1");

const hasDanger = heading.classList.contains("danger");
const hasPrimary = heading.classList.contains("primary");

console.log(hasDanger); // true
console.log(hasPrimary); // false

Example: dark mode

Voici un petit example plus concret. Que vous pouvez visiter ici.

<!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;

        display: grid;
        place-items: center;
        color: #222323;
        background-color: #f0f6f0;
      }
      body.dark {
        color: #f0f6f0;
        background-color: #222323;
      }

      main {
        max-width: 80ch;
      }
    </style>
  </head>
  <body>
    <main>
      <label for="dark">Dark mode</label>
      <input name="dark" type="checkbox" />
      <div>
        <p>du texte</p>
      </div>
    </main>

    <script>
      // Ici le sélecteur veux dire "l'input qui a un `name` egal à `dark`".
      // Au sein d'un <form> les inputs sont référencé via leur `name` plutôt que leur `id`
      // ce qui fait que je n'ai pas besoin de gérer un `id` ET un `name` qui seraient les mêmes et ayant la même utilité.
      const darkInput = document.querySelector('input[name="dark"]');

      // On remarque ici que l'on se base sur l'événement "change" plutôt que "click"
      // cet événement se produit lorsque l'input change de valeur
      // donc si notre checkbox change on mettera à jour le thème
      darkInput.addEventListener("change", updateTheme);

      function updateTheme() {
        // on regarde si la propriété `checked` de notre checkbox est vraie
        if (darkInput.checked) {
          // si elle est vraie alors on ajoute la classe "dark" au body
          document.querySelector("body").classList.add("dark");
        } else {
          // sinon on retire la classe "dark" au body
          document.querySelector("body").classList.remove("dark");
        }
      }
    </script>
  </body>
</html>

Remarquez que darkInput.checked est particulier, presque tous les éléments <input> ont leur valeur dans la propriété .value sauf les checkbox et les boutons radio pour lesquels il faut regarder dans .checked.

Problème: Todo card

Mettez le bout d'HTML ci-dessous dans une page. Faites en sorte que le texte de ma todo card soit désaturé et rayé quand la checkbox est cochée, mais ne le soit pas lorsque la checkbox est décochée. Vous pouvez rajouter des choses tel que des classes et/ou des id à ce bout d'HTML si vous pensez que c'est nécassaire.

<article>
  <h2>Aller faire les courses</h2>
  <p>et ne pas oublier le PQ!</p>
  <input type="checkbox" />
</article>
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;

        display: grid;
        place-items: center;

        font-family: system-ui, sans-serif;
      }

      .done {
        text-decoration: line-through;
        color: grey;
      }
    </style>
  </head>
  <body>
    <article>
      <h2>Aller faire les courses</h2>
      <p>et ne pas oublier le PQ!</p>
      <input type="checkbox" />
    </article>

    <script>
      // je ne suis pas satisfait de ces sélecteurs
      // ils ne sont pas assez expressifs, mais tant pis
      // c'est le problème des exemples trop simples
      const card = document.querySelector("article");
      const checkbox = document.querySelector("input");

      checkbox.addEventListener("change", updateCard);

      function updateCard() {
        if (checkbox.checked) {
          card.classList.add("done");
        } else {
          card.classList.remove("done");
        }
      }
    </script>
  </body>
</html>

Element.style

Tout ça c'est bien marrant, mais on ne va pas créer une classe à chaque fois qu'on veux changer quelque chose, et puis comment on ferait si on voulait changer le style de quelque chose en fonction du choix de l'utilisateur? Comment je fais pour laisser l'utilisateur choisir la taille de sa police? Je ne vais quand même pas écrire autant de classes qu'il y a de tailles de police possible 😂.

Chaque élément a une propriété Element.style qui est un objet ayant pour propriétés toutes les règles CSS qu'on pourrait lui appliquer. Donner une valeur à une propriété de Element.style revient au même que de lui donner une valeur avec du style inline.

Autrement dit, les deux exemples suivants produisent exactement le même résultat. C'est important à noter pour le principe de spécificité de CSS parce que du style inline est plus spécifique que la plupart des sélécteurs CSS.

<p>Du texte</p>
<script>
  document.querySelector("p").style.backgroundColor = "red";
</script>
<p style="background-color: red;">Du texte</p>

Et c'est une bonne chose, comme ça vous pouvez fournir un style par défaut dans une balise <style> ou un fichier CSS et l'écraser plus tard avec les propriété fournies par les JS. (sauf si vous utilisez !important dans le CSS, auquel cas, bon courage 🙈)

À noter que l'on ne peut pas mettre d'espace ou de tiret dans un nom de propriété donc elles ne sont pas écrites en "kabab-case" comme en CSS mais en "camelCase". Ce qui fait que background-color en CSS devient backgroundColor en JS.

Si vous avez un doute vous pouvez toujours logger Element.style pour avoir la liste alphabetique de toutes ses propriétés dans votre console.

<!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>
      *{
        box-sizing: border-box;
      }

      body {
        margin: 0;
        padding: 24px;
        min-height: 100vh;

        display: grid;
        place-items: center;
      }

      main {
        max-width: 80ch;
      }

      #font-control > * {
        vertical-align: middle;
      }
    </style>
  </head>
  <body>
    <main>
      <div id="font-control">
        <label for="font-size">font-size</label>
        <input
          name="font-size"
          type="range"
          min="8"
          max="64"
          step="1"
          value="16"
        />
        <output for="font-size"></output>
      </div>
      <div><!-- http://www.catipsum.com/index.php -->
        <p>
          Sit on human lick sellotape yet loves cheeseburgers thinking longingly
          about tuna brine car rides are evil for have secret plans spit up on
          light gray carpet instead of adjacent linoleum. Run off table persian
          cat jump eat fish. Adventure always drink from the toilet.
        </p>
      </div>
    </main>

    <script>
      const fontSizeInput = document.querySelector("input");

      // ici vous pouvez essayer la différence entre l'événement "change" et "input"
      // change => on confirme une nouvelle valeur
      // input => on est en train d'éditer notre nouvelle valeur
      fontSizeInput.addEventListener("change", upadateFontSize);
      fontSizeInput.addEventListener("input", upadateFontSizeOutput);

      // on met à jour l'<output> et la font-size du body
      upadateFontSize();
      upadateFontSizeOutput();

      function upadateFontSize() {
        document.querySelector("body").style.fontSize = fontSizeInput.value + "px";
      }

      function upadateFontSizeOutput() {
        document.querySelector("output").value = fontSizeInput.value + "px";
      }
    </script>
  </body>
</html>

Exercice <input type="color">

  1. Dans une page HTML vierge, mettez un <input type="color">. Loggez la valeur de cet input quand il change (vous êtes libres d'utiliser l'événement input ou change et vous êtes encouragés à essayer les deux).

Comment se présente la valeur de cet input?

Réponse

Il s'agit d'une string sous forme hexadecimale, les deux premiers chiffres correspondant au canal rouge, les deux du milieu à vert et les deux derniers à bleu.

Code
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <input type="color" />

    <script>
      const colorInput = document.querySelector("input");
      colorInput.addEventListener("input", handleBackgroundColorChange);

      function handleBackgroundColorChange() {
        console.log(colorInput.value);
      }
    </script>
  </body>
</html>

  1. Ajoutez un boutton et donnez lui en couleur de fond la valeur de l'input couleur.
Code
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button>Mon bouton</button>
    <input type="color" />

    <script>
      const colorInput = document.querySelector("input");
      colorInput.addEventListener("input", handleBackgroundColorChange);

      function handleBackgroundColorChange() {
        document.querySelector("button").style.backgroundColor =
          colorInput.value;
      }
    </script>
  </body>
</html>

Problème

Reproduisez l'exemple "no code" de la présentation.

Lien vers l'exemple en question

Les seuls type d'input qui pourrait vous manquer c'est les <input type="number"> utilisé pour les différents paddings et <input type="text"> qui sert à changer le contenu textuel du bouton.

La mise en page ne doit pas nécessairement être la même, mais les fonctionnalitée doivent être équivalentes.

Note: Si l'envie vous en prend de vouloir ajouter des features à cet outils:

  1. sachez que ça me rendrait extrêmement heureux
  2. ne le faites pas, avancez dans le cours et gardez cette energie pour le projet final (ou revenez-y si vous avez vraiment torché tout le reste du cours)
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;

        font-family: system-ui, sans-serif;
        font-weight: normal;
      }

      main {
        min-height: 100vh;

        display: flex;
        align-items: center;
        justify-content: center;
      }

      aside {
        position: absolute;
        top: 0;
        right: 0;

        max-height: 300px;
        overflow: auto;

        padding: 1rem 1rem 0 0;
      }
    </style>
  </head>
  <body>
    <aside>
      <fieldset>
        <label for="name">Text:</label>
        <input name="name" type="text" />
      </fieldset>
      <fieldset>
        <label for="name">Text Size:</label>
        <input name="font-size" type="range" min="1" max="100" step="0.1" />
      </fieldset>
      <fieldset>
        <label for="border-radius">Border radius:</label>
        <input
          name="border-radius"
          type="range"
          value="0"
          min="0"
          max="200"
          step="0.1"
        />
      </fieldset>
      <fieldset>
        <label for="background">Background:</label>
        <input name="background" type="color" />
      </fieldset>
      <fieldset>
        <legend>Padding</legend>
        <label for="top-padding">Top:</label>
        <input name="top-padding" type="number" />
        <label for="right-padding">Right:</label>
        <input name="right-padding" type="number" />
        <label for="bottom-padding">Bottom:</label>
        <input name="bottom-padding" type="number" />
        <label for="left-padding">Left:</label>
        <input name="left-padding" type="number" />
      </fieldset>
    </aside>
    <main><button>Button</button></main>

    <script>
      const nameInput = document.querySelector('input[name="name"]');
      const fontSizeInput = document.querySelector('input[name="font-size"]');
      const borderRadiusInput = document.querySelector(
        'input[name="border-radius"]'
      );
      const backgroundColorInput = document.querySelector(
        'input[name="background"]'
      );

      const paddingTopInput = document.querySelector(
        'input[name="top-padding"]'
      );
      const paddingRightInput = document.querySelector(
        'input[name="right-padding"]'
      );
      const paddingBottomInput = document.querySelector(
        'input[name="bottom-padding"]'
      );
      const paddingLeftInput = document.querySelector(
        'input[name="left-padding"]'
      );

      const button = document.querySelector("button");

      nameInput.addEventListener("input", () => {
        button.textContent = nameInput.value;
      });

      fontSizeInput.addEventListener("input", () => {
        button.style.fontSize = fontSizeInput.value + "px";
      });

      borderRadiusInput.addEventListener("input", () => {
        button.style.borderRadius = borderRadiusInput.value + "px";
      });

      backgroundColorInput.addEventListener("input", () => {
        button.style.backgroundColor = backgroundColorInput.value;
      });

      paddingTopInput.addEventListener("input", () => {
        button.style.paddingTop = paddingTopInput.value + "px";
      });
      paddingRightInput.addEventListener("input", () => {
        button.style.paddingRight = paddingRightInput.value + "px";
      });
      paddingBottomInput.addEventListener("input", () => {
        button.style.paddingBottom = paddingBottomInput.value + "px";
      });
      paddingLeftInput.addEventListener("input", () => {
        button.style.paddingLeft = paddingLeftInput.value + "px";
      });
    </script>
  </body>
</html>