Revelação de conteúdo interativo

Adicione efeitos de revelação interativos e de alto desempenho ao seu site com máscaras CSS e propriedades personalizadas registradas. Ao usar um gradiente radial como máscara e registrar os valores de parada, podemos fazer uma transição suave de entrada e saída, seguindo o ponteiro do usuário com o mínimo de JavaScript.

Implementação

1. Registrar propriedades personalizadas

Para permitir a interpolação suave de valores de parada de gradiente, registre as variáveis usando @property. Isso informa ao mecanismo do navegador sobre o tipo de dados, permitindo que ele faça a transição entre valores durante as atualizações.

/* Register the spotlight inner and outer sizes to enable interpolation */
 @property --inner-size{
  syntax: "<length-percentage>";
  inherits: true;
  initial-value: 0px;
}
@property --outer-size{
  syntax: "<length-percentage>";
  inherits: true;
  initial-value: 0px;
}

As propriedades personalizadas que rastreiam a posição do ponteiro não precisam ser transferidas, então não é necessário registrá-las.

2. Definir a camada de mascaramento

Aplique o mask-image ao elemento que você quer revelar. Use um radial-gradient que faça referência às propriedades registradas.

.reveal-layer {
  /* Only transition the size properties, NOT the position variables */
  transition: --inner-size 0.2s ease-in-out, --outer-size 0.2s ease-in-out;  

  /* The spotlight is defined by the transparent center of the mask */
  mask-image: radial-gradient(
    circle at var(--mouse-x) var(--mouse-y),
    black var(--inner-size, 0%),
    transparent var(--outer-size, 0%)
  );

  /* Ensure the mask doesn't repeat if the element is large */
  mask-repeat: no-repeat;

  /* Make the mask layer non-interactive */
  pointer-events: none;
}

/* Update the gradients stops on interaction */
.reveal-layer:hover {
  --inner-size: 100px;
  --outer-size: 120px;
}  

3. Atualizar coordenadas com JavaScript

Rastreie a posição do ponteiro e atualize as variáveis CSS. Como as propriedades estão registradas e têm um transition definido, o destaque vai se mover de forma suave, mesmo que os eventos de ponteiro sejam pouco frequentes.

const container = document.querySelector('.container');
// Store the container's bounding rect
let rect = container.getBoundingClientRect();
// Update the rect when the container is resized
const resizeObserver = new ResizeObserver(()=>{
  rect = container.getBoundingClientRect();
})
resizeObserver.observe(container);

container.addEventListener('pointermove', (e) => {
  // Calculate position as a percentage of the container.
  const x = ((e.clientX - rect.left) / rect.width) * 100;
  const y = ((e.clientY - rect.top) / rect.height) * 100;

  // Update the registered properties
  container.style.setProperty('--mouse-x', `${x}%`);
  container.style.setProperty('--mouse-y', `${y}%`);
});

4. Acessibilidade e interação

Garantia de acessibilidade OBRIGATÓRIA: esse padrão depende de interações com o ponteiro para revelar um destaque visual. Você PRECISA garantir que todo o conteúdo subjacente permaneça totalmente visível, legível e acessível de forma independente pelo teclado por padrão no layout subjacente, usando a camada de destaque apenas como uma melhoria visual não essencial para usuários de ponteiro. Nunca use esse efeito para ocultar ou bloquear conteúdo essencial para usuários que usam apenas o teclado ou tecnologias adaptativas.

  • Eventos de ponteiro: defina pointer-events: none na camada de sobreposição de máscara para permitir que as interações padrão de clique e toque passem para os controles abaixo.
  • Substituição de movimento reduzido: desativa a interpolação de transição suave para usuários que solicitam movimento reduzido.
/* MANDATORY Copy-Paste Safety: Disable transition scaling for motion-sensitive users */
@media (prefers-reduced-motion: reduce) {
  .reveal-layer {
    transition: none !important;
  }
}

Estratégias de fallback

Substituto de propriedade não registrada

Os navegadores que oferecem suporte a mask-image, mas não a @property, ainda vão mostrar o destaque, mas o movimento vai alternar entre os estados ativado e desativado porque eles não podem interpolar valores dentro de um radial-gradient. Forneça valores substitutos ao usar var().

.reveal-layer {
  mask-image: radial-gradient(
    circle at var(--mouse-x) var(--mouse-y),
    /* Use fallback values when using the `var()` function for browsers that don't get an initial value from the @property registration. */
    black var(--inner-size, 0%),
    transparent var(--outer-size, 0%)
  );
}

Suporte básico para máscaras

Para navegadores que não são compatíveis com o mascaramento de CSS:

  1. Propriedade com prefixo: use a propriedade com o prefixo -webkit-mask-image para maior compatibilidade com navegadores.
  2. Aprimoramento progressivo: projete o estado base da interface para que seja totalmente funcional e legível sem o efeito de revelação. Isso é útil quando o efeito apenas adiciona um toque visual, mas não é um requisito para ler o conteúdo.