O cenário é quase sempre o mesmo, que é uma tabela de dados dentro de um contêiner rolável. Cada linha possui um menu de ação, um pequeno menu suspenso com algumas opções, como Editar, Duplicar e Excluir. Você o constrói, parece funcionar perfeitamente isoladamente, e então alguém o coloca dentro daquela div rolável e as coisas desmoronam. Eu vi exatamente esse bug em três bases de código diferentes: o contêiner, a pilha e a estrutura, todas diferentes. O bug, porém, é totalmente idêntico. A lista suspensa fica cortada na borda do contêiner. Ou aparece atrás de conteúdo que logicamente deveria estar abaixo dele. Ou funciona bem até o usuário rolar e depois flutua. Você alcança o índice z: 9999. Às vezes ajuda, mas outras vezes não faz absolutamente nada. Essa inconsistência é a primeira pista de que algo mais profundo está acontecendo. A razão pela qual ele continua voltando é que três sistemas de navegador separados estão envolvidos, e a maioria dos desenvolvedores entende cada um por si só, mas nunca pensa no que acontece quando todos os três colidem: estouro, empilhamento de contextos e blocos contendo.

Depois de entender como os três interagem, os modos de falha deixam de parecer aleatórios. Na verdade, eles se tornam previsíveis. As três coisas que realmente causam isso Vejamos cada um desses itens em detalhes. O problema do estouro Quando você define overflow: hidden, overflow: scroll ou overflow: auto em um elemento, o navegador cortará qualquer coisa que se estenda além de seus limites, incluindo descendentes posicionados de forma absoluta. .scroll-container { estouro: automático; altura: 300px; /* Isso cortará o menu suspenso, ponto final */ }

.dropdown { posição: absoluta; /* Não importa -- ainda cortado por .scroll-container */ }

Isso me surpreendeu na primeira vez que o encontrei. Eu presumi que position: absoluto permitiria que um elemento escapasse do recorte de um contêiner. Isso não acontece. Na prática, isso significa que um menu posicionado de forma absoluta pode ser cortado por qualquer ancestral que tenha um valor de overflow não visível, mesmo que esse ancestral não seja o bloco que contém o menu. O recorte e o posicionamento são sistemas separados. Acontece que eles colidem de maneiras que parecem completamente aleatórias até que você entenda ambos.

Aqui está um exemplo do React usando createPortal:

importar {criarPortal} de 'react-dom'; importar {useState, useEffect, useRef} de 'react';

function Dropdown({ âncoraRef, isOpen, filhos }) { const [posição, setPosition] = useState({ topo: 0, esquerda: 0 });

useEffect(() => { if (isOpen && âncoraRef.atual) { const rect = âncoraRef.current.getBoundingClientRect(); setPosição({ topo: rect.bottom + window.scrollY, esquerda: rect.left + window.scrollX, }); } }, [isOpen, âncoraRef]);

if (!isOpen) retornar nulo;

return criarPortal(

, documento.corpo ); }

E, claro, não podemos ignorar a acessibilidade. Os elementos fixos que aparecem sobre o conteúdo ainda devem ser acessíveis pelo teclado. Se a ordem de foco não passar naturalmente para o menu suspenso fixo, você precisará gerenciá-la usando código. Também vale a pena verificar se ele não fica acima de outro conteúdo interativo e não há como descartá-lo. Esse te morde nos testes de teclado. Posicionamento de âncora CSS: para onde acho que isso está indo Posicionamento de âncora CSS é a direção que estou mais interessado no momento. Eu não tinha certeza de quanto da especificação era realmente utilizável quando a observei pela primeira vez. Ele permite declarar o relacionamento entre um menu suspenso e seu gatilho diretamente em CSS, e o navegador manipula as coordenadas. .trigger { nome da âncora: --my-trigger; }

.menu suspenso { posição: absoluta; âncora de posição: --my-trigger; topo: âncora(inferior); esquerda: âncora(esquerda); position-try-fallbacks: flip-block, flip-inline; }

A propriedade position-try-fallbacks é o que faz com que valha a pena usar isso em um cálculo manual. O navegador tenta posicionamentos alternativos antes de desistir, então um menu suspenso na parte inferior da janela de visualização vira automaticamente para cima em vez de ser cortado. O suporte ao navegador é sólido em navegadores baseados em Chromium e está crescendo no Safari. O Firefox precisa de um polyfill. O pacote @oddbird/css-anchor-positioning cobre as especificações principais. Eu encontrei casos extremos de layout que exigiam alternativas que eu não esperava, então trate-o como um aprimoramento progressivo ou combine-o com umSubstituição de JavaScript para Firefox. Em suma, promissor, mas ainda não universal. Teste em seus navegadores de destino. E no que diz respeito à acessibilidade, declarar um relacionamento visual em CSS não diz nada à árvore de acessibilidade. aria-controls, aria-expanded, aria-haspopup - essa parte ainda depende de você. Às vezes, a solução é apenas mover o elemento Antes de acessar um portal ou fazer cálculos de coordenadas, sempre faço uma pergunta primeiro: esse menu suspenso realmente precisa ficar dentro do contêiner de rolagem? Caso contrário, mover a marcação para um wrapper de nível superior elimina totalmente o problema, sem JavaScript e sem cálculos de coordenadas. Isso nem sempre é possível. Se o botão e o menu suspenso estiverem encapsulados no mesmo componente, mover um sem o outro significa repensar toda a API. Mas quando você consegue fazer isso, não há nada para depurar. O problema simplesmente não existe. O que o CSS moderno ainda não resolve O CSS já percorreu um longo caminho até aqui, mas ainda há lugares onde ele deixa você na mão. A posição: questões fixas e de transformação ainda existem. Está intencionalmente nas especificações, o que significa que não existe nenhuma solução alternativa de CSS. Se você estiver usando uma biblioteca de animação que envolve seu layout em um elemento transformado, você voltará a precisar de portais ou posicionamento de âncora. O Posicionamento de Âncora CSS é promissor, mas novo. Conforme mencionado anteriormente, o Firefox ainda precisa de um polyfill no momento em que escrevo isto. Encontrei casos extremos de layout que exigiam alternativas que não previ. Se você precisa de um comportamento consistente em todos os navegadores hoje, ainda está recorrendo ao JavaScript para as partes complicadas. A adição pela qual mudei meu fluxo de trabalho foi a API HTML Popover, agora disponível em todos os navegadores modernos. Elementos com o atributo popover são renderizados na camada superior do navegador, acima de tudo, sem necessidade de posicionamento de JavaScript.

Manipulação de escape, dispensa ao clicar fora e semântica de acessibilidade sólida são gratuitos para itens como dicas de ferramentas, widgets de divulgação e sobreposições simples. É a primeira ferramenta que alcanço agora. Dito isto, não resolve o posicionamento. Resolve camadas. Você ainda precisa de posicionamento de âncora ou JavaScript para alinhar um popover ao seu gatilho. A API Popover cuida das camadas. O posicionamento da âncora cuida do posicionamento. Usados ​​juntos, eles cobrem a maior parte do que você faria anteriormente em uma biblioteca. Um guia de decisão para sua situação Depois de passar por tudo isso da maneira mais difícil, é assim que penso sobre a escolha agora.

Use um portal. Eu usaria isso quando o gatilho residisse profundamente em contêineres de rolagem aninhados. Usei esse padrão para menus de ação de tabela e combinei-o com restauração de foco e verificações de acessibilidade. É a opção mais confiável, mas reserve tempo para a fiação extra. Use posicionamento fixo. Isso é para quando você estiver em JavaScript simples ou em uma estrutura leve e puder verificar se nenhum ancestral aplica transformações ou filtros. É simples de configurar e depurar, desde que essa restrição seja válida. Use CSS Anchor Positioning. Alcance isso quando o suporte do seu navegador permitir. Se o suporte do Firefox for necessário, combine-o com o polyfill @oddbird. É para onde a plataforma está se dirigindo e eventualmente se tornará sua abordagem preferida. Reestruture o DOM. Use-o quando a arquitetura permitir e você desejar zero complexidade de tempo de execução. Acredito que seja provavelmente a opção mais subestimada. Combine padrões. Faça isso quando desejar o posicionamento da âncora como sua abordagem principal, emparelhado com um substituto de JavaScript para navegadores não suportados. Ou um portal para posicionamento de DOM emparelhado com getBoundingClientRect() para precisão de coordenadas.

Conclusão Eu costumava tratar esse bug como um problema único – algo para corrigir e seguir em frente. Mas depois que fiquei sentado por tempo suficiente para entender todos os três sistemas envolvidos - recorte de overflow, contextos de empilhamento e blocos contendo - ele parou de parecer aleatório. Eu poderia olhar para um menu suspenso quebrado e rastrear imediatamente qual ancestral foi o responsável. Essa mudança na forma como leio o DOM foi a verdadeira conclusão. Não existe uma única resposta certa. O que eu buscava dependia do que eu poderia controlar na base de código: portais quando a árvore ancestral era imprevisível; posicionamento fixo quando era limpo e simples; mover o elemento quando nada me impedia; e posicionamento da âncora agora,onde posso. Seja qual for a sua escolha, não trate a acessibilidade como a última etapa. Na minha experiência, é exatamente nesse momento que ele é ignorado. Os relacionamentos ARIA, o gerenciamento de foco, o comportamento do teclado – isso não é polido. Eles são parte do que faz a coisa realmente funcionar. Confira o código-fonte completo em meu repositório GitHub. Leitura adicional Estas são as referências às quais voltei enquanto trabalhava nisso:

O contexto de empilhamento (MDN) “Guia de posicionamento de âncora CSS”, Juan Diego Rodriguez “Introdução à API Popover”, Godstime Aburu UI flutuante (floating-ui.com) Estouro de CSS (MDN)

You May Also Like

Enjoyed This Article?

Get weekly tips on growing your audience and monetizing your content — straight to your inbox.

No spam. Join 138,000+ creators. Unsubscribe anytime.

Create Your Free Bio Page

Join 138,000+ creators on Seemless.

Get Started Free