Un cuprins perfect cu HTML + CSS PlatoBlockchain Data Intelligence. Căutare verticală. Ai.

Un cuprins perfect cu HTML + CSS

La începutul acestui an, am autopublicat o carte electronică numită Înțelegerea promisiunilor JavaScript (gratuit pentru descărcare). Chiar dacă nu aveam nicio intenție să o transform într-o carte tipărită, destui oameni au contactat întrebându-se despre o versiune tipărită și am decis să o public și pe aceasta. M-am gândit că ar fi un exercițiu ușor de a folosi HTML și CSS. generați un PDF și apoi trimiteți-l la imprimantă. Ceea ce nu mi-am dat seama a fost că nu aveam un răspuns la o parte importantă a unei cărți tipărite: cuprinsul.

Alcătuirea unui cuprins

În esență, un cuprins este destul de simplu. Fiecare linie reprezintă o parte a unei cărți sau a unei pagini web și indică unde puteți găsi acel conținut. De obicei, liniile conțin trei părți:

  1. Titlul capitolului sau al secțiunii
  2. Lideri (adică acele puncte, liniuțe sau linii) care leagă vizual titlul de numărul paginii
  3. Numărul paginii

Un cuprins este ușor de generat în instrumentele de procesare a textului, cum ar fi Microsoft Word sau Google Docs, dar pentru că conținutul meu era în Markdown și apoi transformat în HTML, aceasta nu a fost o opțiune bună pentru mine. Am vrut ceva automat care să funcționeze cu HTML pentru a genera cuprinsul într-un format potrivit pentru tipărire. De asemenea, am vrut ca fiecare linie să fie un link, astfel încât să poată fi folosită în pagini web și PDF-uri pentru a naviga prin document. Am vrut, de asemenea, puncte lideri între titlu și numărul paginii.

Și așa am început să cercetez.

Am dat peste două postări excelente pe blog despre crearea unui cuprins cu HTML și CSS. Primul a fost „Construiți un cuprins din HTML” de Julie Blanc. Julie a lucrat PagedJS, un polyfill pentru caracteristicile media paginate lipsă din browserele web care formatează corect documentele pentru imprimare. Am început cu exemplul lui Julie, dar am constatat că nu prea merge pentru mine. Apoi, l-am găsit pe Christoph Grabo „Linii de lider TOC receptive cu CSS” post, care a introdus conceptul de utilizare a CSS Grid (spre deosebire de abordarea pe bază de float a lui Julie) pentru a face alinierea mai ușoară. Încă o dată, însă, abordarea lui nu a fost tocmai potrivită pentru scopurile mele.

După ce am citit aceste două postări, totuși, am simțit că am înțeles suficient de bine problemele de aspect pentru a mă apuca de mine. Am folosit piese din ambele postări de blog, precum și am adăugat câteva concepte HTML și CSS noi în abordare pentru a obține un rezultat de care sunt mulțumit.

Alegerea marcajului corect

Când am decis cu privire la marcajul corect pentru un cuprins, m-am gândit în primul rând la semantica corectă. În principiu, un cuprins se referă la un titlu (capitol sau subsecțiune) legat de un număr de pagină, aproape ca o pereche cheie-valoare. Asta m-a condus la două variante:

  • O opțiune este să utilizați un tabel (<table>) cu o coloană pentru titlu și o coloană pentru pagină.
  • Apoi există lista de definiții adesea nefolosită și uitată (<dl>) element. De asemenea, acționează ca o hartă cheie-valoare. Deci, încă o dată, relația dintre titlu și numărul paginii ar fi evidentă.

Oricare dintre acestea mi s-a părut opțiuni bune până mi-am dat seama că într-adevăr funcționează doar pentru cuprinsul unui singur nivel, și anume, doar dacă vreau să am un cuprins cu doar nume de capitole. Dacă am vrut să arăt subsecțiuni în cuprinsul, totuși, nu aveam opțiuni bune. Elementele tabelului nu sunt grozave pentru datele ierarhice, și în timp ce listele de definiții pot fi imbricate din punct de vedere tehnic, semantica nu părea corectă. Așa că m-am întors la planșa de desen.

Am decis să pornesc de la abordarea lui Julie și să folosesc o listă; cu toate acestea, am optat pentru o listă ordonată (<ol>) în loc de o listă neordonată (<ul>). Cred că o listă ordonată este mai potrivită în acest caz. Un cuprins reprezintă o listă de capitole și subtitluri în ordinea în care apar în conținut. Ordinea contează și nu ar trebui să se piardă în markup.

Din păcate, folosirea unei liste ordonate înseamnă pierderea relației semantice dintre titlu și numărul paginii, așa că următorul meu pas a fost să restabilim acea relație în cadrul fiecărui element din listă. Cel mai simplu mod de a rezolva acest lucru este să introduceți pur și simplu cuvântul „pagină” înainte de numărul paginii. În acest fel, relația numărului în raport cu textul este clară, chiar și fără nicio altă distincție vizuală.

Iată un simplu schelet HTML care a stat la baza markupului meu:

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

Aplicarea stilurilor la cuprinsul

Odată ce am stabilit marcajul pe care plănuiam să îl folosesc, următorul pas a fost aplicarea unor stiluri.

În primul rând, am eliminat numerele autogenerate. Puteți alege să păstrați numerele generate automat în propriul proiect, dacă doriți, dar este obișnuit ca cărțile să aibă prefață și postfață nenumerotate incluse în lista de capitole, ceea ce face ca numerele autogenerate să fie incorecte.

În scopul meu, aș completa manual numerele capitolelor, apoi aș ajusta aspectul astfel încât lista de nivel superior să nu aibă nicio umplutură (aliniind-o astfel cu paragrafele) și fiecare listă încorporată este indentată cu două spații. Am ales să folosesc a 2ch valoare de umplutură pentru că încă nu eram sigur ce font voi folosi. The ch Unitatea de lungime permite ca umplutura să fie relativă la lățimea unui caracter - indiferent de fontul folosit - mai degrabă decât o dimensiune absolută a pixelilor care ar putea părea inconsecventă.

Iată CSS-ul cu care am ajuns:

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

Sara Soueidan mi-a subliniat că browserele WebKit elimină semantica listelor când list-style-type is none, așa că a trebuit să adaug role="list" în HTML pentru a-l păstra:

<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 Embed Fallback

Stilizarea titlului și a numărului paginii

Cu lista stilată după placul meu, era timpul să trec la stilarea unui element individual din listă. Pentru fiecare articol din cuprins, titlul și numărul paginii trebuie să fie pe aceeași linie, cu titlul la stânga și numărul paginii aliniat la dreapta.

S-ar putea să vă gândiți: „Nici o problemă, pentru asta este flexbox!” Nu te înșeli! Flexbox poate realiza într-adevăr alinierea corectă a paginii de titlu. Dar există câteva probleme de aliniere dificile atunci când sunt adăugați lideri, așa că am optat în schimb să merg cu abordarea lui Christoph folosind o grilă, care, ca bonus, ajută și la titlurile cu mai multe linii. Iată CSS-ul pentru un articol individual:

.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;
}

Grila are două coloane, dintre care prima este auto-dimensionat pentru a umple toată lățimea recipientului, minus a doua coloană, care este dimensionată la max-content. Numărul paginii este aliniat la dreapta, așa cum este tradițional într-un cuprins.

Singura altă modificare pe care am făcut-o în acest moment a fost să ascund textul „Pagină”. Acest lucru este util pentru cititorii de ecran, dar este inutil din punct de vedere vizual, așa că am folosit a tradiţional visually-hidden clasă pentru a-l ascunde de la vedere:

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

Și, desigur, HTML-ul trebuie actualizat pentru a utiliza acea clasă:

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

Cu această fundație pusă la loc, am trecut să mă adresez liderilor dintre titlu și pagină.

CodePen Embed Fallback

Crearea de lideri de puncte

Liderii sunt atât de obișnuiți în presa scrisă încât s-ar putea să vă întrebați, de ce CSS nu acceptă deja acest lucru? Raspunsul este: da. Ceva de genul.

Există de fapt o leader() funcţie definită în Conținut generat CSS pentru specificația media paginată. Cu toate acestea, ca și în cazul multor specificații media paginate, această funcție nu este implementată în niciun browser, prin urmare excluzând-o ca opțiune (cel puțin în momentul în care scriu acest lucru). Nici măcar nu este listat caniuse.com, probabil pentru că nimeni nu l-a implementat și nu există planuri sau semnale că o va face.

Din fericire, atât Julie, cât și Christoph au abordat deja această problemă în postările lor respective. Pentru a insera liderii de puncte, amândoi au folosit a ::after pseudo-element cu el content proprietate setată la un șir foarte lung de puncte, ca acesta:

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

::after pseudo-elementul este setat pe o poziție absolută pentru a-l scoate din fluxul paginii și pentru a evita împachetarea cu alte linii. Textul este aliniat la dreapta, deoarece vrem ca ultimele puncte ale fiecărei linii să se alinieze la numărul de la sfârșitul liniei. (Mai multe despre complexitatea acestui lucru mai târziu.) The .title elementul este setat să aibă o poziție relativă, astfel încât ::after pseudo-elementul nu iese din cutie. Între timp, cel overflow este ascuns, astfel încât toate acele puncte suplimentare sunt invizibile. Rezultatul este un cuprins drăguț cu lideri de puncte.

Cu toate acestea, mai este ceva care trebuie luat în considerare.

Sara mi-a subliniat, de asemenea, că toate acele puncte contează ca text pentru cititorii de ecran. Deci ce auzi? „Introducere dot dot dot dot…” până când toate punctele sunt anunțate. Aceasta este o experiență îngrozitoare pentru utilizatorii de cititoare de ecran.

Soluția este introducerea unui element suplimentar cu aria-hidden setat la true și apoi utilizați acel element pentru a introduce punctele. Deci HTML devine:

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

Și CSS devine:

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

Acum cititorii de ecran vor ignora punctele și vor scuti utilizatorii de frustrarea de a asculta mai multe puncte anunțate.

CodePen Embed Fallback

Finisaje

În acest moment, componenta de cuprins arată destul de bine, dar ar putea folosi unele lucrări de detalii minore. Pentru început, majoritatea cărților decalează vizual titlurile capitolelor de titlurile subsecțiunilor, așa că am făcut elementele de nivel superior îngroșate și am introdus o marjă pentru a separa subsecțiunile de capitolele care au urmat:

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

Apoi, am vrut să curăț alinierea numerelor paginilor. Totul arăta în regulă când foloseam un font cu lățime fixă, dar pentru fonturile cu lățime variabilă, punctele conducătoare ar putea ajunge să formeze un model în zig-zag pe măsură ce se ajustează la lățimea unui număr de pagină. De exemplu, orice număr de pagină cu un 1 ar fi mai îngust decât altele, rezultând puncte de lider care sunt nealiniate cu punctele de pe liniile anterioare sau următoare.

Misaligned numbers and dots in a table of contents.
Un cuprins perfect cu HTML + CSS

Pentru a rezolva această problemă, am stabilit font-variant-numeric la tabular-nums deci toate numerele sunt tratate cu aceeași lățime. Setând și lățimea minimă la 2ch, m-am asigurat că toate numerele cu una sau două cifre sunt perfect aliniate. (Poate dori să setați acest lucru la 3ch dacă proiectul dvs. are mai mult de 100 de pagini.) Iată CSS-ul final pentru numărul paginii:

.toc-list li > a > .page { min-width: 2ch; font-variant-numeric: tabular-nums; text-align: right;
}
Puncte lider aliniate într-un cuprins.
Un cuprins perfect cu HTML + CSS

Și cu asta, cuprinsul este complet!

CodePen Embed Fallback

Concluzie

Crearea unui cuprins cu numai HTML și CSS a fost o provocare mai mult decât mă așteptam, dar sunt foarte mulțumit de rezultat. Nu numai că această abordare este suficient de flexibilă pentru a găzdui capitole și subsecțiuni, dar gestionează sub-subsecțiunile frumos, fără a actualiza CSS-ul. Abordarea generală funcționează pe paginile web în care doriți să faceți linkuri către diferite locații de conținut, precum și pe PDF-uri în care doriți ca cuprinsul să fie conectat la diferite pagini. Și, desigur, arată grozav și în tipărire dacă sunteți vreodată înclinat să îl utilizați într-o broșură sau carte.

Aș dori să le mulțumesc Julie Blanc și Christoph Grabo pentru postările lor excelente pe blog despre crearea unui cuprins, deoarece ambele au fost de neprețuit când am început. De asemenea, aș dori să-i mulțumesc Sara Soueidan pentru feedback-ul ei privind accesibilitatea în timp ce lucram la acest proiect.


Un cuprins perfect cu HTML + CSS publicat inițial pe CSS-trucuri. Tu ar trebui primiți buletinul informativ.

Timestamp-ul:

Mai mult de la CSS Trucuri