Desvanecimento simples ao passar o rato usando CSS3

Desvanecimento simples ao passar o rato usando CSS3

Última verificação: 15 de outubro de 2026
19 min de leitura
Tutorial

Na era do jQuery, o fade ao passar o rato resolvia-se com .animate() ou .fadeIn(). Em 2026, isso é desnecessário para estados simples de interface: o navegador interpola opacity diretamente com CSS. O resultado é menos script, menos risco de layout thrashing e um efeito que respeita as preferências do sistema quando se liga prefers-reduced-motion.

Este artigo é uma receita prática de front-end para temas WordPress e Gutenberg. Começa com um fade mínimo ao passar o rato e passa depois por transition versus animation, :focus-visible para teclado, movimento reduzido, wp_enqueue_style correto, armadilhas típicas do CSS de blocos e a razão pela qual as propriedades tratadas pelo compositor mantêm os 60 fps de forma mais previsível do que width ou top.

#Transições CSS ou JavaScript para efeitos hover

JavaScript é a escolha certa quando o estado depende da rede, da validação de formulários ou de sequências complexas. Para “o rato passa sobre a imagem, escurece um pouco”, o CSS é mais fácil de ler e mais barato de executar.

Vantagens concretas na prática:

  • Não há event listeners para limpar em ecrãs de administração com comportamento de SPA.
  • O navegador pode tratar opacity e transform na thread do compositor quando a camada já existe.
  • O mesmo conjunto de regras serve o rato e o foco quando o estado é espelhado com :focus-visible.
  • Evitam-se conflitos entre mouseenter / mouseleave e um CSS que já está a animar.

A exceção é quando o efeito tem de sincronizar com uma camada controlada por script (por exemplo, uma lightbox própria que define aria-hidden). Mesmo aí, o fade pode ficar no CSS e o JavaScript limita-se a trocar uma classe.

#Como criar um efeito fade básico com CSS

Imagine uma imagem que deve escurecer quando o rato passa por cima. Defina o valor inicial de forma explícita, declare que propriedade deve ser interpolada e altere o valor em :hover.

.hover-image {
  opacity: 1;
  transition: opacity 0.3s ease-in-out;
}

.hover-image:hover {
  opacity: 0.7;
}

O que acontece:

  1. A imagem começa com opacity: 1 (totalmente visível).
  2. Ao passar o rato, o valor vai até 0.7 em 300 ms.
  3. A função ease-in-out suaviza o início e o fim.
  4. Quando o hover termina, a transição regressa a 1 com a mesma regra.

opacity é um número entre 0 e 1 e afeta o elemento inteiro, incluindo os filhos. Se só o fundo deve escurecer, background-color com rgba() ou color-mix() é muitas vezes mais preciso do que escurecer o cartão todo.

#Diferença entre transition e animation em CSS

transition e animation não são ferramentas intermutáveis. A escolha depende do que desencadeia o movimento.

Use transition quando:

  • Uma propriedade muda entre dois estados (hover, foco, classe aberta ou fechada).
  • O navegador deve interpolar a partir do valor atual até um valor novo.
  • O efeito deve inverter a meio caminho se o utilizador afastar o rato.

Use @keyframes / animation quando:

  • O movimento deve correr sem interação do utilizador (carregamento, pulsação discreta, introdução).
  • São precisos vários keyframes, atrasos por etapa ou animation-iteration-count.
  • O estado não é uma simples mudança de A para B.
/* Transition: guiada por mudança de estado */
.btn {
  opacity: 1;
  transition: opacity 0.2s ease-out;
}
.btn:hover {
  opacity: 0.85;
}

/* Animation: sequência temporizada */
@keyframes fade-in-once {
  from { opacity: 0; }
  to { opacity: 1; }
}
.hero-title {
  animation: fade-in-once 0.4s ease-out both;
}

Um erro comum em temas é “animar” o hover com uma animation infinita e depois lutar contra animation-fill-mode. Para hover, transition é quase sempre a solução mais limpa.

#Efeito hover com focus-visible para teclado

:hover sozinho não chega para quem navega com teclado. Espelhe o feedback visual com :focus-visible, para que a navegação com Tab tenha o mesmo fade sem que cada clique do rato deixe um anel de foco visível.

.card-media {
  opacity: 1;
  outline: none;
  transition: opacity 0.25s ease, box-shadow 0.25s ease;
}

.card-media:hover,
.card-media:focus-visible {
  opacity: 0.75;
}

.card-media:focus-visible {
  box-shadow: 0 0 0 3px color-mix(in srgb, CanvasText 40%, transparent);
}

Notas práticas:

  • Use :focus-visible, e não apenas :focus, para evitar a marcação de foco depois de um clique do rato nos navegadores modernos.
  • Garanta que o elemento pode mesmo receber foco (<a>, <button> ou tabindex="0" com tratamento de teclado).
  • Não remova outline sem o substituir. O fade sozinho não dá contraste suficiente como indicador de foco.

Em ecrãs táteis, :hover fica “preso” em alguns navegadores. Mantenha o fade discreto (0.7 a 0.9) para que um hover bloqueado não pareça um erro.

#Como desativar a animação hover com prefers-reduced-motion

Alguns utilizadores pediram ao sistema menos movimento. Respeite essa escolha com uma media query que desliga transições e animações.

@media (prefers-reduced-motion: reduce) {
  .hover-image,
  .card-media,
  .btn {
    transition: none;
    animation: none;
  }
}

Também é possível encurtar a duração em vez de anular tudo, mas para um fade em hover none funciona: o estado continua a mudar, só sem interpolação.

Teste no macOS (definição de redução de movimento nas opções de acessibilidade) e no Windows 11 (efeitos de animação nas opções de acessibilidade e efeitos visuais). Confirme que o indicador de foco continua visível com a transição desligada.

#Como compreender as propriedades de transição CSS

#Transition-property

Indique sempre propriedades concretas. transition: all é tentador, mas anima também coisas em que não pensou (por exemplo border-width ou font-size vindos de outra regra) e custa mais.

/* Preferir */
transition-property: opacity, transform;

/* Evitar em componentes de interface */
transition-property: all;

#Transition-duration

Em interfaces, 150 a 300 ms é normalmente a zona em que o efeito se nota sem parecer lento. Abaixo de 100 ms pode parecer um corte seco. Acima de 400 ms, o hover começa a parecer lento no desktop.

/* Muito rápido (subtil) */
transition-duration: 0.15s;

/* Padrão para interface */
transition-duration: 0.3s;

#Transition-timing-function

Controla a curva de aceleração. É isto que faz as animações parecerem naturais.

  • ease-out: arranque rápido, aterragem suave, muitas vezes o melhor à saída do hover.
  • ease-in-out: suavidade simétrica, adequada a um fade de opacity.
  • linear: raramente desejável em interfaces, mais útil em indicadores de progresso.
  • cubic-bezier(...): quando a marca tem uma curva de movimento própria.

#Transition-delay

O atraso é útil quando uma camada interior deve esperar por uma exterior. Num fade simples de imagem, o atraso é normalmente 0s; caso contrário, a interface parece lenta.

#Qual é a sintaxe abreviada completa de transition

A forma abreviada é property duration timing-function delay:

.hover-image {
  transition: opacity 0.3s ease-in-out 0s;
}

/* Várias propriedades */
.card-img {
  transition:
    opacity 0.25s ease-out,
    transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

Mantenha a ordem consistente em todo o tema. Quando um componente usa a forma longa e outro a abreviada com valores diferentes, a linguagem de movimento fica desarrumada.

#Como fazer efeitos hover avançados em CSS sem jQuery

#1. Fade + escala (efeito zoom)

Combine opacidade e transformação para um efeito de zoom. transform: scale() junta-se bem a opacity porque ambas podem ser aceleradas pelo compositor.

.card-img {
  opacity: 1;
  transform: scale(1);
  transition:
    opacity 0.3s ease-in-out,
    transform 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.card-img:hover,
.card-img:focus-visible {
  opacity: 0.9;
  transform: scale(1.04);
}

Evite animar width / height para o zoom. Isso obriga a layout e paint em cada frame.

#2. Fade numa imagem dentro de um contentor

Perfeito para galerias de imagens. Quando a área de hover é maior do que a imagem (o cartão inteiro), coloque :hover no contentor:

.image-container img {
  opacity: 1;
  transition: opacity 0.3s ease-in-out;
}

.image-container:hover img,
.image-container:focus-within img {
  opacity: 0.7;
}

:focus-within cobre o foco de teclado no link dentro do cartão.

#Como carregar a folha de estilos do fade no WordPress

Não coloque o CSS do fade num bloco <style> solto em header.php se o puder evitar. Use a API de enqueue do tema, para que dependências, versões e carregamento sejam previsíveis.

<?php
declare(strict_types=1);

add_action('wp_enqueue_scripts', static function (): void {
    $theme_version = wp_get_theme()->get('Version');
    $css_path = get_theme_file_path('assets/css/hover-fade.css');
    $css_ver = file_exists($css_path) ? (string) filemtime($css_path) : $theme_version;

    wp_enqueue_style(
        'theme-hover-fade',
        get_theme_file_uri('assets/css/hover-fade.css'),
        [],
        $css_ver
    );
});

Porquê este padrão:

  • filemtime invalida a cache quando o ficheiro muda, sem subir a versão à mão.
  • O handle theme-hover-fade pode ser removido ou substituído num tema filho.
  • Evitam-se duplicados se um bloco ou plugin também tentar injetar as mesmas regras.

Em temas de blocos (FSE) continua a ser possível carregar uma pequena folha utilitária, ou colocar classes utilitárias em theme.json quando faz sentido. Regras de hover maiores, com media queries, continuam a pertencer a ficheiros CSS.

Se o efeito só é necessário num modelo, condicione o enqueue com is_singular() / is_page_template() para manter o CSS crítico mais pequeno.

#Porque é que o fade não funciona em blocos Gutenberg

O Gutenberg altera a estrutura do DOM mais do que os modelos clássicos. Um fade que “funcionava no Classic” falha muitas vezes por estas razões:

#Seletor errado no wrapper

Os blocos são muitas vezes envolvidos em .wp-block-image, .wp-block-group ou figure/figcaption. Uma regra que aponta diretamente para img:hover não funciona quando o hover acontece no link que envolve a imagem.

/* Demasiado estreito em muitos layouts de blocos */
.wp-block-image img:hover {
  opacity: 0.7;
}

/* Mais robusto */
.wp-block-image a:hover img,
.wp-block-image a:focus-visible img,
.wp-block-image figure:hover img {
  opacity: 0.7;
}

#Estilos inline e imagens de conteúdo

Estilos de imagem definidos no editor (style="width:...") não competem necessariamente com opacity, mas um !important num reset do tema ou no CSS de um plugin pode competir. Mantenha a especificidade baixa e consistente; evite !important no fade.

#CSS adicional versus folha de estilos do bloco

O “CSS adicional” em Apresentação e Personalizar serve para protótipos. Em produção, use um ficheiro próprio via enqueue, ou um estilo específico do bloco registado com wp_enqueue_block_style, para que as regras só carreguem onde o bloco é usado.

#Interatividade e blocos aninhados

Blocos de grupo com links no interior podem ativar o hover no pai enquanto o foco está no filho. Teste com rato e com Tab. :focus-within no wrapper do cartão resolve muitos destes casos.

#Editor versus frontend

O canvas do editor nem sempre carrega o CSS de frontend do tema. Se o cliente “não vê o fade no editor”, isso é esperado, a não ser que também carregue os estilos via enqueue_block_assets ou add_editor_style. Documente a diferença: o efeito aparece no site publicado, não necessariamente no canvas.

#Que propriedades CSS são rápidas de animar

Animar propriedades que obrigam a layout (width, height, top, left, margin) ou a paint dispendioso (box-shadow em superfícies enormes sem camada própria) arrisca engasgos no telemóvel.

Prefira para fade e microinterações:

  • opacity
  • transform (translate, scale, rotate)

Cuidado com:

  • filter: blur() em imagens grandes (pode ser dispendioso)
  • valores de box-shadow animados em secções inteiras do ecrã
  • transition: all

Raramente é preciso will-change: opacity em cada imagem de uma grelha. will-change reserva memória; use-o temporariamente em elementos que animam com frequência, ou confie que o navegador promove a camada durante a transição.

Meça com o painel Performance: procure frames longos durante o hover numa lista com muitos cartões. Se aparecer layout thrashing, quase sempre é a propriedade errada que está a ser animada.

#Como resolver um fade que não aparece

  1. Confirme que o valor inicial e o final são mesmo diferentes.
  2. Confirme que transition está na regra base, não só em :hover. A transição é lida do estilo para o qual o elemento muda, por isso sem ela falta a transição de regresso.
  3. Procure outras regras que definam transition: none globalmente.
  4. Verifique prefers-reduced-motion no sistema operativo.
  5. No WordPress, confirme que a folha de estilos está carregada (código-fonte ou separador Network) e que o seletor corresponde ao DOM do bloco renderizado.

#Que regra de transition se aplica quando o hover começa e termina

O CSS Transitions Module Level 1 continua a ser um W3C Working Draft, publicado pela última vez a 8 de janeiro de 2026, e não uma recomendação final. A especificação define que a duração, o atraso e a função de temporização vêm do after-change style do elemento, ou seja, do estilo para o qual o elemento está a mudar. Quando o ponteiro entra na imagem, aplica-se o transition da regra :hover. Quando o ponteiro sai, aplica-se o transition da regra base.

Daqui saem duas consequências práticas:

  • Se transition estiver só em :hover, a imagem escurece com fade, mas volta de repente quando o ponteiro sai, porque a regra base não tem transição.
  • É possível dar velocidades diferentes à entrada e à saída de propósito, por exemplo uma resposta rápida à entrada e um regresso mais calmo.
.hover-image {
  opacity: 1;
  transition: opacity 0.3s ease-out; /* usado no regresso */
}

.hover-image:hover {
  opacity: 0.7;
  transition-duration: 0.15s; /* usado à entrada */
}

A especificação cobre também o caso em que o ponteiro sai antes de o fade terminar. A secção 3.1, sobre a inversão mais rápida de transições interrompidas, explica que um regresso com a duração completa sobre uma distância menor parece assimétrico. Por isso, a transição de regresso é encurtada por um fator próprio (reversing shortening factor), e a função de temporização continua a ser usada por inteiro, sem saltar para o meio da curva. Não é preciso JavaScript para calcular o tempo que falta. Fonte: CSS Transitions Module Level 1.

#Um fade de opacity conta como movimento segundo a WCAG

O critério de sucesso 2.3.3 Animation from Interactions da WCAG 2.2 (nível AAA) exige que a animação de movimento desencadeada por interação possa ser desativada, a menos que seja essencial para a funcionalidade ou para a informação. O documento explicativo do W3C para o 2.3.3 delimita o conceito: alterações de cor ou de opacity que não mudam o tamanho, a forma ou a posição percebidos não contam como animação de movimento. Uma errata retirou o desfoque desta exceção, por isso filter: blur() já não está incluído.

Um fade só de opacity fica assim fora do 2.3.3, enquanto transform: scale() muda o tamanho percebido e fica dentro. Isto permite uma regra de movimento reduzido mais precisa do que desligar tudo:

@media (prefers-reduced-motion: reduce) {
  .card-img {
    transform: none;
    transition: opacity 0.2s ease-out;
  }
}

O MDN descreve a mesma abordagem na documentação de prefers-reduced-motion: uma animação de escala é atenuada para uma animação de dissolução, que não desencadeia distúrbios vestibulares. O valor reduce é avaliado como verdadeiro, por isso @media (prefers-reduced-motion) equivale a @media (prefers-reduced-motion: reduce).

Para o foco aplica-se outro critério. O 2.4.7 Focus Visible (nível AA) exige um indicador de foco visível, e o MDN remete ainda para o 1.4.11 Non-text Contrast, que pede um contraste mínimo de 3:1 para o indicador. Uma mudança de opacity numa imagem raramente cumpre isso sozinha, por isso o foco precisa de um outline ou box-shadow próprio.

#Porque é que opacity 0 não esconde um elemento

A documentação do MDN sobre opacity aponta três pontos que muitas vezes escapam nos efeitos hover:

  • Um elemento com opacity: 0 fica invisível, mas continua no DOM. Recebe eventos do ponteiro e pode receber foco se estiver na ordem de tabulação. Um botão de overlay escondido pode, portanto, ser clicado e focado. Torne-o visível quando recebe foco, ou desligue os eventos do ponteiro com pointer-events e retire-o da ordem de tabulação.
  • A opacity, por si só, não esconde conteúdo dos leitores de ecrã. Para isso usa-se o atributo hidden, visibility ou display.
  • Qualquer valor abaixo de 1 cria um novo stacking context. Um overlay com z-index dentro de um cartão que passa para 0.9 pode acabar numa ordem de empilhamento diferente da esperada.

Se só o fundo deve escurecer, o MDN recomenda uma cor de fundo com canal alfa em vez de opacity no elemento inteiro. Com texto sobre a imagem, o contraste entre texto e fundo tem de continuar suficiente depois do fade.

#Fade de entrada na primeira apresentação com @starting-style

Uma transition só arranca quando um valor muda. O MDN explica que, por omissão, as transições não são desencadeadas na primeira atualização de estilo do elemento, nem quando display passa de none para um valor visível. Um cartão inserido no DOM com opacity: 1 não aparece, portanto, com fade por si só.

@starting-style define os valores a partir dos quais a transição começa. Para elementos que alternam com display: none, é ainda preciso transition-behavior: allow-discrete, porque display tem um tipo de animação discreto. Na transição para none, o valor só muda a 100 por cento da duração, por isso o elemento fica visível durante todo o fade.

.notice {
  opacity: 1;
  transition: opacity 0.3s ease-out;
  transition: opacity 0.3s ease-out, display 0.3s allow-discrete;
}

.notice.is-hidden {
  display: none;
  opacity: 0;
}

@starting-style {
  .notice {
    opacity: 0;
  }
}

A primeira linha de transition é intencional: o MDN escreve a propriedade duas vezes para que os navegadores sem suporte para allow-discrete continuem a animar a opacity. @starting-style tem a mesma especificidade que a regra original e por isso tem de vir depois dela. O MDN marca ambas as funcionalidades como Baseline 2024. Navegadores mais antigos mostram o elemento sem fade, o que é uma alternativa aceitável para uma transição puramente visual.

#Como reagir ao fim do fade com transitionend

Quando o JavaScript só troca uma classe e o CSS faz o fade, o script pode escutar transitionend para saber quando a transição terminou, por exemplo antes de definir hidden ou de mover o foco. A página do MDN sobre transitionend e a especificação dão quatro regras sobre quando o evento chega de facto:

  • transitionend dispara nos dois sentidos: quando o elemento atinge o estado de hover e quando regressa por completo.
  • Se a transição for removida antes de terminar, por exemplo porque transition-property é removida ou display passa a none, não há transitionend.
  • Se disparar transitioncancel, por exemplo porque o ponteiro sai a meio do fade, não há transitionend para essa transição.
  • Se a duração e o atraso forem ambos 0s, não existe transição e nenhum dos eventos de transição dispara.

A última regra é a que mais falha na prática. Quando prefers-reduced-motion define transition: none, um script que espera por transitionend para arrumar o estado fica à espera para sempre. Filtre por propertyName e preveja um caminho alternativo quando não há transição a correr.

const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;

// No CSS: .is-fading-out { opacity: 0; }
function hideNotice(el) {
  el.classList.add('is-fading-out');
  if (reduce) {
    el.hidden = true;
    return;
  }
  el.addEventListener('transitionend', function onEnd(event) {
    if (event.propertyName !== 'opacity') return;
    el.removeEventListener('transitionend', onEnd);
    el.hidden = true;
  });
}

#Hover em ecrãs táteis com a media query hover

A funcionalidade de media hover, do Media Queries Level 4, testa se o mecanismo de entrada principal consegue pairar sobre elementos. Segundo o MDN, devolve none quando a entrada principal não consegue pairar de todo ou não o faz de forma prática, como muitos telemóveis que emulam o hover com um toque longo. Coloque a regra de hover dentro de @media (hover: hover) quando um hover preso no telemóvel é um problema, e deixe :focus-visible fora, para que o foco de teclado continue a dar o mesmo estado.

@media (hover: hover) {
  .card-media:hover {
    opacity: 0.75;
  }
}

.card-media:focus-visible {
  opacity: 0.75;
}

#Suporte dos navegadores para transition, focus-visible e prefers-reduced-motion

A tabela mostra a primeira versão com suporte sem prefixo, segundo o MDN browser-compat-data na versão 8.1.6 (outubro de 2026).

FuncionalidadeChromeFirefoxSafariEdge
transition2616912
:focus-within605210.179
:focus-visible868515.486
prefers-reduced-motion746310.179
@media (hover)3864912
transition-behavior11712917.4117
@starting-style11712917.5117

O Internet Explorer suporta transition a partir da versão 10, mas nenhuma das outras funcionalidades da tabela. O Safari tinha transition com o prefixo -webkit- desde a versão 3.1.

Há uma consequência desta tabela fácil de ignorar. Num navegador sem :focus-visible, como o Safari anterior à 15.4, a regra .card-media:hover, .card-media:focus-visible é ignorada por inteiro, incluindo a parte do hover. O MDN explica no artigo sobre listas de seletores que um único seletor desconhecido invalida a lista toda. Escreva hover e foco como duas regras separadas, ou envolva-os em :is(), que aceita uma lista de seletores tolerante.

#O que lembrar sobre fade e hover em CSS

As transições CSS são a forma moderna e fácil de manter de fazer fade em hover em temas WordPress.

Pontos-chave:

  • Anime opacity (e, se fizer sentido, transform), não propriedades de layout.
  • Use transition para estados e animation para sequências temporizadas.
  • Espelhe o hover com :focus-visible / :focus-within.
  • Respeite prefers-reduced-motion.
  • Carregue o CSS via wp_enqueue_style e ajuste os seletores aos wrappers do Gutenberg.
  • Mantenha a duração em cerca de 0,2 a 0,3 s nas interfaces.
  • Use @starting-style quando um elemento deve aparecer com fade na primeira apresentação.

Transições em transform e opacity são baratas porque não forçam reflow, e essa diferença nota-se logo num portátil antigo ou num telemóvel de gama média. Aplicamos estes detalhes de front-end em cada tema feito à medida em WordPress.

Próximo passo

Transforme o artigo numa implementação real

Este bloco reforça a ligação interna e conduz o leitor para o passo seguinte mais útil dentro da arquitetura do site.

Quer implementar isto no seu site?

Se quer transformar o artigo em melhorias concretas, redesign ou num plano de implementação, posso fechar o escopo e executar.

Explorar outros serviços WordPress e base de conhecimento

Reforce o seu negócio com suporte técnico profissional em áreas-chave do ecossistema WordPress.

Perguntas frequentes

Respostas práticas para aplicar o tema na execução real.

Como se cria uma transição CSS suave ao passar o rato?#
Use a propriedade transition com uma propriedade animável concreta, como opacity ou transform, uma duração curta de 0.2s a 0.3s e uma função de temporização como ease ou ease-in-out.
Como se escurece uma imagem ao passar o rato com CSS?#
Defina o estado inicial com opacity 1, altere-o em :hover, por exemplo para 0.7, e acrescente transition: opacity 0.3s ease-in-out para uma transição suave.
Porque usar CSS em vez de JavaScript para efeitos hover?#
Os efeitos hover em CSS são mais leves, mais fáceis de manter e em regra mais fluidos, porque o navegador consegue otimizar as transições sem trabalho extra de JavaScript.
Como se altera o efeito fade out ao passar o rato com CSS?#
Acrescente transition: opacity 0.3s ease-in-out ao elemento e defina um valor de opacity mais baixo na pseudo-classe :hover. O navegador trata da transição suave automaticamente, sem JavaScript.

Precisa de FAQ adaptado ao setor e mercado? Crio uma versão alinhada com os seus objetivos de negócio.

Fale connosco

Artigos Relacionados