Revelación de contenido interactivo

Agrega efectos de revelación interactivos y de alto rendimiento a tu sitio con máscaras de CSS y propiedades personalizadas registradas. Si usamos un gradiente radial como máscara y registramos sus valores de detención, podemos realizar una transición fluida de la entrada y la salida, mientras seguimos el puntero del usuario con un mínimo de JavaScript.

Implementación

1. Registra propiedades personalizadas

Para habilitar la interpolación fluida de los valores de detención de gradiente, debes registrar las variables con @property. Esto informa al motor del navegador sobre el tipo de datos, lo que le permite realizar la transición entre valores durante las actualizaciones.

/* 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;
}

No es necesario realizar la transición de las propiedades personalizadas que hacen un seguimiento de la posición del puntero, por lo que no es necesario registrarlas.

2. Define la capa de enmascaramiento

Aplica el mask-image al elemento que quieras revelar. Usa un radial-gradient que haga referencia a las propiedades 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. Actualiza las coordenadas con JavaScript

Haz un seguimiento de la posición del puntero y actualiza las variables de CSS. Como las propiedades están registradas y tienen un transition definido, el foco se moverá sin problemas, incluso si los eventos de puntero son poco frecuentes.

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. Accesibilidad e interacción

Garantía de accesibilidad OBLIGATORIA: Este patrón se basa en las interacciones del puntero para revelar un foco visual. DEBES garantizar que todo el contenido subyacente permanezca completamente visible, legible y accesible de forma independiente con el teclado de forma predeterminada en el diseño subyacente, usando la capa de foco puramente como una mejora visual no esencial para los usuarios de punteros. Nunca uses este efecto para ocultar o bloquear contenido esencial para los usuarios de tecnologías de accesibilidad o que solo usan el teclado.

  • Eventos de puntero: Establece pointer-events: none en la capa de superposición de máscara para permitir que las interacciones estándar de clic y toque pasen a los controles que se encuentran debajo.
  • Anulación de movimiento reducido: Inhabilita la interpolación de transición fluida para los usuarios que solicitan un movimiento reducido.
/* MANDATORY Copy-Paste Safety: Disable transition scaling for motion-sensitive users */
@media (prefers-reduced-motion: reduce) {
  .reveal-layer {
    transition: none !important;
  }
}

Estrategias de resguardo

Resguardo de propiedad no registrada

Los navegadores que admiten mask-image, pero no @property, seguirán mostrando el foco, pero el movimiento saltará entre los estados activado y desactivado porque no pueden interpolar valores dentro de un radial-gradient. Proporciona valores de resguardo cuando uses 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%)
  );
}

Compatibilidad con máscaras básicas

Para los navegadores que no admiten el enmascaramiento de CSS en absoluto:

  1. Propiedad con prefijo: Usa la propiedad con prefijo -webkit-mask-image para una mayor compatibilidad con navegadores.
  2. Mejora progresiva: Diseña el estado base de la IU para que sea completamente funcional y legible sin el efecto de revelación. Esto es útil cuando el efecto solo agrega un toque visual y no es un requisito para leer el contenido.