En perfekt innehållsförteckning med HTML + CSS PlatoBlockchain Data Intelligence. Vertikal sökning. Ai.

En perfekt innehållsförteckning med HTML + CSS

Tidigare i år gav jag ut en e-bok som heter Förstå JavaScript-löften (gratis för nedladdning). Även om jag inte hade för avsikt att förvandla den till en tryckt bok, kontaktade tillräckligt många människor för att fråga om en tryckt version som jag bestämde mig för att självpublicera den också. Jag trodde att det skulle vara en enkel övning med HTML och CSS för att generera en PDF och skicka den sedan till skrivaren. Vad jag inte insåg var att jag inte hade något svar på en viktig del av en tryckt bok: innehållsförteckningen.

Sammansättningen av en innehållsförteckning

I grunden är en innehållsförteckning ganska enkel. Varje rad representerar en del av en bok eller webbsida och anger var du kan hitta innehållet. Vanligtvis innehåller raderna tre delar:

  1. Titeln på kapitlet eller avsnittet
  2. Ledare (dvs. de prickar, streck eller linjer) som visuellt kopplar titeln till sidnumret
  3. Sidnumret

En innehållsförteckning är lätt att skapa inuti ordbehandlingsverktyg som Microsoft Word eller Google Docs, men eftersom mitt innehåll var i Markdown och sedan omvandlats till HTML, var det inte ett bra alternativ för mig. Jag ville ha något automatiserat som skulle fungera med HTML för att generera innehållsförteckningen i ett format som var lämpligt för utskrift. Jag ville också att varje rad skulle vara en länk så att den kunde användas i webbsidor och PDF-filer för att navigera runt i dokumentet. Jag ville också ha punktledare mellan titel och sidnummer.

Och så började jag forska.

Jag stötte på två utmärkta blogginlägg om att skapa en innehållsförteckning med HTML och CSS. Den första var "Skapa en innehållsförteckning från din HTML" av Julie Blanc. Julie jobbade på PagedJS, en polyfill för saknade bladade mediafunktioner i webbläsare som korrekt formaterar dokument för utskrift. Jag började med Julies exempel, men upptäckte att det inte riktigt fungerade för mig. Därefter hittade jag Christoph Grabo's "Responsiva TOC-ledare med CSS" post, som introducerade konceptet att använda CSS Grid (i motsats till Julies float-baserade tillvägagångssätt) för att göra anpassningen enklare. Än en gång, dock var hans tillvägagångssätt inte helt rätt för mina syften.

Efter att ha läst dessa två inlägg kände jag dock att jag hade en tillräckligt bra förståelse för layoutproblemen för att börja på egen hand. Jag använde bitar från båda blogginläggen samt lade till några nya HTML- och CSS-koncept i metoden för att komma fram till ett resultat som jag är nöjd med.

Att välja rätt uppmärkning

När jag bestämde mig för rätt uppmärkning för en innehållsförteckning tänkte jag i första hand på rätt semantik. I grunden handlar en innehållsförteckning om att en titel (kapitel eller underavsnitt) är knuten till ett sidnummer, nästan som ett nyckel-värdepar. Det ledde mig till två alternativ:

  • Ett alternativ är att använda en tabell (<table>) med en kolumn för rubriken och en kolumn för sidan.
  • Sedan finns det den ofta oanvända och bortglömda definitionslistan (<dl>) element. Den fungerar också som en nyckel-värdekarta. Så återigen skulle förhållandet mellan titeln och sidnumret vara uppenbart.

Båda av dessa verkade vara bra alternativ tills jag insåg att de egentligen bara fungerar för innehållsförteckningar på en nivå, nämligen bara om jag ville ha en innehållsförteckning med bara kapitelnamn. Om jag ville visa underavsnitt i innehållsförteckningen hade jag dock inga bra alternativ. Tabellelement är inte bra för hierarkiska data, och även om definitionslistor tekniskt sett kan kapslas, verkade semantiken inte korrekt. Så jag gick tillbaka till ritbordet.

Jag bestämde mig för att bygga på Julies tillvägagångssätt och använda en lista; men jag valde en beställd lista (<ol>) istället för en oordnad lista (<ul>). Jag tror att en ordnad lista är mer lämplig i det här fallet. En innehållsförteckning representerar en lista med kapitel och underrubriker i den ordning som de förekommer i innehållet. Ordningen är viktig och bör inte gå vilse i uppmärkningen.

Tyvärr innebär att använda en ordnad lista att förlora den semantiska relationen mellan titeln och sidnumret, så mitt nästa steg var att återupprätta det förhållandet inom varje listobjekt. Det enklaste sättet att lösa detta är att helt enkelt infoga ordet "sida" före sidnumret. På så sätt är förhållandet mellan siffran och texten tydligt, även utan någon annan visuell distinktion.

Här är ett enkelt HTML-skelett som låg till grund för min uppmärkning:

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

Tillämpa stilar på innehållsförteckningen

När jag väl hade etablerat märkningen jag planerade att använda var nästa steg att tillämpa några stilar.

Först tog jag bort de autogenererade siffrorna. Du kan välja att behålla de autogenererade siffrorna i ditt eget projekt om du vill, men det är vanligt att böcker har onumrerade för- och efterord inkluderade i listan över kapitel, vilket gör de autogenererade siffrorna felaktiga.

För mitt syfte skulle jag fylla i kapitelnumren manuellt och sedan justera layouten så att listan på toppnivån inte har någon utfyllnad (och därmed anpassa den till stycken) och varje inbäddad lista är indragen med två mellanslag. Jag valde att använda en 2ch utfyllnadsvärde eftersom jag fortfarande inte var helt säker på vilket typsnitt jag skulle använda. De ch längdenhet tillåter utfyllnad att vara relativt till bredden på ett tecken - oavsett vilket teckensnitt som används - snarare än en absolut pixelstorlek som kan sluta se inkonsekvent ut.

Här är CSS:en jag slutade med:

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

Sara Soueidan påpekade för mig att WebKit-webbläsare tar bort listsemantik när list-style-type is none, så jag behövde lägga till role="list" in i HTML-koden för att bevara den:

<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 Bädda in reserv

Styling av titel och sidnummer

Med listan utformad efter min smak var det dags att gå vidare till att styla en enskild listpost. För varje punkt i innehållsförteckningen måste titel och sidnummer stå på samma rad, med titeln till vänster och sidnumret till höger.

Du kanske tänker, "Inga problem, det är vad flexbox är till för!" Du har inte fel! Flexbox kan verkligen uppnå korrekt titelsidesjustering. Men det finns några knepiga anpassningsproblem när ledarna läggs till, så jag valde istället att gå med Christophs tillvägagångssätt med hjälp av ett rutnät, vilket som en bonus eftersom det också hjälper till med flerradstitlar. Här är CSS för en enskild artikel:

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

Rutnätet har två kolumner, varav den första är auto-storlek för att fylla upp hela behållarens bredd, minus den andra kolumnen, som är dimensionerad till max-content. Sidnumret är justerat till höger, som är traditionellt i en innehållsförteckning.

Den enda andra ändringen jag gjorde vid det här laget var att dölja "Sida"-texten. Detta är användbart för skärmläsare men onödigt visuellt, så jag använde en traditionell visually-hidden klass för att dölja det:

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

Och naturligtvis måste HTML-koden uppdateras för att använda den klassen:

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

Med den här grunden på plats gick jag vidare till ledarna mellan titeln och sidan.

CodePen Bädda in reserv

Skapa punktledare

Ledare är så vanliga i tryckta medier att du kanske undrar, varför stöder inte CSS redan det? Svaret är: det gör det. Ungefär.

Det finns faktiskt en leader() funktion definierad i CSS-genererat innehåll för Paged Media-specifikation. Men som med mycket av de sökta mediaspecifikationerna är den här funktionen inte implementerad i någon webbläsare, därför utesluter den som ett alternativ (åtminstone när jag skriver detta). Det är inte ens listat på caniuse.com, förmodligen för att ingen har implementerat det och det finns inga planer eller signaler om att de kommer att göra det.

Lyckligtvis har både Julie och Christoph redan tagit upp detta problem i sina respektive inlägg. För att infoga punktledarna använde de båda en ::after pseudoelement med dess content egenskapen inställd på en mycket lång sträng av punkter, så här:

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

Smakämnen ::after pseudo-element är inställt på en absolut position för att ta det ur sidans flöde och undvika radbrytning till andra rader. Texten justeras till höger eftersom vi vill att de sista prickarna på varje rad ska vara jämna med numret i slutet av raden. (Mer om komplexiteten i detta senare.) The .title element är inställt för att ha en relativ position så att ::after pseudo-element bryter inte ut ur sin låda. Under tiden har overflow är dold så att alla de extra prickarna är osynliga. Resultatet är en vacker innehållsförteckning med punktledare.

Men det finns något annat som behöver övervägas.

Sara påpekade också för mig att alla dessa prickar räknas som text till skärmläsare. Så vad hör du? "Introduktion prick prick prick prick..." tills alla prickar tillkännages. Det är en hemsk upplevelse för användare av skärmläsare.

Lösningen är att sätta in ytterligare ett element med aria-hidden satt till true och använd sedan det elementet för att infoga prickarna. Så HTML blir:

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

Och CSS blir:

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

Nu kommer skärmläsare att ignorera prickarna och bespara användarna frustrationen av att lyssna på flera prickar som annonseras.

CodePen Bädda in reserv

Finputsning

Vid det här laget ser innehållsförteckningskomponenten ganska bra ut, men den kan behöva lite detaljarbete. Till att börja med kompenserar de flesta böcker visuellt kapiteltitlar från underavsnittstitlar, så jag gjorde objekten på översta nivån fetstilta och introducerade en marginal för att separera underavsnitt från kapitlen som följde:

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

Därefter ville jag rensa upp justeringen av sidnumren. Allt såg okej ut när jag använde ett typsnitt med fast bredd, men för typsnitt med variabel bredd kan ledarprickarna sluta bilda ett sicksackmönster när de anpassar sig till bredden på ett sidnummer. Till exempel skulle alla sidnummer med en 1 vara smalare än andra, vilket resulterar i ledarpunkter som är felinriktade med prickarna på föregående eller efterföljande rader.

Feljusterade siffror och punkter i en innehållsförteckning.
En perfekt innehållsförteckning med HTML + CSS

För att lösa det här problemet ställde jag in font-variant-numeric till tabular-nums så alla nummer behandlas med samma bredd. Genom att även ställa in minimibredden till 2ch, såg jag till att alla tal med en eller två siffror är perfekt anpassade. (Du kanske vill ställa in detta till 3ch om ditt projekt har fler än 100 sidor.) Här är den slutliga CSS för sidnumret:

.toc-list li > a > .page { min-width: 2ch; font-variant-numeric: tabular-nums; text-align: right;
}
Justerade ledarpunkter i en innehållsförteckning.
En perfekt innehållsförteckning med HTML + CSS

Och därmed är innehållsförteckningen komplett!

CodePen Bädda in reserv

Slutsats

Att skapa en innehållsförteckning med inget annat än HTML och CSS var en mer utmaning än jag förväntade mig, men jag är väldigt nöjd med resultatet. Detta tillvägagångssätt är inte bara tillräckligt flexibelt för att rymma kapitel och underavsnitt, utan det hanterar underavsnitt snyggt utan att uppdatera CSS. Det övergripande tillvägagångssättet fungerar på webbsidor där du vill länka till de olika platserna för innehåll, samt PDF-filer där du vill att innehållsförteckningen ska länka till olika sidor. Och naturligtvis ser den också bra ut i tryck om du någonsin är benägen att använda den i en broschyr eller bok.

Jag skulle vilja tacka Julie Blanc och Christoph Grabo för deras utmärkta blogginlägg om att skapa en innehållsförteckning, eftersom båda dessa var ovärderliga när jag började. Jag vill också tacka Sara Soueidan för hennes tillgänglighetsfeedback när jag arbetade med det här projektet.


En perfekt innehållsförteckning med HTML + CSS ursprungligen publicerad på CSS-tricks. Du borde få nyhetsbrevet.

Tidsstämpel:

Mer från CSS-tricks