Comparaison de deux textareas : l'un sans redimensionnement automatique nécessitant un défilement manuel, l'autre s'ajustant automatiquement à la hauteur du contenu.

Comment redimensionner automatiquement un textarea ?

Récemment, j’ai été confronté à une fonctionnalité intéressante : coder un textarea capable d’ajuster automatiquement sa hauteur en fonction de son contenu. Après avoir exploré plusieurs stratégies, j’ai pu proposer une solution fonctionnelle.
Vous voulez savoir comment redimensionner automatiquement un textarea ? Laissez-moi vous présenter ma réflexion et ma solution.

Comportement par défaut du textarea

Pour rappel, le textarea est un champ qui permet à l’utilisateur de saisir un texte sur plusieurs lignes. Ce champ peut être redimensionné par l’utilisateur grâce à la poignée de déplacement en bas à droite. Lorsque la page est initialisée, et qu’aucun style ne lui est appliqué, le textarea prend automatiquement une hauteur correspondant à la valeur de l’attribut rows valant 2 par défaut.

Lorsque l’utilisateur saisit son texte, la taille du textarea reste fixe et scroll automatiquement dès que le contenu et le curseur sortent de la zone visible du champ. L’utilisateur est donc obligé de redimensionner lui-même le textarea pour pouvoir lire son message en entier.

Ce comportement n’est pas réellement problématique sur les ordinateurs puisque l’utilisateur peut facilement redimensionner le textarea grâce à sa souris. En revanche, lorsqu’il saisit son texte sur un appareil tactile comme les téléphones ou les tablettes, l’expérience est beaucoup moins agréable. Cette contrainte pousse alors de nombreux clients à demander des textarea auto-ajustables.

Gérer l’attribut rows ne mène à rien

Afin de redimensionner automatiquement le textarea, la seule solution viable est d’ajuster sa hauteur grâce à JavaScript dès que son contenu change.

Vous remarquerez que lorsque nous retaillons un textarea, si la hauteur en pixels de celui-ci n’est pas un multiple de la hauteur d’une ligne, la barre de défilement s’affiche et la première et/ou la dernière ligne sont partiellement masquées. Cela est beaucoup moins esthétique qu’une zone de texte parfaitement ajustée. En considérant cette caractéristique, l’idée de travailler avec l’attribut rows peut sembler judicieuse, car le navigateur ajuste automatiquement la taille en fonction de la hauteur d’une ligne du textarea.

Je dis « sembler », car la seule solution pour identifier le nombre de lignes est de compter les caractères \n dans la valeur du textarea. Cette méthode fonctionne tant que la ligne écrite par l’utilisateur est inférieure à la largeur du champ. Dès qu’une ligne est trop longue, le navigateur effectue un retour à la ligne visuel que nous ne pouvons pas détecter dans une string. C’est pourquoi nous devons abandonner cette piste.

Calculer la hauteur du texte saisi

Élaboration de la solution

Pour déterminer la hauteur du contenu du textarea, une autre solution consiste à calculer la hauteur du texte saisi. Grâce à cette stratégie, nous ne rencontrerons plus de difficultés avec le retour automatique à la ligne.

L’attribut scrollHeight des éléments du DOM permet d’évaluer la hauteur de leur contenu, même si une partie est masquée. Cet attribut est particulièrement utile lorsque le navigateur affiche une barre de défilement, comme pour le textarea.

La solution consiste donc à modifier la hauteur du textarea dès que l’utilisateur modifie sa saisie. Pour cela, il est plus simple d’éditer les styles du textarea en utilisant la valeur scrollHeight, qui est exprimée en pixels.

Coder le redimensionnement automatiquement du textarea

Création de la structure de la page Web

Pour développer la solution précédente, nous allons partir d’un template HTML et CSS assez basique. Le code source associé est le suivant :

<!-- /index.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>textarea auto adaptatif</title>

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <section class="container pt-5">
        <h1>Redimensionnement automatique du textarea</h1>

        <div class="mt-4">
            <textarea class="field js--auto-resize"></textarea>
        </div>
    </section>
    <script src="script.js"></script>
</body>
</html>

Ce premier fichier HTML déclare la structure du document. Il contient un textarea auquel nous appliquons deux classes CSS. Ces dernières permettront de :

  • Styliser son apparence ;
  • De le sélectionner plus facilement avec JavaScript.
/* /style.css */
textarea.field {
    width: 100%;

    resize: none;
}

Le code CSS est assez simple, les autres règles sont définies par Bootstrap. Dans ce fichier, nous appliquons une largeur de 100% pour faciliter le dimensionnement responsive, et nous supprimons la possibilité pour l’utilisateur de retailler lui-même le textarea.

Implémentation de la logique de redimensionnement en JavaScript

Nous pouvons maintenant passer à l’écriture du code JavaScript responsable de l’adaptation automatique de la hauteur des textarea :

// /script.js
class AutoResizeElement {
  /**
   * Constructeur.
   * @param {HTMLTextAreaElement} el Le textarea à redimensionner automatiquement.
   */
  constructor(el) {
    this._el = el;

    // Configurer la taille de base
    this._el.rows = 1;
    this._resize();

    el.addEventListener('input', (_) => this._resize()); // <1>
    window.addEventListener('resize', () => this._resize()); // <1>
  }

  /**
   * Redimensionnement automatique du textarea.
   */
  _resize() {
    // Calcul de la hauteur à partir du contenu courant
    this._el.style.height = ''; // <2>
    this._el.style.height = `${this._el.scrollHeight}px`;
  }
}


window.addEventListener('load', () => { // <3>
  // Appliquer le traitement automatique à tous les éléments textarea souhaités
  document.querySelectorAll('textarea.js--auto-resize').forEach(function (el) {
    new AutoResizeElement(el);
  });
});
1

Deux events sont à prendre en compte : input lors de la saisie utilisateur et resize lorsque la taille de la fenêtre change.

2

Cette instruction est obligatoire pour permettre au navigateur de calculer correctement la valeur scrollHeight.

3

En attendant l’événement load, nous nous assurons que le CSS et les autres scripts JavaScript sont correctement chargés.

En fonction du contenu du textarea, la modification de la taille de la fenêtre peut influencer le nombre de retours automatiques à la ligne. Lors du redimensionnement de la fenêtre, l’attribut scrollHeight sera recalculé par le navigateur, et pourra varier. Il est donc important d’ajouter un event listener à l’événement resize pour éviter des tailles incohérentes du textarea.

Améliorer l’expérience utilisateur

Supprimer la barre de défilement

Analyse du bogue

En testant ce code source, vous remarquerez probablement qu’une barre de défilement apparaît dans le textarea. L’affichage de ce widget est lié à la propriété box-sizing qui est définie à border-box par Bootstrap.

La règle border-box permet aux blocs de respecter les règles width et height en prenant en compte le padding et la taille des bordures. Les navigateurs appliquent généralement aux textarea, un padding et une bordure par défaut. Cette règle est donc importante pour éviter le dépassement des champs du bloc parent.

Lors du calcul de l’attribut scrollHeight, la taille des bordures n’est pas prise en compte. Par conséquent, en fixant la height du textarea à la taille du contenu, l’espace disponible pour afficher le texte est taille-du-contenu - taille-des-bordure-top-et-bottom. La barre de défilement apparaît donc pour permettre à l’utilisateur d’accéder à la zone masquée par les bordures.

Calculer la taille des bordures

Une solution que nous retrouvons souvent sur les forums est d’ajouter la règle overflow: hidden aux styles du textarea. Cette solution peut effectivement contourner le problème des bordures lorsque leur taille est faible (par exemple, la taille par défaut ou 1 pixel). Cependant, lorsque la taille des bordures devient trop importante, le texte saisi n’est visible que partiellement et l’utilisateur ne peut plus faire défiler le contenu du textarea. Cette solution est donc mauvaise !

Une meilleure solution est de calculer la taille des bordures top et bottom. Pour cela, nous utilisons les attributs offsetHeight et clientHeight, qui donnent respectivement la hauteur totale de l’élément (incluant les bordures) et la hauteur de l’élément sans les bordures. En soustrayant ces valeurs, nous obtenons ainsi la hauteur des bordures.

 // /script.js
   _resize() {
     // Calcul de la hauteur à partir du contenu courant
     this._el.style.height = '';
-    this._el.style.height = `${this._el.scrollHeight}px`;
+    this._el.style.height = `${this._el.offsetHeight - this._el.clientHeight + this._el.scrollHeight}px`;
   }

Si vous testez de nouveau le comportement des navigateurs, vous verrez que la barre de défilement n’est plus affichée.

Limiter l’amplitude de redimensionnement automatique

La solution précédente est parfaitement fonctionnelle. Cependant, en fonction du design system de votre site ou application Web, les experts UX/UI peuvent imposer d’autres contraintes, telles que la limitation des tailles minimale et maximale du textarea. C’est notamment le cas des text fields Material Design, dont vous pouvez consulter les spécifications sur le site officiel.

Faisons évoluer notre code pour fixer ces limites à l’aide des data attributes.

Calculer précisément la hauteur d’une ligne

Si nous souhaitons préserver une hauteur qui évite la coupure de certaines lignes, il est nécessaire de trouver la hauteur d’une ligne et le padding vertical. Ces hauteurs permettent de définir précisément les hauteurs minimale et maximale du textarea.

 // /script.js
 // ...
   constructor(el) {
     this._el = el;

+    this._yPadding = -1;
+    this._lineHeight = -1;
+    this._configureHeights();
+
     // Configurer la taille de base
     this._el.rows = 1;
     this._resize();

     el.addEventListener('input', (_) => this._resize());
-    window.addEventListener('resize', () => this._resize());
+    window.addEventListener(
+      'resize',
+      () => {
+        this._configureHeights(); <1>
+        this._resize();
+      },
+    );
+  }
+
+  /**
+   * Calcule les hauteurs des lignes et du padding lorsque c'est nécessaire.
+   */
+  _configureHeights() {
+    /** @type {HTMLTextAreaElement} */
+    const clone = this._el.cloneNode();
+    clone.rows = 1;
+
+    // Configurer l'élément comme invisible
+    clone.style.position = 'absolute';
+    clone.style.visibility = 'hidden';
+
+    // Supprimer les espacements verticaux parasites (sauf padding)
+    clone.style.border = 'none';
+    clone.style.height = '';
+    clone.style.minHeight = '';
+    clone.style.maxHeight = '';
+
+    if (this._el.parentNode) {
+      this._el.parentNode.appendChild(clone); <2>
+
+      const height = clone.clientHeight;
+
+      clone.style.padding = '0';
+      this._lineHeight = clone.clientHeight;
+      this._yPadding = height - this._lineHeight;
+    }
+
+    clone.remove(); <3>
+
+    console.log(this._lineHeight, this._yPadding);
   }
 // ...
1

Le calcul des valeurs n’est pas toujours nécessaire lors du resize, mais cette instruction est nécessaire pour les pages réactives qui adaptent la hauteur des lignes selon la largeur de la fenêtre.

2

En ajoutant l’élément dans l’arbre DOM, le navigateur peut calculer la valeur du clientHeight.

3

Ne pas oublier de supprimer l’élément, car il n’est plus utile.

En rafraîchissant la page, j’obtiens le message "24 4", ce qui correspond aux valeurs affichées par l’inspecteur d’élément du DevTools (arrondies à l’entier).

Fixer les hauteurs minimales et maximales à partir des data attributes

Définir les data attributes

Les data attributes sont des attributs des balises HTML qui respectent le format data-*. Ils servent à stocker des informations supplémentaires en plus des attributs standards. Ces données sont ensuite accessibles via JavaScript grâce à l’attribut dataset de l’élément du DOM.

Afin de configurer les hauteurs minimale et maximale, nous allons déclarer les data attributes suivants :

  • data-rows-min : nombre de lignes minimales du textarea, qui a pour valeur par défaut 1.
  • data-rows-max : le nombre de lignes maximales du textarea, qui a pour valeur par défaut 4.

Modifions le template HTML pour tester deux textarea, un avec les valeurs par défaut et l’autre avec les data attributes définis :

 // /index.html
 // ...
         <div class="mt-4">
             <textarea class="field js--auto-resize"></textarea>
         </div>
+        <div class="mt-3"></div>
+            <textarea class="field js--auto-resize" data-rows-min="2" data-rows-max="10"></textarea>
+        </div>
     </section>
 // ...

Appliquer les contraintes avec JavaScript

La dernière étape consiste à modifier le comportement JavaScript pour configurer les styles min-height et max-height en fonction des data attributes :

 // /script.js
 // ...
     this._configureHeights();

     // Configurer la taille de base
-    this._el.rows = 1;
     this._resize();

     el.addEventListener('input', (_) => this._resize());
 // ...

     clone.remove();

-    console.log(this._lineHeight, this._yPadding);
+    this._setHeightRange(); <1>
+  }
+
+  /**
+   * Configure l'attribut rows et la hauteur min et max du textarea.
+   */
+  _setHeightRange() {
+    // Récupération des paramètres
+    const min = Number.parseInt(this._el.dataset.rowsMin || '1');
+    const max = Number.parseInt(this._el.dataset.rowsMax || '4');
+
+    if (isNaN(min) || isNaN(max)) {
+      throw new Error("'data-rows-min' and 'data-rows-max' data attributes must be integers");
+    }
+
+    const borderWidth = this._el.offsetHeight - this._el.clientHeight; <2>
+
+    // Appliquer les règles
+    this._el.rows = min;
+    this._el.style.minHeight = `${borderWidth + this._yPadding + this._lineHeight * min}px`;
+    this._el.style.maxHeight = `${borderWidth + this._yPadding + this._lineHeight * max}px`;
   }
1

Après le calcul de la nouvelle hauteur de ligne et du padding vertical, nous modifions les contraintes de hauteur.

2

Le calcul de la taille des bordures est également nécessaire ici pour éviter le problème avec la règle CSS box-sizing.

En rafraîchissant la page, vous verrez que la hauteur des textarea vides correspond à la configuration définie. Par ailleurs, en saisissant un texte suffisamment long, le champ s’arrête à sa hauteur maximale. L’objectif est donc atteint !

Conclusion

En conclusion, le redimensionnement automatique des textarea en fonction de leur contenu permet d’améliorer l’expérience utilisateur, notamment sur les appareils tactiles. La meilleure stratégie est d’utiliser l’attribut scrollHeight pour ajuster la hauteur du textarea en temps réel. Cependant, en fonction des styles CSS appliqués, nous devons réajuster nos calculs (dans mon cas, il faut prendre en compte la taille des bordures).

Nous pouvons également aller plus loin en fixant des limites de redimensionnement via les data attributes et les styles min-height et max-height. Cela assure une présentation esthétique, qui respecte le design system utilisé. Ainsi, grâce à cette approche, nous répondons aux attentes des utilisateurs et des clients.

N’hésitez pas à tester cette solution dans vos projets et à consulter d’autres articles pour approfondir vos connaissances sur l’optimisation de l’expérience utilisateur !

Laisser un commentaire