สารบัญที่สมบูรณ์แบบด้วย HTML + CSS PlatoBlockchain Data Intelligence ค้นหาแนวตั้ง AI.

สารบัญที่สมบูรณ์แบบด้วย HTML + CSS

เมื่อต้นปีนี้ ฉันตีพิมพ์ ebook ด้วยตนเองชื่อ การทำความเข้าใจสัญญาจาวาสคริปต์ (ดาวน์โหลดฟรี). แม้ว่าฉันไม่ได้ตั้งใจจะเปลี่ยนมันเป็นหนังสือฉบับพิมพ์ แต่มีคนมากพอที่จะสอบถามเกี่ยวกับฉบับพิมพ์ที่ฉันตัดสินใจเผยแพร่ด้วยตนเองเช่นกัน ฉันคิดว่ามันจะเป็นแบบฝึกหัดง่ายๆ โดยใช้ HTML และ CSS เพื่อ สร้าง PDF แล้วส่งไปที่เครื่องพิมพ์ สิ่งที่ฉันไม่รู้ก็คือฉันไม่มีคำตอบในส่วนสำคัญของหนังสือที่พิมพ์ นั่นคือ สารบัญ

การแต่งหน้าของสารบัญ

ที่แกนหลักของสารบัญนั้นค่อนข้างง่าย แต่ละบรรทัดแสดงถึงส่วนหนึ่งของหนังสือหรือหน้าเว็บ และระบุว่าคุณสามารถหาเนื้อหานั้นได้จากที่ใด โดยปกติ เส้นจะมีสามส่วน:

  1. ชื่อของบทหรือส่วน
  2. ผู้นำ (เช่น จุด ขีดกลาง หรือเส้น) ที่เชื่อมโยงชื่อเรื่องกับหมายเลขหน้า
  3. หมายเลขหน้า

สารบัญสามารถสร้างได้ง่ายภายในเครื่องมือประมวลผลคำ เช่น Microsoft Word หรือ Google Docs แต่เนื่องจากเนื้อหาของฉันอยู่ใน Markdown แล้วแปลงเป็น HTML นั่นไม่ใช่ตัวเลือกที่ดีสำหรับฉัน ฉันต้องการระบบอัตโนมัติที่ทำงานร่วมกับ HTML เพื่อสร้างสารบัญในรูปแบบที่เหมาะสมสำหรับการพิมพ์ ฉันยังต้องการให้แต่ละบรรทัดเป็นลิงก์เพื่อให้สามารถใช้ในหน้าเว็บและ PDF เพื่อไปยังส่วนต่างๆ ของเอกสารได้ ฉันยังต้องการจุดผู้นำระหว่างชื่อและหมายเลขหน้า

ดังนั้นฉันจึงเริ่มค้นคว้า

ฉันพบโพสต์บล็อกที่ยอดเยี่ยมสองโพสต์เกี่ยวกับการสร้างสารบัญด้วย HTML และ CSS คนแรกคือ “สร้างสารบัญจาก HTML ของคุณ” โดย จูลี่ บล็องก์ จูลี่ทำงาน เพจJS, โพลีฟิลสำหรับคุณสมบัติสื่อหน้าที่หายไปในเว็บเบราว์เซอร์ที่จัดรูปแบบเอกสารสำหรับการพิมพ์อย่างเหมาะสม ฉันเริ่มต้นด้วยตัวอย่างของ Julie แต่พบว่ามันไม่ได้ผลสำหรับฉัน ต่อไปฉันพบ Christoph Grabo's “ผู้นำ TOC ที่ตอบสนองต่อ CSS” โพสต์ซึ่งแนะนำแนวคิดของการใช้ CSS Grid (ตรงข้ามกับแนวทางแบบลอยตัวของ Julie) เพื่อให้การจัดตำแหน่งง่ายขึ้น เป็นอีกครั้งที่แนวทางของเขาไม่เหมาะกับจุดประสงค์ของฉัน

หลังจากอ่านสองโพสต์นี้แล้ว ฉันรู้สึกว่ามีความเข้าใจที่ดีเพียงพอเกี่ยวกับปัญหาเลย์เอาต์ที่จะดำเนินการด้วยตัวเอง ฉันใช้ชิ้นส่วนจากโพสต์ในบล็อกทั้งสองรวมทั้งเพิ่มแนวคิด HTML และ CSS ใหม่ ๆ ลงในแนวทางเพื่อให้ได้ผลลัพธ์ที่ฉันพอใจ

การเลือกมาร์กอัปที่ถูกต้อง

เมื่อตัดสินใจเลือกมาร์กอัปที่ถูกต้องสำหรับสารบัญ ฉันคิดว่าหลักเกี่ยวกับความหมายที่ถูกต้อง โดยพื้นฐานแล้ว สารบัญเป็นเรื่องเกี่ยวกับชื่อ (บทหรือส่วนย่อย) ที่เชื่อมโยงกับหมายเลขหน้า เกือบจะเหมือนกับคู่คีย์-ค่า นั่นทำให้ฉันมีทางเลือกสองทาง:

  • ทางเลือกหนึ่งคือการใช้ตาราง (<table>) โดยมีหนึ่งคอลัมน์สำหรับชื่อและหนึ่งคอลัมน์สำหรับหน้า
  • จากนั้นมีรายการคำจำกัดความที่ไม่ได้ใช้บ่อยและถูกลืม (<dl>) ธาตุ. นอกจากนี้ยังทำหน้าที่เป็นแผนผังคีย์-ค่า ดังนั้น เป็นอีกครั้งที่ความสัมพันธ์ระหว่างชื่อและหมายเลขหน้าจะชัดเจนขึ้น

ทั้งสองตัวเลือกนี้ดูเหมือนจะเป็นตัวเลือกที่ดี จนกระทั่งฉันตระหนักว่าพวกมันใช้งานได้จริงกับสารบัญระดับเดียวเท่านั้น กล่าวคือ เฉพาะเมื่อฉันต้องการมีสารบัญที่มีเพียงแค่ชื่อบทเท่านั้น ถ้าฉันต้องการแสดงส่วนย่อยในสารบัญ ฉันก็ไม่มีทางเลือกที่ดี องค์ประกอบตารางไม่เหมาะกับข้อมูลแบบลำดับชั้นและในขณะที่รายการคำจำกัดความสามารถซ้อนกันในทางเทคนิคได้ แต่ความหมายก็ดูเหมือนจะไม่ถูกต้อง ดังนั้นฉันจึงกลับไปที่กระดานวาดภาพ

ฉันตัดสินใจที่จะสร้างแนวทางของ Julie และใช้รายการ อย่างไรก็ตาม ฉันเลือกรายการสั่งซื้อ (<ol>) แทนที่จะเป็นรายการที่ไม่เรียงลำดับ (<ul>). ฉันคิดว่ารายการสั่งซื้อเหมาะสมกว่าในกรณีนี้ สารบัญแสดงรายการของบทและหัวข้อย่อยตามลำดับที่ปรากฏในเนื้อหา ลำดับมีความสำคัญและไม่ควรหลงทางในมาร์กอัป

ขออภัย การใช้รายการที่เรียงลำดับหมายถึงการสูญเสียความสัมพันธ์ทางความหมายระหว่างชื่อและหมายเลขหน้า ดังนั้น ขั้นตอนต่อไปของฉันคือการสร้างความสัมพันธ์นั้นขึ้นใหม่ภายในแต่ละรายการ วิธีที่ง่ายที่สุดในการแก้ปัญหานี้คือใส่คำว่า "หน้า" ก่อนหมายเลขหน้า ด้วยวิธีนี้ ความสัมพันธ์ของตัวเลขที่สัมพันธ์กับข้อความจึงมีความชัดเจน แม้จะไม่มีความแตกต่างทางสายตาก็ตาม

นี่คือโครงร่าง HTML อย่างง่ายที่สร้างพื้นฐานของมาร์กอัปของฉัน:

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

การใช้สไตล์กับสารบัญ

เมื่อฉันสร้างมาร์กอัปที่ฉันวางแผนจะใช้แล้ว ขั้นตอนต่อไปคือการใช้สไตล์บางอย่าง

อันดับแรก ฉันลบตัวเลขที่สร้างอัตโนมัติ คุณสามารถเลือกที่จะเก็บตัวเลขที่สร้างอัตโนมัติไว้ในโปรเจ็กต์ของคุณเองได้หากต้องการ แต่เป็นเรื่องปกติที่หนังสือจะมีคำนำและคำนำหน้าที่ไม่มีหมายเลขรวมอยู่ในรายการของบท ซึ่งทำให้ตัวเลขที่สร้างอัตโนมัติไม่ถูกต้อง

เพื่อจุดประสงค์ของฉัน ฉันจะกรอกหมายเลขบทด้วยตนเอง แล้วปรับเลย์เอาต์เพื่อให้รายการระดับบนสุดไม่มีช่องว่างภายใน (ดังนั้นจึงจัดแนวด้วยย่อหน้า) และแต่ละรายการที่ฝังไว้จะถูกเว้นวรรคสองช่อง ฉันเลือกใช้ a 2ch ค่าช่องว่างภายในเพราะฉันยังไม่ค่อยแน่ใจว่าจะใช้แบบอักษรใด ดิ ch หน่วยความยาวช่วยให้ช่องว่างภายในสัมพันธ์กับความกว้างของอักขระ ไม่ว่าจะใช้แบบอักษรใด แทนที่จะเป็นขนาดพิกเซลที่แน่นอนซึ่งอาจทำให้ดูไม่สอดคล้องกัน

นี่คือ CSS ที่ฉันลงเอยด้วย:

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

ซาร่า ซูเอดัน ชี้ให้ฉันเห็นว่าเบราว์เซอร์ WebKit ลบรายการความหมายเมื่อ list-style-type is noneดังนั้นฉันจึงจำเป็นต้องเพิ่ม role="list" ลงใน HTML เพื่อรักษาไว้:

<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 ฝังตัวสำรอง

จัดแต่งทรงผมชื่อและหมายเลขหน้า

ด้วยการจัดรูปแบบรายการตามความชอบของฉัน ถึงเวลาแล้วที่จะต้องจัดรูปแบบรายการแต่ละรายการ สำหรับแต่ละรายการในสารบัญ ชื่อและหมายเลขหน้าต้องอยู่ในบรรทัดเดียวกัน โดยให้ชื่อเรื่องอยู่ด้านซ้ายและหมายเลขหน้าจะจัดชิดขวา

คุณอาจกำลังคิดว่า “ไม่มีปัญหา นั่นคือสิ่งที่ flexbox มีไว้เพื่อ!” คุณไม่ผิด! Flexbox สามารถจัดตำแหน่งหน้าชื่อเรื่องที่ถูกต้องได้อย่างแท้จริง แต่มีปัญหาการจัดตำแหน่งที่ยุ่งยากเมื่อมีการเพิ่มผู้นำ ดังนั้นฉันจึงเลือกที่จะใช้แนวทางของ Christoph โดยใช้ตารางซึ่งถือเป็นโบนัสเนื่องจากช่วยในเรื่องหลายบรรทัด นี่คือ CSS สำหรับแต่ละรายการ:

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

ตารางมีสองคอลัมน์ คอลัมน์แรกคือ auto-ขนาดเพื่อเติมความกว้างทั้งหมดของคอนเทนเนอร์ ลบคอลัมน์ที่สองซึ่งมีขนาดถึง max-content. หมายเลขหน้าถูกจัดชิดขวา เช่นเดียวกับแบบดั้งเดิมในสารบัญ

การเปลี่ยนแปลงเพียงอย่างเดียวที่ฉันทำ ณ จุดนี้คือการซ่อนข้อความ "หน้า" สิ่งนี้มีประโยชน์สำหรับโปรแกรมอ่านหน้าจอแต่ไม่จำเป็นสำหรับการมองเห็น ดังนั้นฉันจึงใช้ a แบบดั้งเดิม visually-hidden ชั้น เพื่อซ่อนไม่ให้มองเห็น:

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

และแน่นอนว่าต้องอัปเดต HTML เพื่อใช้คลาสนั้น:

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

ด้วยรากฐานนี้ ฉันได้ย้ายไปที่ผู้นำระหว่างชื่อเรื่องและหน้า

CodePen ฝังตัวสำรอง

การสร้างผู้นำดอท

ผู้นำเป็นเรื่องธรรมดามากในสื่อสิ่งพิมพ์ที่คุณอาจสงสัยว่าทำไม CSS ถึงไม่สนับสนุนสิ่งนั้นอยู่แล้ว คำตอบคือ: มันทำ ดีชนิด.

จริงๆแล้วมี leader() ฟังก์ชันที่กำหนดไว้ใน เนื้อหาที่สร้างโดย CSS สำหรับข้อกำหนดสื่อเพจ. อย่างไรก็ตาม เช่นเดียวกับข้อกำหนดของสื่อที่ใช้เพจส่วนใหญ่ ฟังก์ชันนี้ไม่ได้ใช้งานในเบราว์เซอร์ใดๆ ดังนั้นจึงไม่รวมเป็นตัวเลือก (อย่างน้อยในขณะที่ฉันกำลังเขียนสิ่งนี้) มันไม่ได้ระบุไว้ใน caniuse.comน่าจะเป็นเพราะไม่มีใครดำเนินการ และไม่มีแผนหรือสัญญาณที่จะดำเนินการ

โชคดีที่ทั้ง Julie และ Christoph ได้กล่าวถึงปัญหานี้ในโพสต์ของตนแล้ว ในการแทรกผู้นำจุด พวกเขาทั้งคู่ใช้ a ::after องค์ประกอบหลอกด้วย content คุณสมบัติตั้งค่าเป็นสตริงจุดที่ยาวมากดังนี้:

.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 องค์ประกอบหลอกถูกตั้งค่าเป็นตำแหน่งที่แน่นอนเพื่อนำออกจากโฟลว์ของหน้าและหลีกเลี่ยงการตัดไปยังบรรทัดอื่น ข้อความถูกจัดชิดขวาเพราะเราต้องการให้จุดสุดท้ายของแต่ละบรรทัดตรงกับตัวเลขที่ท้ายบรรทัด (เพิ่มเติมเกี่ยวกับความซับซ้อนของสิ่งนี้ในภายหลัง) The .title องค์ประกอบถูกกำหนดให้มีตำแหน่งสัมพัทธ์ดังนั้น ::after องค์ประกอบหลอกไม่แตกออกจากกล่อง ในขณะเดียวกัน overflow ถูกซ่อนไว้เพื่อให้มองไม่เห็นจุดพิเศษเหล่านั้น ผลลัพธ์ที่ได้คือสารบัญที่สวยงามพร้อมจุดผู้นำ

อย่างไรก็ตาม มีอย่างอื่นที่ต้องพิจารณา

ซาร่ายังชี้ให้ฉันเห็นว่าจุดทั้งหมดเหล่านี้นับเป็นข้อความไปยังโปรแกรมอ่านหน้าจอ แล้วคุณได้ยินอะไรไหม? “Introduction dot dot dot dot…” จนกว่าจะมีการประกาศจุดทั้งหมด นั่นเป็นประสบการณ์ที่แย่มากสำหรับผู้ใช้โปรแกรมอ่านหน้าจอ

วิธีแก้ไขคือการแทรกองค์ประกอบเพิ่มเติมด้วย aria-hidden ตั้งค่าให้ true แล้วใช้องค์ประกอบนั้นเพื่อแทรกจุด ดังนั้น HTML จะกลายเป็น:

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

และ CSS จะกลายเป็น:

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

ตอนนี้โปรแกรมอ่านหน้าจอจะละเว้นจุดและช่วยให้ผู้ใช้ไม่ต้องหงุดหงิดกับการฟังหลายจุดที่กำลังประกาศ

CodePen ฝังตัวสำรอง

สัมผัสการตกแต่ง

ณ จุดนี้ ส่วนประกอบของสารบัญดูค่อนข้างดี แต่อาจใช้รายละเอียดเล็กๆ น้อยๆ ได้บ้าง ในการเริ่มต้น หนังสือส่วนใหญ่จะออฟเซ็ตชื่อบทจากชื่อส่วนย่อย ดังนั้นฉันจึงทำให้รายการระดับบนสุดเป็นตัวหนาและแนะนำระยะขอบเพื่อแยกส่วนย่อยออกจากบทที่ตามมา:

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

ต่อไป ฉันต้องการล้างการจัดตำแหน่งหมายเลขหน้า ทุกอย่างดูโอเคเมื่อฉันใช้แบบอักษรที่มีความกว้างคงที่ แต่สำหรับแบบอักษรที่มีความกว้างผันแปร จุดผู้นำอาจกลายเป็นรูปแบบซิกแซกเมื่อปรับตามความกว้างของหมายเลขหน้า ตัวอย่างเช่น หมายเลขหน้าใดๆ ที่มี 1 จะแคบกว่าหมายเลขอื่น ส่งผลให้จุดนำไม่ตรงกับจุดในบรรทัดก่อนหน้าหรือบรรทัดถัดไป

ตัวเลขและจุดไม่ตรงแนวในสารบัญ
สารบัญที่สมบูรณ์แบบด้วย HTML + CSS

เพื่อแก้ไขปัญหานี้ ฉันตั้งค่า font-variant-numeric ไปยัง tabular-nums ดังนั้นตัวเลขทั้งหมดจึงมีความกว้างเท่ากัน ด้วยการตั้งค่าความกว้างขั้นต่ำเป็น 2chฉันแน่ใจว่าตัวเลขทั้งหมดที่มีหนึ่งหรือสองหลักอยู่ในแนวเดียวกันอย่างสมบูรณ์ (คุณอาจต้องการตั้งค่านี้เป็น 3ch หากโครงการของคุณมีมากกว่า 100 หน้า) นี่คือ CSS สุดท้ายสำหรับหมายเลขหน้า:

.toc-list li > a > .page { min-width: 2ch; font-variant-numeric: tabular-nums; text-align: right;
}
ผู้นำที่จัดแนวจุดในสารบัญ
สารบัญที่สมบูรณ์แบบด้วย HTML + CSS

และด้วยเหตุนี้สารบัญจึงสมบูรณ์!

CodePen ฝังตัวสำรอง

สรุป

การสร้างสารบัญโดยไม่มีอะไรนอกจาก HTML และ CSS เป็นสิ่งที่ท้าทายมากกว่าที่ฉันคาดไว้ แต่ฉันมีความสุขมากกับผลลัพธ์ที่ได้ วิธีการนี้ไม่เพียงแต่ยืดหยุ่นพอที่จะรองรับบทและส่วนย่อยเท่านั้น แต่ยังจัดการส่วนย่อยได้อย่างดีโดยไม่ต้องอัปเดต CSS วิธีการโดยรวมใช้งานได้บนหน้าเว็บที่คุณต้องการเชื่อมโยงไปยังตำแหน่งต่างๆ ของเนื้อหา เช่นเดียวกับ PDF ที่คุณต้องการให้สารบัญเชื่อมโยงไปยังหน้าต่างๆ และแน่นอน การพิมพ์นี้ก็ดูดีเช่นกัน หากคุณเคยอยากนำไปใช้ในโบรชัวร์หรือหนังสือ

ฉันอยากจะขอบคุณ Julie Blanc และ Christoph Grabo สำหรับบล็อกโพสต์ที่ยอดเยี่ยมของพวกเขาเกี่ยวกับการสร้างสารบัญ เนื่องจากทั้งสองสิ่งนี้มีค่ามากเมื่อฉันเริ่มต้น ฉันขอขอบคุณ Sara Soueidan สำหรับความคิดเห็นเกี่ยวกับการเข้าถึงพิเศษของเธอขณะที่ฉันทำงานในโครงการนี้


สารบัญที่สมบูรณ์แบบด้วย HTML + CSS เผยแพร่ครั้งแรกเมื่อ CSS-เคล็ดลับ. คุณควร รับจดหมายข่าว.

ประทับเวลา:

เพิ่มเติมจาก เคล็ดลับ CSS