Mục lục hoàn hảo với HTML + CSS Thông tin dữ liệu PlatoBlockchain. Tìm kiếm dọc. Ái.

Một mục lục hoàn hảo với HTML + CSS

Đầu năm nay, tôi đã tự xuất bản một ebook có tên Hiểu những lời hứa về JavaScript (tải về miễn phí). Mặc dù tôi không có ý định chuyển nó thành sách in, nhưng đã có đủ người liên hệ để hỏi về một phiên bản in mà tôi cũng quyết định tự xuất bản. Tôi nghĩ đây sẽ là một bài tập dễ dàng khi sử dụng HTML và CSS để tạo một tệp PDF và sau đó gửi nó đến máy in. Điều tôi không nhận ra là tôi không có câu trả lời cho một phần quan trọng của một cuốn sách in: mục lục.

Trang điểm của mục lục

Về cốt lõi, mục lục khá đơn giản. Mỗi dòng đại diện cho một phần của sách hoặc trang web và cho biết bạn có thể tìm thấy nội dung đó ở đâu. Thông thường, các dòng chứa ba phần:

  1. Tiêu đề của chương hoặc phần
  2. Dấu gạch đầu dòng (tức là những dấu chấm, dấu gạch ngang hoặc dòng) kết nối trực quan tiêu đề với số trang
  3. Số trang

Mục lục rất dễ tạo bên trong các công cụ xử lý văn bản như Microsoft Word hoặc Google Docs, nhưng vì nội dung của tôi ở trong Markdown và sau đó được chuyển thành HTML, đó không phải là một lựa chọn tốt cho tôi. Tôi muốn một thứ gì đó tự động hoạt động với HTML để tạo mục lục ở định dạng phù hợp cho việc in ấn. Tôi cũng muốn mỗi dòng là một liên kết để nó có thể được sử dụng trong các trang web và PDF để điều hướng xung quanh tài liệu. Tôi cũng muốn các nhà lãnh đạo chấm giữa tiêu đề và số trang.

Và vì vậy tôi bắt đầu nghiên cứu.

Tôi đã xem qua hai bài đăng trên blog tuyệt vời về cách tạo mục lục bằng HTML và CSS. Đầu tiên là “Xây dựng Mục lục từ HTML của bạn” của Julie Blanc. Julie đã làm việc trên PagedJS, một polyfill cho các tính năng phương tiện được phân trang bị thiếu trong các trình duyệt web định dạng đúng tài liệu để in. Tôi bắt đầu với ví dụ của Julie, nhưng thấy rằng nó không hoàn toàn phù hợp với tôi. Tiếp theo, tôi tìm thấy Christoph Grabo's “Các dòng dẫn đầu TOC đáp ứng với CSS” bài đăng giới thiệu khái niệm sử dụng CSS Grid (trái ngược với cách tiếp cận dựa trên float của Julie) để làm cho việc căn chỉnh dễ dàng hơn. Tuy nhiên, một lần nữa, cách tiếp cận của anh ấy không hoàn toàn đúng với mục đích của tôi.

Tuy nhiên, sau khi đọc hai bài viết này, tôi cảm thấy mình đã hiểu đủ tốt về các vấn đề bố cục để bắt tay vào thực hiện. Tôi đã sử dụng các phần từ cả hai bài đăng trên blog cũng như thêm một số khái niệm HTML và CSS mới vào cách tiếp cận để đưa ra kết quả mà tôi hài lòng.

Chọn đánh dấu chính xác

Khi quyết định đánh dấu chính xác cho mục lục, tôi chủ yếu nghĩ về ngữ nghĩa chính xác. Về cơ bản, mục lục là về một tiêu đề (chương hoặc tiểu mục) được gắn với một số trang, gần giống như một cặp khóa-giá trị. Điều đó dẫn tôi đến hai lựa chọn:

  • Một tùy chọn là sử dụng một bảng (<table>) với một cột cho tiêu đề và một cột cho trang.
  • Sau đó, có danh sách định nghĩa thường không được sử dụng và bị lãng quên (<dl>) yếu tố. Nó cũng hoạt động như một bản đồ khóa-giá trị. Vì vậy, một lần nữa, mối quan hệ giữa tiêu đề và số trang sẽ rõ ràng.

Một trong hai cách này dường như là những lựa chọn tốt cho đến khi tôi nhận ra rằng chúng thực sự chỉ hoạt động với các mục lục cấp đơn, cụ thể là, chỉ khi tôi muốn có một mục lục chỉ có tên chương. Tuy nhiên, nếu tôi muốn hiển thị các tiểu mục trong mục lục, tôi không có bất kỳ lựa chọn nào tốt. Các phần tử bảng không phù hợp với dữ liệu phân cấp, và mặc dù về mặt kỹ thuật, danh sách định nghĩa có thể được lồng vào nhau, nhưng ngữ nghĩa có vẻ không chính xác. Vì vậy, tôi quay trở lại bảng vẽ.

Tôi quyết định xây dựng cách tiếp cận của Julie và sử dụng một danh sách; tuy nhiên, tôi đã chọn một danh sách có thứ tự (<ol>) thay vì một danh sách không có thứ tự (<ul>). Tôi nghĩ rằng một danh sách có thứ tự là thích hợp hơn trong trường hợp này. Mục lục thể hiện danh sách các chương và tiêu đề phụ theo thứ tự xuất hiện trong nội dung. Thứ tự quan trọng và không nên bị mất trong đánh dấu.

Thật không may, sử dụng danh sách có thứ tự có nghĩa là mất mối quan hệ ngữ nghĩa giữa tiêu đề và số trang, vì vậy bước tiếp theo của tôi là thiết lập lại mối quan hệ đó trong mỗi mục danh sách. Cách dễ nhất để giải quyết vấn đề này là chỉ cần chèn từ “trang” vào trước số trang. Bằng cách đó, mối quan hệ của con số so với văn bản là rõ ràng, ngay cả khi không có bất kỳ sự phân biệt trực quan nào khác.

Đây là một khung HTML đơn giản hình thành nền tảng cho đánh dấu của tôi:

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

Áp dụng các kiểu cho mục lục

Khi tôi đã thiết lập đánh dấu mà tôi định sử dụng, bước tiếp theo là áp dụng một số kiểu.

Đầu tiên, tôi đã xóa các số tự động tạo. Bạn có thể chọn giữ các số tự động tạo trong dự án của riêng mình nếu bạn muốn, nhưng thông thường các cuốn sách có các từ ngữ không được đánh số và từ ngữ sau khi được bao gồm trong danh sách các chương, điều này làm cho các số tự động được tạo không chính xác.

Đối với mục đích của tôi, tôi sẽ điền số chương theo cách thủ công sau đó điều chỉnh bố cục để danh sách cấp cao nhất không có bất kỳ phần đệm nào (do đó căn chỉnh nó với các đoạn văn) và mỗi danh sách nhúng được thụt vào bởi hai khoảng trắng. Tôi đã chọn sử dụng một 2ch giá trị đệm bởi vì tôi vẫn không chắc mình sẽ sử dụng phông chữ nào. Các ch đơn vị chiều dài cho phép vùng đệm tương đối với chiều rộng của một ký tự - bất kể phông chữ nào được sử dụng - thay vì kích thước pixel tuyệt đối có thể trông không nhất quán.

Đây là CSS tôi đã kết thúc với:

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

Sara Soueidan đã chỉ ra cho tôi rằng trình duyệt WebKit loại bỏ ngữ nghĩa danh sách khi list-style-type is none, vì vậy tôi cần thêm role="list" vào HTML để bảo vệ nó:

<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>
Dự phòng nhúng CodePen

Tạo kiểu cho tiêu đề và số trang

Với danh sách được tạo kiểu theo ý thích của tôi, đã đến lúc chuyển sang tạo kiểu cho một mục danh sách riêng lẻ. Đối với mỗi mục trong mục lục, tiêu đề và số trang phải nằm trên cùng một dòng, với tiêu đề ở bên trái và số trang được căn ở bên phải.

Bạn có thể nghĩ, "Không sao, đó là những gì flexbox dành cho!" Bạn không sai! Flexbox thực sự có thể đạt được sự căn chỉnh trang tiêu đề chính xác. Nhưng có một số vấn đề về căn chỉnh phức tạp khi các nhà lãnh đạo được thêm vào, vì vậy thay vào đó, tôi đã chọn cách tiếp cận của Christoph bằng cách sử dụng lưới, điều này như một phần thưởng vì nó cũng giúp ích cho các tiêu đề nhiều dòng. Đây là CSS cho một mục riêng lẻ:

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

Lưới có hai cột, cột đầu tiên là auto-kích thước để lấp đầy toàn bộ chiều rộng của vùng chứa, trừ đi cột thứ hai, có kích thước là max-content. Số trang được căn chỉnh ở bên phải, như truyền thống trong mục lục.

Thay đổi duy nhất mà tôi thực hiện vào thời điểm này là ẩn văn bản "Trang". Điều này hữu ích cho trình đọc màn hình nhưng không cần thiết về mặt hình ảnh, vì vậy tôi đã sử dụng truyền thống visually-hidden tốt nghiệp lớp XNUMX để ẩn nó khỏi chế độ xem:

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

Và tất nhiên, HTML cần được cập nhật để sử dụng lớp đó:

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

Với nền tảng này, tôi chuyển sang đề cập đến các nhà lãnh đạo giữa tiêu đề và trang.

Dự phòng nhúng CodePen

Tạo các nhà lãnh đạo chấm

Các nhà lãnh đạo rất phổ biến trong các phương tiện in ấn đến nỗi bạn có thể tự hỏi, tại sao CSS chưa hỗ trợ điều đó? Câu trả lời là: nó có. Vâng, loại.

Thực sự có một leader() chức năng được xác định trong Nội dung được tạo CSS cho đặc tả Phương tiện được trang. Tuy nhiên, như với nhiều thông số kỹ thuật của phương tiện được phân trang, chức năng này không được triển khai trong bất kỳ trình duyệt nào, do đó, loại trừ nó như một tùy chọn (ít nhất là tại thời điểm tôi viết bài này). Nó thậm chí không được liệt kê trên caniuse.com, có lẽ là vì không ai thực hiện nó và không có kế hoạch hoặc tín hiệu nào cho thấy họ sẽ làm.

May mắn thay, cả Julie và Christoph đều đã giải quyết vấn đề này trong các bài viết tương ứng của họ. Để chèn các nhà lãnh đạo dấu chấm, cả hai đều sử dụng ::after phần tử giả với nó content thuộc tính được đặt thành một chuỗi dấu chấm rất dài, như sau:

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

Sản phẩm ::after phần tử giả được đặt ở vị trí tuyệt đối để đưa phần tử đó ra khỏi dòng chảy của trang và tránh bao trùm sang các dòng khác. Văn bản được căn về bên phải vì chúng ta muốn các dấu chấm cuối cùng của mỗi dòng thẳng hàng với số ở cuối dòng. (Nói thêm về sự phức tạp của việc này sau.) .title phần tử được thiết lập để có một vị trí tương đối để ::after phần tử giả không thoát ra khỏi hộp của nó. Trong khi đó, overflow bị ẩn để tất cả các dấu chấm phụ đó vô hình. Kết quả là một mục lục khá đẹp với các nhà lãnh đạo chấm.

Tuy nhiên, có một số thứ khác cần được xem xét.

Sara cũng chỉ ra cho tôi rằng tất cả các dấu chấm đó được coi là văn bản cho trình đọc màn hình. Vậy bạn nghe thấy gì? “Giới thiệu chấm chấm chấm chấm chấm…” cho đến khi tất cả các dấu chấm được công bố. Đó là một trải nghiệm tồi tệ cho người dùng trình đọc màn hình.

Giải pháp là chèn một phần tử bổ sung với aria-hidden đặt thành true và sau đó sử dụng phần tử đó để chèn các dấu chấm. Vì vậy, HTML trở thành:

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

Và CSS trở thành:

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

Giờ đây, trình đọc màn hình sẽ bỏ qua các dấu chấm và giúp người dùng không phải phiền lòng khi nghe nhiều dấu chấm được công bố.

Dự phòng nhúng CodePen

Kết thúc chạm

Tại thời điểm này, thành phần mục lục trông khá tốt, nhưng nó có thể sử dụng một số công việc chi tiết nhỏ. Để bắt đầu, hầu hết các sách đều bù trừ trực quan tiêu đề chương từ tiêu đề tiểu mục, vì vậy tôi đã tô đậm các mục cấp cao nhất và giới thiệu một lề để tách các tiểu mục khỏi các chương tiếp theo:

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

Tiếp theo, tôi muốn xóa sự liên kết của các số trang. Mọi thứ trông ổn khi tôi sử dụng phông chữ có chiều rộng cố định, nhưng đối với phông chữ có chiều rộng thay đổi, các dấu chấm đầu cuối có thể tạo thành một mẫu ngoằn ngoèo khi chúng điều chỉnh theo chiều rộng của số trang. Ví dụ: bất kỳ số trang nào có số 1 sẽ hẹp hơn các số khác, dẫn đến các dấu chấm của người đứng đầu bị lệch với các dấu chấm trên các dòng trước hoặc sau.

Số và dấu chấm bị sai lệch trong mục lục.
Một mục lục hoàn hảo với HTML + CSS

Để khắc phục sự cố này, tôi đặt font-variant-numeric đến tabular-nums vì vậy tất cả các số được xử lý với cùng một chiều rộng. Cũng bằng cách đặt chiều rộng tối thiểu thành 2ch, Tôi đã đảm bảo rằng tất cả các số có một hoặc hai chữ số đều được căn chỉnh hoàn hảo. (Bạn có thể muốn đặt điều này thành 3ch nếu dự án của bạn có hơn 100 trang.) Đây là CSS cuối cùng cho số trang:

.toc-list li > a > .page { min-width: 2ch; font-variant-numeric: tabular-nums; text-align: right;
}
Người lãnh đạo căn chỉnh chấm trong mục lục.
Một mục lục hoàn hảo với HTML + CSS

Và cùng với đó, mục lục đã hoàn thành!

Dự phòng nhúng CodePen

Kết luận

Tạo một mục lục không có gì ngoài HTML và CSS là một thử thách hơn tôi mong đợi, nhưng tôi rất hài lòng với kết quả này. Cách tiếp cận này không chỉ đủ linh hoạt để chứa các chương và tiểu mục mà còn xử lý các tiểu mục một cách độc đáo mà không cần cập nhật CSS. Cách tiếp cận tổng thể hoạt động trên các trang web mà bạn muốn liên kết đến các vị trí khác nhau của nội dung, cũng như các tệp PDF mà bạn muốn mục lục liên kết đến các trang khác nhau. Và tất nhiên, nó cũng trông tuyệt vời khi in nếu bạn có xu hướng sử dụng nó trong một tập tài liệu hoặc sách.

Tôi muốn cảm ơn Julie Blanc và Christoph Grabo vì những bài đăng trên blog xuất sắc của họ về việc tạo mục lục, vì cả hai đều vô giá khi tôi bắt đầu. Tôi cũng muốn cảm ơn Sara Soueidan vì phản hồi về khả năng tiếp cận của cô ấy khi tôi làm việc trong dự án này.


Một mục lục hoàn hảo với HTML + CSS ban đầu được xuất bản trên CSS-Thủ thuật. Bạn nên nhận bản tin.

Dấu thời gian:

Thêm từ Thủ thuật CSS