Um anel vazado que acompanha o ponteiro com atraso curto e cresce ao passar sobre link, botão ou bloco marcado. Não é uma dobra: é um elemento de página, que vive uma vez, como primeiro filho do <body>.
O que ele resolve: dar sensação de resposta a uma página que, fora os links, não reage a nada. É o efeito mais barato que existe para fazer um site parecer construído em vez de montado — e o mais fácil de fazer errado.
Quando usar
- Site de estúdio, portfólio, marca — onde a assinatura vale mais que a densidade de informação.
- Landing de campanha com poucos alvos e muito espaço em branco.
- Junto do
listas/lista-imagem-flutuante, que também responde ao ponteiro: os dois se somam bem porque falam da mesma mão.
Quando não usar
- Página densa de conteúdo — tabela, formulário longo, painel. O anel vira ruído sobre informação.
- Página com muito link no corpo do texto. O anel piscaria de tamanho a cada palavra sublinhada. Nesse caso use a variação alvo por marcação.
- Produto que gente usa todo dia. Efeito de ponteiro é para visita, não para ferramenta.
Padrão editorial
Não há texto a escrever. O que há é uma decisão de onde o anel cresce, e ela é editorial: crescer é a forma de o componente dizer "isto aqui é clicável".
Por padrão ele cresce sobre todo <a> e todo <button>. Numa página onde isso é demais, marque os alvos à mão com data-ptr-grow e troque o seletor — o anel passa a apontar só o que importa, e o gesto ganha significado em vez de virar reflexo.
Especificação dos assets
Nenhum. Não há imagem, ícone nem SVG.
Como usar
Cole o anel logo depois da abertura do <body>, antes de qualquer seção:
<body>
<div class="ptr-disc" data-ptr aria-hidden="true"></div>
<!-- o resto da página -->
Para marcar um alvo que não é link nem botão:
<span data-ptr-grow>bloco que o anel reconhece</span>
Ganchos
| Gancho | O quê |
|---|---|
[data-ptr] |
a raiz que o script procura. Um por página |
[data-ptr-grow] |
marca um alvo que não é <a> nem <button> |
--ptr-x / --ptr-y |
posição, escritas pelo script |
--ptr-o |
opacidade, 0 até o primeiro movimento |
--ptr-k |
escala. 1 em repouso, 2.15 sobre alvo |
--ptr-size |
diâmetro em repouso. 26px |
--ptr-z |
camada. 90 — acima do conteúdo, abaixo de menu e overlay |
.is-grande |
estado sobre alvo, aplicado pelo script |
Variações
| Variação | Como |
|---|---|
| Disco cheio | background: var(--accent) e mix-blend-mode: difference. Mais chamativo, e some sobre fundo de tom médio |
| Esconde o cursor do sistema | cursor: none no <body>, dentro da mesma media query. É o que a maioria faz — leia o gotcha antes |
| Alvo por marcação | trocar o seletor do script por apenas [data-ptr-grow] |
| Mais lento | PASSO para 0.10. Abaixo disso parece travamento |
Gotchas
-
O cursor do sistema continua visível, e isso é decisão, não esquecimento. Quase toda implementação deste padrão põe
cursor:nonee desenha um substituto. O cursor do sistema é a única coisa na tela que a pessoa configurou — tamanho, cor, contraste, rastro —, e há quem dependa dessa configuração para saber onde está. Por isso o anel aqui é vazado e acompanha: o cursor nativo aparece por dentro dele. A variação que esconde está documentada, com o preço declarado. -
Fora de qualquer
[data-rv]. O core termina o reveal emfilter:blur(0), eblur(0)continua sendo um filtro: elemento com filtro vira bloco de contenção para descendenteposition:fixed. Dentro de uma dobra revelada, o anel passaria a seguir o ponteiro em relação à dobra, e o erro só apareceria depois que ela entrasse na tela. Ver MANUAL §5. -
pointer-events:nonenão é opcional. O anel fica sempre exatamente debaixo do ponteiro. Sem essa linha, ele come todo clique da página. -
O laço dorme. Quando a distância entre o anel e o ponteiro cai abaixo de meio pixel, o
requestAnimationFramepara. Sem isso o componente seguraria um quadro vivo pela sessão inteira, com o ponteiro parado. -
closestem vez do alvo direto. Opointermoveentrega o elemento mais profundo — um<span>ou um<svg>dentro do botão. Sem subir a árvore, o anel piscaria ao passar sobre um ícone dentro do próprio alvo. -
Toque numa tela híbrida não move o anel. O
pointermovedispara também para dedo em notebook com tela sensível. O script descartapointerType === 'touch': quem toca não tem cursor a acompanhar. -
A media query de ponteiro não é "mobile no fim".
(hover:hover) and (pointer:fine)é media query de capacidade, não de largura: ela não adapta o layout, decide se o componente existe. Por isso ela não segue a regra do §6.6 — e obin/conformidade.pyconcorda, porque só conta as dewidth.
Movimento reduzido / toque
Com prefers-reduced-motion: reduce, o componente não existe: o CSS o mantém em display:none e o script sai antes de ligar qualquer listener. Não há versão degradada porque não há nada a degradar — anel que persegue o ponteiro é movimento puro, sem função informativa. Nada se perde.
No toque, idem: sem (pointer:fine) o anel nunca é desenhado, e o rAF nunca começa. É por isso que ele nasce em display:none e só aparece dentro da media query, em vez do contrário — assim celular não paga nem o custo de existir.
Histórico
| Origem | Quando | O quê |
|---|---|---|
| autoral | 2026-09-04 | Construído do zero. Padrão de domínio comum, sem referência única |
Origem. O disco ou anel que persegue o ponteiro é quase assinatura de agência — aparece em portfólio de estúdio e em página premiada há mais de uma década, sem dono. Nada foi copiado: reescrito do zero dentro do contrato desta biblioteca. O registro consolidado vai em projetos/autorais.md.
Decisões da tradução para o contrato. O cursor do sistema não é escondido, pelo motivo do primeiro gotcha — é a diferença mais importante entre este componente e o padrão de mercado. O crescimento vem por escala em transform, não por inline-size, porque transform não remonta layout, mesma razão do scaleX no navegacao/progresso-leitura. O laço dorme quando o ponteiro para. O script não liga um único listener sem ponteiro fino nem com movimento reduzido, e o CSS nasce em display:none pelo mesmo motivo. E o elemento fica fora de qualquer [data-rv], pela regra de contenção do §5.
Calibragem no primeiro uso. Espere ajustar duas coisas: o PASSO do script, que decide o quanto o anel se atrasa, e o seletor de alvo — em página com muito link no corpo do texto, a variação alvo por marcação costuma ser a certa.