Um índice perfeito com HTML + CSS PlatoBlockchain Data Intelligence. Pesquisa vertical. Ai.

Um índice perfeito com HTML + CSS

No início deste ano, publiquei um e-book chamado Entendendo as promessas do JavaScript (grátis para download). Mesmo que eu não tivesse nenhuma intenção de transformá-lo em um livro impresso, um número suficiente de pessoas entrou em contato perguntando sobre uma versão impressa que eu decidi publicá-lo também. Achei que seria um exercício fácil usar HTML e CSS para gerar um PDF e enviá-lo para a impressora. O que eu não percebi é que não tinha resposta para uma parte importante de um livro impresso: o índice.

A composição de um índice

Em sua essência, um índice é bastante simples. Cada linha representa uma parte de um livro ou página da Web e indica onde você pode encontrar esse conteúdo. Normalmente, as linhas contêm três partes:

  1. O título do capítulo ou seção
  2. Líderes (ou seja, aqueles pontos, traços ou linhas) que conectam visualmente o título ao número da página
  3. O número da página

Um índice é fácil de gerar dentro de ferramentas de processamento de texto como Microsoft Word ou Google Docs, mas como meu conteúdo estava em Markdown e depois transformado em HTML, essa não era uma boa opção para mim. Eu queria algo automatizado que funcionasse com HTML para gerar o índice em um formato adequado para impressão. Eu também queria que cada linha fosse um link para que pudesse ser usado em páginas da Web e PDFs para navegar pelo documento. Eu também queria líderes de ponto entre o título e o número da página.

E assim comecei a pesquisar.

Me deparei com dois excelentes posts sobre a criação de um índice com HTML e CSS. O primeiro foi “Construa um índice a partir do seu HTML” por Julie Blanc. Julie trabalhou PagedJS, um polyfill para recursos de mídia paginada ausentes em navegadores da Web que formata corretamente os documentos para impressão. Comecei com o exemplo de Julie, mas descobri que não funcionou para mim. Em seguida, encontrei Christoph Grabo's “Linhas líderes de TOC responsivas com CSS” post, que introduziu o conceito de usar CSS Grid (em oposição à abordagem baseada em float de Julie) para facilitar o alinhamento. Mais uma vez, porém, sua abordagem não foi muito adequada para os meus propósitos.

Depois de ler esses dois posts, no entanto, senti que tinha uma compreensão boa o suficiente dos problemas de layout para embarcar no meu próprio. Usei partes de ambas as postagens do blog, além de adicionar alguns novos conceitos de HTML e CSS à abordagem para chegar a um resultado com o qual estou feliz.

Escolhendo a marcação correta

Ao decidir sobre a marcação correta para um índice, pensei principalmente na semântica correta. Fundamentalmente, um índice é sobre um título (capítulo ou subseção) vinculado a um número de página, quase como um par de valores-chave. Isso me levou a duas opções:

  • Uma opção é usar uma tabela (<table>) com uma coluna para o título e uma coluna para a página.
  • Depois, há a lista de definições frequentemente não utilizada e esquecida (<dl>) elemento. Ele também atua como um mapa de valor-chave. Então, mais uma vez, a relação entre o título e o número da página seria óbvia.

Qualquer uma dessas parecia uma boa opção até eu perceber que eles realmente só funcionam para índices de nível único, ou seja, apenas se eu quisesse ter um índice com apenas nomes de capítulos. Se eu quisesse mostrar subseções no índice, porém, não tinha boas opções. Elementos de tabela não são bons para dados hierárquicos, e enquanto as listas de definição podem tecnicamente ser aninhadas, a semântica não parecia correta. Então, voltei para a prancheta.

Decidi partir da abordagem de Julie e usar uma lista; no entanto, optei por uma lista ordenada (<ol>) em vez de uma lista não ordenada (<ul>). Eu acho que uma lista ordenada é mais apropriada neste caso. Um índice representa uma lista de capítulos e subtítulos na ordem em que aparecem no conteúdo. A ordem importa e não deve se perder na marcação.

Infelizmente, usar uma lista ordenada significa perder a relação semântica entre o título e o número da página, então meu próximo passo foi restabelecer essa relação dentro de cada item da lista. A maneira mais fácil de resolver isso é simplesmente inserir a palavra “página” antes do número da página. Dessa forma, fica clara a relação do número em relação ao texto, mesmo sem qualquer outra distinção visual.

Aqui está um esqueleto HTML simples que formou a base da minha marcação:

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

Aplicando estilos ao índice

Depois de estabelecer a marcação que planejava usar, o próximo passo foi aplicar alguns estilos.

Primeiro, removi os números gerados automaticamente. Você pode optar por manter os números gerados automaticamente em seu próprio projeto, se desejar, mas é comum que os livros tenham prefácios e posfácios não numerados incluídos na lista de capítulos, o que torna os números gerados automaticamente incorretos.

Para o meu propósito, eu preencheria os números dos capítulos manualmente e depois ajustaria o layout para que a lista de nível superior não tivesse nenhum preenchimento (alinhando-a assim com os parágrafos) e cada lista incorporada fosse recuada por dois espaços. Eu optei por usar um 2ch valor de preenchimento porque eu ainda não tinha certeza de qual fonte eu usaria. o ch A unidade de comprimento permite que o preenchimento seja relativo à largura de um caractere - não importa qual fonte seja usada - em vez de um tamanho de pixel absoluto que pode parecer inconsistente.

Aqui está o CSS que acabei com:

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

Sara Soueidan apontou para mim que os navegadores WebKit removem a semântica da lista quando list-style-type is none, então eu precisava adicionar role="list" no HTML para preservá-lo:

<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>
Retorno de Incorporação do CodePen

Estilizando o título e o número da página

Com a lista estilizada ao meu gosto, era hora de passar a estilizar um item de lista individual. Para cada item do índice, o título e o número da página devem estar na mesma linha, com o título à esquerda e o número da página alinhado à direita.

Você pode estar pensando: “Sem problemas, é para isso que serve o flexbox!” Você não está errado! O Flexbox pode de fato alcançar o alinhamento correto da página de rosto. Mas há alguns problemas de alinhamento complicados quando os líderes são adicionados, então optei por seguir a abordagem de Christoph usando uma grade, que é um bônus, pois também ajuda com títulos de várias linhas. Aqui está o CSS para um item 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;
}

A grade tem duas colunas, a primeira das quais é auto-dimensionado para preencher toda a largura do contêiner, menos a segunda coluna, que é dimensionada para max-content. O número da página é alinhado à direita, como é tradicional em um índice.

A única outra mudança que fiz neste momento foi ocultar o texto “Página”. Isso é útil para leitores de tela, mas visualmente desnecessário, então usei um tradicional visually-hidden classe para ocultá-lo da vista:

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

E, claro, o HTML precisa ser atualizado para usar essa 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>

Com essa base no lugar, passei a me dirigir aos líderes entre o título e a página.

Retorno de Incorporação do CodePen

Criando líderes de ponto

Os líderes são tão comuns na mídia impressa que você pode estar se perguntando, por que o CSS ainda não suporta isso? A resposta é: ele faz. Bem, tipo isso.

Existe realmente um leader() função definida no Conteúdo gerado por CSS para especificação de mídia paginada. No entanto, como em grande parte das especificações de mídia paginada, essa função não é implementada em nenhum navegador, portanto, excluindo-a como uma opção (pelo menos no momento em que estou escrevendo isso). Não está nem listado caniuse. com, presumivelmente porque ninguém o implementou e não há planos ou sinais de que o farão.

Felizmente, Julie e Christoph já abordaram esse problema em seus respectivos posts. Para inserir os líderes de ponto, ambos usaram um ::after pseudo-elemento com seu content propriedade definida como uma longa sequência de pontos, como esta:

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

A ::after pseudo-elemento é definido para uma posição absoluta para tirá-lo do fluxo da página e evitar a quebra de outras linhas. O texto é alinhado à direita porque queremos que os últimos pontos de cada linha fiquem alinhados com o número no final da linha. (Mais sobre as complexidades disso mais tarde.) O .title elemento é definido para ter uma posição relativa para que o ::after pseudo-elemento não sai de sua caixa. Enquanto isso, o overflow está oculto para que todos os pontos extras fiquem invisíveis. O resultado é um belo sumário com líderes de ponto.

No entanto, há algo mais que precisa ser considerado.

Sara também apontou para mim que todos esses pontos contam como texto para leitores de tela. Então o que você ouve? “Introdução ponto ponto ponto ponto…” até que todos os pontos sejam anunciados. Essa é uma experiência terrível para usuários de leitores de tela.

A solução é inserir um elemento adicional com aria-hidden definido para true e, em seguida, use esse elemento para inserir os pontos. Então o HTML se torna:

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

E o CSS se torna:

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

Agora os leitores de tela ignorarão os pontos e pouparão os usuários da frustração de ouvir vários pontos sendo anunciados.

Retorno de Incorporação do CodePen

Toques finais

Neste ponto, o componente de índice parece muito bom, mas pode precisar de alguns detalhes menores. Para começar, a maioria dos livros desloca visualmente os títulos dos capítulos dos títulos das subseções, então coloquei os itens de nível superior em negrito e introduzi uma margem para separar as subseções dos capítulos seguintes:

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

Em seguida, eu queria limpar o alinhamento dos números de página. Tudo parecia bem quando eu estava usando uma fonte de largura fixa, mas para fontes de largura variável, os pontos líderes podem acabar formando um padrão em ziguezague à medida que se ajustam à largura de um número de página. Por exemplo, qualquer número de página com 1 seria mais estreito do que outros, resultando em pontos líderes desalinhados com os pontos nas linhas anteriores ou seguintes.

Números e pontos desalinhados em um índice.
Um índice perfeito com HTML + CSS

Para corrigir esse problema, configurei font-variant-numeric para tabular-nums então todos os números são tratados com a mesma largura. Ao definir também a largura mínima para 2ch, assegurei que todos os números com um ou dois dígitos estejam perfeitamente alinhados. (Você pode querer definir isso para 3ch se o seu projeto tiver mais de 100 páginas.) Aqui está o CSS final para o número da página:

.toc-list li > a > .page { min-width: 2ch; font-variant-numeric: tabular-nums; text-align: right;
}
Pontos líderes alinhados em um índice.
Um índice perfeito com HTML + CSS

E com isso, o índice está completo!

Retorno de Incorporação do CodePen

Conclusão

Criar um índice com nada além de HTML e CSS foi um desafio maior do que eu esperava, mas estou muito feliz com o resultado. Essa abordagem não é apenas flexível o suficiente para acomodar capítulos e subseções, mas também lida bem com subsubseções sem atualizar o CSS. A abordagem geral funciona em páginas da Web em que você deseja vincular os vários locais de conteúdo, bem como PDFs em que deseja que o índice seja vinculado a diferentes páginas. E, claro, também fica ótimo impresso se você estiver inclinado a usá-lo em uma brochura ou livro.

Gostaria de agradecer a Julie Blanc e Christoph Grabo por suas excelentes postagens no blog sobre a criação de um índice, pois ambos foram inestimáveis ​​quando eu estava começando. Também gostaria de agradecer a Sara Soueidan por seu feedback de acessibilidade enquanto trabalhava neste projeto.


Um índice perfeito com HTML + CSS publicado originalmente em Truques de CSS. Você deve receba o boletim informativo.

Carimbo de hora:

Mais de Truques CSS