Como você conecta um elemento a outro? Você pode tentar rastrear as posições ou usar algum tipo de elemento wrapper.
<!-- index.html -->
<div class="container">
<a href="/link" class="anchor">I’m the anchor</a>
<div class="anchored">I’m the anchored thing</div>
</div>
/* styles.css */
.container {
position: relative;
}
.anchored {
position: absolute;
}
Essas soluções geralmente não são ideais. Eles precisam de JavaScript ou de uma marcação extra. A API de posicionamento de âncora CSS tem como objetivo resolver esse problema, fornecendo uma API CSS para elementos de tethering. Ele fornece um meio de posicionar e dimensionar um elemento com base na posição e no tamanho de outros elementos.
Suporte ao navegador
É possível testar a API de posicionamento de âncora CSS no Chrome Canary com a flag "Experimental Web Platform Features". Para ativar essa flag, abra o Chrome Canary e acesse chrome://flags
. Em seguida, ative a flag "Recursos experimentais da plataforma da Web".
Há também um polyfill em desenvolvimento pela equipe da Oddbird. Confira o repositório em github.com/oddbird/css-anchor-positioning.
Você pode verificar o suporte à ancoragem com:
@supports(anchor-name: --foo) {
/* Styles... */
}
Essa API ainda está em fase experimental e pode mudar. Este artigo aborda as partes importantes de forma geral. A implementação atual também não está totalmente sincronizada com a especificação do Grupo de trabalho do CSS.
O problema
Por que você precisa fazer isso? Um caso de uso importante seria criar dicas ou experiências semelhantes a elas. Nesse caso, você geralmente quer vincular a dica de ferramenta ao conteúdo a que ela se refere. Muitas vezes, é necessário amarrar um elemento a outro. Você também espera que a interação com a página não quebre essa vinculação, por exemplo, se um usuário rolar ou redimensionar a interface.
Outra parte do problema é se você quiser garantir que o elemento vinculado permaneça visível. Por exemplo, se você abrir uma dica e ela for cortada pelos limites da viewport. Isso pode não ser uma ótima experiência para os usuários. Você quer que a dica de ferramenta se adapte.
Soluções atuais
No momento, há algumas maneiras diferentes de abordar o problema.
Primeiro, a abordagem rudimentar de "envolver a âncora". Você pega os dois elementos e os envolve em um contêiner. Em seguida, use position
para posicionar a dica de ferramenta em relação à âncora.
<div class="containing-block">
<div class="tooltip">Anchor me!</div>
<a class="anchor">The anchor</a>
</div>
.containing-block {
position: relative;
}
.tooltip {
position: absolute;
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%);
}
Você pode mover o contêiner, e tudo vai permanecer onde você quer, na maior parte.
Outra abordagem é se você souber a posição da âncora ou puder rastreá-la de alguma forma. Você pode transmiti-lo para a dica com propriedades personalizadas.
<div class="tooltip">Anchor me!</div>
<a class="anchor">The anchor</a>
:root {
--anchor-width: 120px;
--anchor-top: 40vh;
--anchor-left: 20vmin;
}
.anchor {
position: absolute;
top: var(--anchor-top);
left: var(--anchor-left);
width: var(--anchor-width);
}
.tooltip {
position: absolute;
top: calc(var(--anchor-top));
left: calc((var(--anchor-width) * 0.5) + var(--anchor-left));
transform: translate(-50%, calc(-100% - 10px));
}
Mas e se você não souber a posição da âncora? Provavelmente, você vai precisar intervir com o JavaScript. Você pode fazer algo como o código a seguir, mas isso significa que seus estilos estão começando a vazar do CSS para o JavaScript.
const setAnchorPosition = (anchored, anchor) => {
const bounds = anchor.getBoundingClientRect().toJSON();
for (const [key, value] of Object.entries(bounds)) {
anchored.style.setProperty(`--${key}`, value);
}
};
const update = () => {
setAnchorPosition(
document.querySelector('.tooltip'),
document.querySelector('.anchor')
);
};
window.addEventListener('resize', update);
document.addEventListener('DOMContentLoaded', update);
Isso começa a levantar algumas questões:
- Quando devo calcular os estilos?
- Como calcular os estilos?
- Com que frequência preciso calcular os estilos?
Isso resolveu o problema? Talvez para seu caso de uso, mas há um problema: nossa solução não se adapta. Ele não responde. E se o elemento ancorado for cortado pela viewport?
Agora você precisa decidir se vai reagir a isso e como. O número de perguntas e decisões que você precisa tomar está começando a crescer. Você só quer ancorar um elemento a outro. E, em um mundo ideal, sua solução vai se ajustar e reagir ao ambiente.
Para facilitar um pouco, você pode usar uma solução em JavaScript. Isso vai gerar o custo de adicionar uma dependência ao projeto e pode causar problemas de desempenho, dependendo de como você as usa. Por exemplo, alguns pacotes usam requestAnimationFrame
para manter a posição correta. Isso significa que você e sua equipe precisam se familiarizar com o pacote e as opções de configuração dele. Como resultado, suas perguntas e decisões podem não ser reduzidas, mas alteradas. Isso faz parte do "porquê" do posicionamento de âncora do CSS. Isso vai ajudar você a não pensar em problemas de desempenho ao calcular a posição.
Confira como o código pode ficar ao usar "floating-ui", um pacote conhecido para esse problema:
import {computePosition, flip, offset, autoUpdate} from 'https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.2.1/+esm';
const anchor = document.querySelector('.anchor')
const tooltip = document.querySelector('.tooltip')
const updatePosition = () => {
computePosition(anchor, tooltip, {
placement: 'top',
middleware: [offset(10), flip()]
})
.then(({x, y}) => {
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`
})
})
};
const clean = autoUpdate(anchor, tooltip, updatePosition);
Tente reposicionar a âncora nesta demonstração que usa esse código.
A "dica" pode não se comportar como você espera. Ele reage ao sair da viewport no eixo y, mas não no eixo x. Leia a documentação e provavelmente vai encontrar uma solução que funcione para você.
No entanto, encontrar um pacote que funcione para seu projeto pode levar muito tempo. São decisões extras e podem ser frustrantes se não funcionarem como você quer.
Como usar o posicionamento de âncora
Insira a API de posicionamento de âncora do CSS. A ideia é manter os estilos no CSS e reduzir o número de decisões que você precisa tomar. Você espera alcançar o mesmo resultado, mas o objetivo é melhorar a experiência do desenvolvedor.
- Não é necessário usar JavaScript.
- Deixe o navegador encontrar a melhor posição com base na sua orientação.
- Não há mais dependências de terceiros
- Sem elementos de wrapper.
- Funciona com elementos que estão na camada superior.
Vamos recriar e resolver o problema que estávamos tentando resolver acima. Em vez disso, use a analogia de um barco com uma âncora. Eles representam o elemento ancorado e a âncora. A água representa o bloco que contém o objeto.
Primeiro, você precisa escolher como definir a âncora. Para fazer isso no CSS, defina a propriedade anchor-name
no elemento âncora. Ele aceita um valor dashed-ident.
.anchor {
anchor-name: --my-anchor;
}
Como alternativa, é possível definir uma âncora no HTML com o atributo anchor
. O valor do atributo é o ID do elemento âncora. Isso cria uma âncora implícita.
<a id="my-anchor" class="anchor"></a>
<div anchor="my-anchor" class="boat">I’m a boat!</div>
Depois de definir uma âncora, você pode usar a função anchor
. A função anchor
usa três argumentos:
- Elemento âncora:o
anchor-name
da âncora a ser usada. Você também pode omitir o valor para usar uma âncoraimplicit
. Ele pode ser definido pela relação HTML ou com uma propriedadeanchor-default
com um valoranchor-name
. - Lado do âncora: uma palavra-chave da posição que você quer usar. Pode ser
top
,right
,bottom
,left
,center
etc. Ou você pode transmitir uma porcentagem. Por exemplo, 50% seria igual acenter
. - Padrão: é um valor de fallback opcional que aceita um comprimento ou uma porcentagem.
Use a função anchor
como um valor para as propriedades de inserção (top
, right
, bottom
, left
ou equivalentes lógicos) do elemento ancorado. Também é possível usar a função anchor
em calc
:
.boat {
bottom: anchor(--my-anchor top);
left: calc(anchor(--my-anchor center) - (var(--boat-size) * 0.5));
}
/* alternative with anchor-default */
.boat {
anchor-default: --my-anchor;
bottom: anchor(top);
left: calc(anchor(center) - (var(--boat-size) * 0.5));
}
Não há uma propriedade de inserção center
. Uma opção é usar calc
se você souber o tamanho do elemento ancorado. Por que não usar translate
? Você pode usar:
.boat {
anchor-default: --my-anchor;
bottom: anchor(top);
left: anchor(center);
translate: -50% 0;
}
No entanto, o navegador não considera as posições transformadas para elementos ancorados. Vai ficar claro por que isso é importante ao considerar posições de fallback e posicionamento automático.
Você pode ter notado o uso da propriedade personalizada --boat-size
acima. No entanto, se você quiser basear o tamanho do elemento ancorado no da âncora, também poderá acessar esse tamanho. Em vez de calcular por conta própria, use a função anchor-size
. Por exemplo, para fazer o barco quatro vezes a largura da âncora:
.boat {
width: calc(4 * anchor-size(--my-anchor width));
}
Você também tem acesso à altura com anchor-size(--my-anchor height)
. E você pode usá-lo para definir o tamanho de um ou de ambos os eixos.
E se você quiser ancorar em um elemento com posicionamento absolute
? A regra é que os elementos não podem ser irmãos. Nesse caso, é possível envolver a âncora com um contêiner que tenha posicionamento relative
. Em seguida, você pode ancorar nele.
<div class="anchor-wrapper">
<a id="my-anchor" class="anchor"></a>
</div>
<div class="boat">I’m a boat!</div>
Confira esta demonstração em que você pode arrastar a âncora e o barco vai seguir.
Rastrear a posição de rolagem
Em alguns casos, o elemento âncora pode estar dentro de um contêiner de rolagem. Ao mesmo tempo, o elemento ancorado pode estar fora desse contêiner. Como a rolagem acontece em uma linha de execução diferente do layout, é necessário ter uma maneira de acompanhar isso. A propriedade anchor-scroll
pode fazer isso. Você define o elemento ancorado e atribui a ele o valor da âncora que quer acompanhar.
.boat { anchor-scroll: --my-anchor; }
Teste esta demonstração em que você pode ativar e desativar anchor-scroll
com a caixa de seleção no canto.
A analogia não é perfeita, porque em um mundo ideal, o barco e a âncora estão na água. Além disso, recursos como a API Popover permitem manter elementos relacionados próximos. O posicionamento da âncora funciona com elementos que estão na camada superior. Esse é um dos principais benefícios da API: poder vincular elementos em fluxos diferentes.
Considere esta demonstração que tem um contêiner de rolagem com âncoras que têm dicas de ferramentas. Os elementos de dica de ferramenta que são pop-ups não podem ser colocalizados com as âncoras:
No entanto, você vai notar como os pop-ups rastreiam os respectivos links de âncora. Você pode redimensionar esse contêiner de rolagem, e as posições serão atualizadas.
Posicionamento substituto e automático
É aqui que o poder de posicionamento de âncoras aumenta. Um position-fallback
pode posicionar o elemento ancorado com base em um conjunto de substitutos fornecidos. Você guia o navegador com seus estilos e deixa que ele resolva a posição.
O caso de uso comum aqui é uma dica que precisa alternar entre aparecer acima ou abaixo de uma âncora. Esse comportamento é baseado na possibilidade de a dica ser cortada pelo contêiner. Esse contêiner geralmente é a janela de visualização.
Se você analisou o código da última demonstração, deve ter notado que havia uma propriedade position-fallback
em uso. Se você rolou o contêiner, talvez tenha notado que os pop-ups fixados pularam. Isso aconteceu quando as respectivas âncoras se aproximaram do limite da viewport. Nesse momento, os pop-ups estão tentando se ajustar para permanecer na janela de visualização.
Antes de criar um position-fallback
explícito, o posicionamento de âncora também oferece o posicionamento automático. Você pode fazer essa inversão sem custo financeiro usando um valor de auto
na função de âncora e na propriedade de inseto oposto. Por exemplo, se você usar anchor
para bottom
, defina top
como auto
.
.tooltip {
position: absolute;
bottom: anchor(--my-anchor auto);
top: auto;
}
A alternativa ao posicionamento automático é usar um position-fallback
explícito. Para isso, você precisa definir um conjunto de posição de substituição. O navegador vai passar por elas até encontrar uma que possa usar e, em seguida, aplicar essa posição. Se não encontrar uma que funcione, o padrão será o primeiro definido.
Um position-fallback
que tenta mostrar as dicas acima e abaixo pode ter esta aparência:
@position-fallback --top-to-bottom {
@try {
bottom: anchor(top);
left: anchor(center);
}
@try {
top: anchor(bottom);
left: anchor(center);
}
}
A aplicação disso nas dicas de ferramentas fica assim:
.tooltip {
anchor-default: --my-anchor;
position-fallback: --top-to-bottom;
}
O uso de anchor-default
significa que você pode reutilizar o position-fallback
para outros elementos. Também é possível usar uma propriedade personalizada com escopo para definir anchor-default
.
Considere esta demonstração usando o barco novamente. Há um position-fallback
definido. À medida que você muda a posição da âncora, o barco se ajusta para permanecer dentro do contêiner. Tente mudar o valor do padding, que ajusta o padding do corpo. Observe como o navegador corrige o posicionamento. As posições são alteradas ao mudar o alinhamento da grade do contêiner.
O position-fallback
é mais detalhado desta vez, tentando posições no sentido horário.
.boat {
anchor-default: --my-anchor;
position-fallback: --compass;
}
@position-fallback --compass {
@try {
bottom: anchor(top);
right: anchor(left);
}
@try {
bottom: anchor(top);
left: anchor(right);
}
@try {
top: anchor(bottom);
right: anchor(left);
}
@try {
top: anchor(bottom);
left: anchor(right);
}
}
Exemplos
Agora que você já tem uma ideia dos principais recursos para posicionamento de âncora, vamos conferir alguns exemplos interessantes além das dicas. O objetivo desses exemplos é ajudar você a ter ideias de como usar o posicionamento de âncora. A melhor maneira de melhorar a especificação é com a contribuição de usuários reais como você.
Menus de contexto
Vamos começar com um menu de contexto usando a API Popover. A ideia é que clicar no botão com a seta revela um menu de contexto. E esse menu terá um menu próprio para expandir.
A marcação não é a parte importante aqui. No entanto, você tem três botões usando popovertarget
. Em seguida, você tem três elementos usando o atributo popover
. Isso permite abrir os menus de contexto sem JavaScript. Ela pode ser assim:
<button popovertarget="context">
Toggle Menu
</button>
<div popover="auto" id="context">
<ul>
<li><button>Save to your Liked Songs</button></li>
<li>
<button popovertarget="playlist">
Add to Playlist
</button>
</li>
<li>
<button popovertarget="share">
Share
</button>
</li>
</ul>
</div>
<div popover="auto" id="share">...</div>
<div popover="auto" id="playlist">...</div>
Agora, você pode definir um position-fallback
e compartilhá-lo entre os menus de contexto. Também desmarcamos todos os estilos inset
para os pop-ups.
[popovertarget="share"] {
anchor-name: --share;
}
[popovertarget="playlist"] {
anchor-name: --playlist;
}
[popovertarget="context"] {
anchor-name: --context;
}
#share {
anchor-default: --share;
position-fallback: --aligned;
}
#playlist {
anchor-default: --playlist;
position-fallback: --aligned;
}
#context {
anchor-default: --context;
position-fallback: --flip;
}
@position-fallback --aligned {
@try {
top: anchor(top);
left: anchor(right);
}
@try {
top: anchor(bottom);
left: anchor(right);
}
@try {
top: anchor(top);
right: anchor(left);
}
@try {
bottom: anchor(bottom);
left: anchor(right);
}
@try {
right: anchor(left);
bottom: anchor(bottom);
}
}
@position-fallback --flip {
@try {
bottom: anchor(top);
left: anchor(left);
}
@try {
right: anchor(right);
bottom: anchor(top);
}
@try {
top: anchor(bottom);
left: anchor(left);
}
@try {
top: anchor(bottom);
right: anchor(right);
}
}
Isso fornece uma interface de menu de contexto aninhado adaptável. Tente mudar a posição do conteúdo com a seleção. A opção escolhida atualiza o alinhamento da grade. Isso afeta a forma como o posicionamento de âncora posiciona os pop-ups.
Foco e acompanhamento
Esta demonstração combina primitivas CSS com :has(). A ideia é fazer a transição de um indicador visual para o input
com foco.
Para fazer isso, defina uma nova âncora no momento da execução. Nesta demonstração, uma propriedade personalizada com escopo é atualizada no foco de entrada.
#email {
anchor-name: --email;
}
#name {
anchor-name: --name;
}
#password {
anchor-name: --password;
}
:root:has(#email:focus) {
--active-anchor: --email;
}
:root:has(#name:focus) {
--active-anchor: --name;
}
:root:has(#password:focus) {
--active-anchor: --password;
}
:root {
--active-anchor: --name;
--active-left: anchor(var(--active-anchor) right);
--active-top: calc(
anchor(var(--active-anchor) top) +
(
(
anchor(var(--active-anchor) bottom) -
anchor(var(--active-anchor) top)
) * 0.5
)
);
}
.form-indicator {
left: var(--active-left);
top: var(--active-top);
transition: all 0.2s;
}
Mas como você pode levar isso adiante? Você pode usá-la para alguma forma de sobreposição instrutiva. Uma dica pode se mover entre pontos de interesse e atualizar o conteúdo. Você pode fazer um crossfade do conteúdo. Animações discretas que permitem animar display
ou transições de visualização podem funcionar aqui.
Cálculo de gráfico de barras
Outra coisa divertida que você pode fazer com o posicionamento de âncora é combiná-lo com calc
. Imagine um gráfico com alguns popovers que fazem anotações nele.
É possível acompanhar os valores mais altos e mais baixos usando o CSS min
e max
. O CSS para isso pode ficar assim:
.chart__tooltip--max {
left: anchor(--chart right);
bottom: max(
anchor(--anchor-1 top),
anchor(--anchor-2 top),
anchor(--anchor-3 top)
);
translate: 0 50%;
}
Há um pouco de JavaScript em jogo para atualizar os valores do gráfico e um pouco de CSS para estilizar o gráfico. Mas o posicionamento de âncora cuida das atualizações de layout.
Alças de redimensionamento
Não é necessário ancorar em apenas um elemento. É possível usar muitas âncoras para um elemento. Você pode ter notado isso no exemplo de gráfico de barras. As dicas de ferramentas foram ancoradas ao gráfico e à barra correspondente. Se você levar esse conceito um pouco mais adiante, poderá usá-lo para redimensionar elementos.
Você pode tratar os pontos de ancoragem como alças de redimensionamento personalizadas e usar um valor inset
.
.container {
position: absolute;
inset:
anchor(--handle-1 top)
anchor(--handle-2 right)
anchor(--handle-2 bottom)
anchor(--handle-1 left);
}
Nesta demonstração, o GreenSock Draggable torna as alças Draggable. No entanto, o elemento <img>
é redimensionado para preencher o contêiner, que se ajusta para preencher a lacuna entre as alças.
Um SelectMenu?
Este último é uma provocação do que está por vir. No entanto, é possível criar um popover com foco e agora você tem o posicionamento de âncora. Você pode criar as bases de um elemento <select>
estilizável.
<div class="select-menu">
<button popovertarget="listbox">
Select option
<svg>...</svg>
</button>
<div popover="auto" id="listbox">
<option>A</option>
<option>Styled</option>
<option>Select</option>
</div>
</div>
Um anchor
implícito facilita isso. No entanto, o CSS para um ponto de partida rudimentar pode ser parecido com este:
[popovertarget] {
anchor-name: --select-button;
}
[popover] {
anchor-default: --select-button;
top: anchor(bottom);
width: anchor-size(width);
left: anchor(left);
}
Combine os recursos da API Popover com o posicionamento de âncora do CSS e você está quase lá.
É legal quando você começa a introduzir coisas como :has()
. Você pode girar o marcador ao abrir:
.select-menu:has(:open) svg {
rotate: 180deg;
}
Para onde você pode levá-lo em seguida? O que mais precisamos para que o select
funcione? Vamos deixar isso para o próximo artigo. Mas não se preocupe, os elementos de seleção estilizáveis estão chegando. Não perca!
Pronto.
A plataforma da Web está em evolução. O posicionamento de âncora do CSS é uma parte crucial para melhorar a forma como você desenvolve controles de interface. Ele vai ajudar você a tomar decisões difíceis. Mas também permite que você faça coisas que nunca fez antes. como estilizar um elemento <select>
. Envie sua opinião.
Foto de CHUTTERSNAP no Unsplash