Une table des matières parfaite avec HTML + CSS PlatoBlockchain Data Intelligence. Recherche verticale. Aï.

Une table des matières parfaite avec HTML + CSS

Plus tôt cette année, j'ai auto-publié un ebook intitulé Comprendre les promesses JavaScript (gratuit en téléchargement). Même si je n'avais aucune intention d'en faire un livre imprimé, suffisamment de personnes se sont renseignées sur une version imprimée que j'ai décidé de publier également. J'ai pensé que ce serait un exercice facile en utilisant HTML et CSS pour générer un PDF, puis l'envoyer à l'imprimante. Ce que je n'avais pas réalisé, c'est que je n'avais pas de réponse à une partie importante d'un livre imprimé : la table des matières.

La composition d'une table des matières

À la base, une table des matières est assez simple. Chaque ligne représente une partie d'un livre ou d'une page Web et indique où vous pouvez trouver ce contenu. Généralement, les lignes contiennent trois parties :

  1. Le titre du chapitre ou de la section
  2. Les lignes de repère (c'est-à-dire les points, tirets ou lignes) qui relient visuellement le titre au numéro de page
  3. Le numéro de page

Une table des matières est facile à générer à l'intérieur d'outils de traitement de texte comme Microsoft Word ou Google Docs, mais comme mon contenu était en Markdown puis transformé en HTML, ce n'était pas une bonne option pour moi. Je voulais quelque chose d'automatisé qui fonctionnerait avec HTML pour générer la table des matières dans un format adapté à l'impression. Je voulais également que chaque ligne soit un lien afin qu'elle puisse être utilisée dans des pages Web et des PDF pour naviguer dans le document. Je voulais aussi des points de repère entre le titre et le numéro de page.

Et donc j'ai commencé à faire des recherches.

Je suis tombé sur deux excellents articles de blog sur la création d'une table des matières avec HTML et CSS. Le premier était "Créer une table des matières à partir de votre HTML" par Julie Blanc. Julie a travaillé sur PagedJS, un polyfill pour les fonctionnalités multimédias paginées manquantes dans les navigateurs Web qui formate correctement les documents pour l'impression. J'ai commencé avec l'exemple de Julie, mais j'ai trouvé que cela ne fonctionnait pas tout à fait pour moi. Ensuite, j'ai trouvé celui de Christoph Grabo « Lignes de repère TOC réactives avec CSS » post, qui a introduit le concept d'utilisation de CSS Grid (par opposition à l'approche flottante de Julie) pour faciliter l'alignement. Encore une fois, cependant, son approche n'était pas tout à fait adaptée à mes besoins.

Après avoir lu ces deux articles, cependant, j'ai senti que j'avais une assez bonne compréhension des problèmes de mise en page pour me lancer moi-même. J'ai utilisé des éléments des deux articles de blog et j'ai ajouté de nouveaux concepts HTML et CSS à l'approche pour obtenir un résultat qui me satisfait.

Choisir le bon balisage

Lorsque j'ai décidé du balisage correct pour une table des matières, j'ai pensé principalement à la sémantique correcte. Fondamentalement, une table des matières concerne un titre (chapitre ou sous-section) lié à un numéro de page, presque comme une paire clé-valeur. Cela m'a conduit à deux options :

  • Une option consiste à utiliser une table (<table>) avec une colonne pour le titre et une colonne pour la page.
  • Ensuite, il y a la liste de définitions souvent inutilisée et oubliée (<dl>) élément. Il agit également comme une carte clé-valeur. Donc, encore une fois, la relation entre le titre et le numéro de page serait évidente.

L'une ou l'autre de ces options semblait être une bonne option jusqu'à ce que je réalise qu'elles ne fonctionnent vraiment que pour les tables des matières à un seul niveau, à savoir uniquement si je voulais avoir une table des matières avec uniquement des noms de chapitre. Si je voulais afficher des sous-sections dans la table des matières, cependant, je n'avais pas de bonnes options. Les éléments de table ne sont pas parfaits pour les données hiérarchiques, et bien que les listes de définitions puissent techniquement être imbriquées, la sémantique ne semblait pas correcte. Alors, je suis retourné à la planche à dessin.

J'ai décidé de m'appuyer sur l'approche de Julie et d'utiliser une liste ; cependant, j'ai opté pour une liste ordonnée (<ol>) au lieu d'une liste non ordonnée (<ul>). Je pense qu'une liste ordonnée est plus appropriée dans ce cas. Une table des matières représente une liste de chapitres et de sous-titres dans l'ordre dans lequel ils apparaissent dans le contenu. L'ordre est important et ne doit pas se perdre dans le balisage.

Malheureusement, utiliser une liste ordonnée signifie perdre la relation sémantique entre le titre et le numéro de page, donc ma prochaine étape était de rétablir cette relation dans chaque élément de la liste. Le moyen le plus simple de résoudre ce problème consiste simplement à insérer le mot "page" avant le numéro de page. De cette façon, la relation du nombre par rapport au texte est claire, même sans aucune autre distinction visuelle.

Voici un squelette HTML simple qui a constitué la base de mon balisage :

<ol class="toc-list"> <li> <a href="#link_to_heading"> <span class="title">Chapter or subsection title</span> <span class="page">Page 1</span> </a> <ol> <!-- subsection items --> </ol> </li>
</ol>

Appliquer des styles à la table des matières

Une fois que j'avais établi le balisage que je prévoyais d'utiliser, l'étape suivante consistait à appliquer certains styles.

Tout d'abord, j'ai supprimé les numéros générés automatiquement. Vous pouvez choisir de conserver les numéros générés automatiquement dans votre propre projet si vous le souhaitez, mais il est courant que les livres aient des avant-propos et des postfaces non numérotés inclus dans la liste des chapitres, ce qui rend les numéros générés automatiquement incorrects.

Pour mon but, je remplirais les numéros de chapitre manuellement, puis j'ajusterais la mise en page afin que la liste de niveau supérieur n'ait pas de remplissage (alignant ainsi avec les paragraphes) et chaque liste intégrée soit en retrait de deux espaces. J'ai choisi d'utiliser un 2ch valeur de rembourrage parce que je n'étais toujours pas sûr de la police que j'utiliserais. La ch L'unité de longueur permet au rembourrage d'être relatif à la largeur d'un caractère - quelle que soit la police utilisée - plutôt qu'à une taille de pixel absolue qui pourrait finir par sembler incohérente.

Voici le CSS avec lequel j'ai fini:

.toc-list, .toc-list ol { list-style-type: none;
} .toc-list { padding: 0;
} .toc-list ol { padding-inline-start: 2ch;
}

Sara Soueidan m'a fait remarquer que les navigateurs WebKit suppriment la sémantique des listes lorsque list-style-type is none, j'ai donc dû ajouter role="list" dans le HTML pour le conserver :

<ol class="toc-list" role="list"> <li> <a href="#link_to_heading"> <span class="title">Chapter or subsection title</span> <span class="page">Page 1</span> </a> <ol role="list"> <!-- subsection items --> </ol> </li>
</ol>
CodePen Intégrer la solution de secours

Styliser le titre et le numéro de page

Avec la liste stylisée à mon goût, il était temps de passer au style d'un élément de liste individuel. Pour chaque élément de la table des matières, le titre et le numéro de page doivent être sur la même ligne, avec le titre à gauche et le numéro de page aligné à droite.

Vous pensez peut-être : "Pas de problème, c'est à ça que sert flexbox !" Vous n'avez pas tort ! Flexbox peut en effet obtenir le bon alignement de la page de titre. Mais il y a quelques problèmes d'alignement délicats lorsque les leaders sont ajoutés, j'ai donc plutôt opté pour l'approche de Christoph en utilisant une grille, ce qui en prime car cela aide également avec les titres multilignes. Voici le CSS pour un élément individuel :

.toc-list li > a { text-decoration: none; display: grid; grid-template-columns: auto max-content; align-items: end;
} .toc-list li > a > .page { text-align: right;
}

La grille comporte deux colonnes, dont la première est auto-dimensionné pour remplir toute la largeur du conteneur, moins la deuxième colonne, qui est dimensionnée pour max-content. Le numéro de page est aligné à droite, comme il est traditionnel dans une table des matières.

La seule autre modification que j'ai apportée à ce stade a été de masquer le texte "Page". Ceci est utile pour les lecteurs d'écran mais inutile visuellement, j'ai donc utilisé un traditionnel visually-hidden classe pour le cacher de la vue:

.visually-hidden { clip: rect(0 0 0 0); clip-path: inset(100%); height: 1px; overflow: hidden; position: absolute; width: 1px; white-space: nowrap;
}

Et, bien sûr, le HTML doit être mis à jour pour utiliser cette classe :

<ol class="toc-list" role="list"> <li> <a href="#link_to_heading"> <span class="title">Chapter or subsection title</span> <span class="page"><span class="visually-hidden">Page</span> 1</span> </a> <ol role="list"> <!-- subsection items --> </ol> </li>
</ol>

Avec cette fondation en place, je suis passé à l'adresse des dirigeants entre le titre et la page.

CodePen Intégrer la solution de secours

Création de points de repère

Les leaders sont si courants dans la presse écrite que vous vous demandez peut-être pourquoi CSS ne le prend pas déjà en charge ? La réponse est: Cela fait. Bon type de.

Il y a en fait un leader() fonction définie dans le Spécification du contenu généré par CSS pour les médias paginés. Cependant, comme pour la plupart des spécifications des médias paginés, cette fonction n'est implémentée dans aucun navigateur, ce qui l'exclut en option (du moins au moment où j'écris ceci). Ce n'est même pas répertorié sur caniuse.com, probablement parce que personne ne l'a mis en œuvre et qu'il n'y a aucun plan ou signal indiquant qu'ils le feront.

Heureusement, Julie et Christoph ont déjà abordé ce problème dans leurs messages respectifs. Pour insérer les points de repère, ils ont tous deux utilisé un ::after pseudo-élément avec son content propriété définie sur une très longue chaîne de points, comme ceci :

.toc-list li > a > .title { position: relative; overflow: hidden;
} .toc-list li > a .title::after { position: absolute; padding-left: .25ch; content: " . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . "; text-align: right;
}

Les ::after pseudo-element est défini sur une position absolue pour le sortir du flux de la page et éviter de passer à d'autres lignes. Le texte est aligné à droite car nous voulons que les derniers points de chaque ligne correspondent au nombre à la fin de la ligne. (Plus sur les complexités de ceci plus tard.) .title l'élément est défini pour avoir une position relative de sorte que le ::after le pseudo-élément ne sort pas de sa boîte. Pendant ce temps, le overflow est caché afin que tous ces points supplémentaires soient invisibles. Le résultat est une jolie table des matières avec des points de repère.

Cependant, il y a autre chose qui doit être pris en considération.

Sara m'a également fait remarquer que tous ces points comptent comme du texte pour les lecteurs d'écran. Alors qu'entendez-vous ? "Introduction point point point point..." jusqu'à ce que tous les points soient annoncés. C'est une expérience horrible pour les utilisateurs de lecteurs d'écran.

La solution consiste à insérer un élément supplémentaire avec aria-hidden ajuster à true puis utilisez cet élément pour insérer les points. Donc le HTML devient :

<ol class="toc-list" role="list"> <li> <a href="#link_to_heading"> <span class="title">Chapter or subsection title<span class="leaders" aria-hidden="true"></span></span> <span class="page"><span class="visually-hidden">Page</span> 1</span> </a> <ol role="list"> <!-- subsection items --> </ol> </li>
</ol>

Et le CSS devient :

.toc-list li > a > .title { position: relative; overflow: hidden;
} .toc-list li > a .leaders::after { position: absolute; padding-left: .25ch; content: " . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . "; text-align: right;
}

Désormais, les lecteurs d'écran ignoreront les points et épargneront aux utilisateurs la frustration d'écouter plusieurs points annoncés.

CodePen Intégrer la solution de secours

La touche finale

À ce stade, le composant de la table des matières semble plutôt bon, mais il pourrait nécessiter un travail de détail mineur. Pour commencer, la plupart des livres compensent visuellement les titres des chapitres des titres des sous-sections, j'ai donc mis les éléments de niveau supérieur en gras et introduit une marge pour séparer les sous-sections des chapitres suivants :

.toc-list > li > a { font-weight: bold; margin-block-start: 1em;
}

Ensuite, je voulais nettoyer l'alignement des numéros de page. Tout semblait correct lorsque j'utilisais une police à largeur fixe, mais pour les polices à largeur variable, les points de repère pouvaient finir par former un motif en zigzag lorsqu'ils s'ajustaient à la largeur d'un numéro de page. Par exemple, tout numéro de page avec un 1 serait plus étroit que les autres, ce qui entraînerait des points de repère mal alignés avec les points des lignes précédentes ou suivantes.

Chiffres et points mal alignés dans une table des matières.
Une table des matières parfaite avec HTML + CSS

Pour résoudre ce problème, j'ai mis font-variant-numeric à tabular-nums donc tous les nombres sont traités avec la même largeur. En définissant également la largeur minimale sur 2ch, je me suis assuré que tous les nombres à un ou deux chiffres sont parfaitement alignés. (Vous voudrez peut-être le régler sur 3ch si votre projet a plus de 100 pages.) Voici le CSS final pour le numéro de page :

.toc-list li > a > .page { min-width: 2ch; font-variant-numeric: tabular-nums; text-align: right;
}
Points de repère alignés dans une table des matières.
Une table des matières parfaite avec HTML + CSS

Et avec ça, la table des matières est complète !

CodePen Intégrer la solution de secours

Conclusion

Créer une table des matières avec rien d'autre que du HTML et du CSS a été plus difficile que prévu, mais je suis très satisfait du résultat. Non seulement cette approche est suffisamment flexible pour accueillir les chapitres et les sous-sections, mais elle gère bien les sous-sous-sections sans mettre à jour le CSS. L'approche globale fonctionne sur les pages Web où vous souhaitez créer un lien vers les différents emplacements de contenu, ainsi que sur les fichiers PDF dans lesquels vous souhaitez que la table des matières soit liée à différentes pages. Et bien sûr, il a également fière allure sur papier si vous avez envie de l'utiliser dans une brochure ou un livre.

J'aimerais remercier Julie Blanc et Christoph Grabo pour leurs excellents articles de blog sur la création d'une table des matières, car les deux ont été inestimables lorsque j'ai commencé. J'aimerais également remercier Sara Soueidan pour ses commentaires sur l'accessibilité pendant que je travaillais sur ce projet.


Une table des matières parfaite avec HTML + CSS publié à l'origine le Astuces CSS. Vous devriez recevoir le bulletin.

Horodatage:

Plus de Astuces CSS