View Transitions

Es una nueva API de navegador diseñada para facilitar animaciones fluidas y transiciones entre vistas o elementos en páginas web.

Esta funcionalidad permite a los desarrolladores crear experiencias más dinámicas y atractivas, como pasar de una galería de imágenes a un detalle ampliado o cambiar entre diferentes secciones de una página, con transiciones suaves entre estados.

¿Cómo funciona?

La API de View Transitions actúa como un puente entre dos estados visuales diferentes de una página web. En lugar de simplemente actualizar el contenido de manera abrupta, permite al navegador animar los cambios de forma fluida.

  1. Estado inicial: La vista actual (por ejemplo, una miniatura de imagen).
  2. Estado final: La vista siguiente (por ejemplo, una imagen ampliada o detalles).
  3. Transición animada: El navegador interpola automáticamente entre ambos estados.

HTML+JS

				
					<fieldset id="gallery" class="hub">
  <div>
    <input type="radio" id="image-1" name="gallery" value="image-1" checked>
    <img decoding="async" src="https://assets.codepen.io/2585/image_fx_a_black_wolf_in_armor_wrecking_a_steampunk_ci.jpg" alt="" />
    <label for="image-1">Cyber Wolf</label>
  </div>

  <div>
    <input type="radio" id="image-2" name="gallery" value="image-2">
    <img decoding="async" src="https://assets.codepen.io/2585/image_fx_cyberpunk_city_scene_with_neon_streaks_of_lig+%283%29.jpg" alt="" />
    <label for="image-2">Flying cars</label>
  </div>

  <div>
    <input type="radio" id="image-3" name="gallery" value="image-3">
    <img decoding="async" src="https://assets.codepen.io/2585/image_fx_cyberpunk_city_scene_with_neon_streaks_of_lig+%282%29.jpg" alt="" />
    <label for="image-3">Flying cars 2</label>
  </div>
  
  <div>
    <input type="radio" id="image-4" name="gallery" value="image-4">
    <img decoding="async" src="https://assets.codepen.io/2585/image_fx_cyberpunk_city_scene_with_neon_streaks_of_lig+%281%29.jpg" alt="" />
    <label for="image-4">Flying cars 3</label>
  </div>
   
  <div>
    <input type="radio" id="image-5" name="gallery" value="image-5">
    <img decoding="async" src="https://assets.codepen.io/2585/image_fx_cyberpunk_city_scene_with_neon_streaks_of_lig+%284%29.jpg" alt="" />
    <label for="image-5">Cyber T-Rex</label>
  </div>
   
  <div>
    <input type="radio" id="image-6" name="gallery" value="image-6">
    <img decoding="async" src="https://assets.codepen.io/2585/image_fx_a_raptor_in_armor_wrecking_a_cyberbunk_city_w.jpg" alt="" />
    <label for="image-6">Cyber Raptor</label>
  </div>
   
  <div>
    <input type="radio" id="image-7" name="gallery" value="image-7">
    <img decoding="async" src="https://assets.codepen.io/2585/image_fx_cyberpunk_city_scene_with_neon_streaks_of_lig.jpg" alt="" />
    <label for="image-7">Cyber freeway</label>
  </div>
</fieldset>
<script>
    
    // watch for radio group input clicks
// if VT is supported, prevent the default behavior
// call VT and manually set checked
document.querySelectorAll('#gallery input').forEach(radio => {
  radio.onclick = e => {
    if (!document.startViewTransition) return;

    e.preventDefault();

    function mutate() {
      // radio group naturally handled unchecking the current one
      e.target.checked = true;
      // reset the zindex so the next item can be on top
      e.target.parentNode.style.zIndex = null;
    }

    // ensures selected item is on top during view transition
    e.target.parentNode.style.zIndex = 2;

    // always mutate, but VT if possible
    document.startViewTransition ?
    document.startViewTransition(() => mutate()) :
    mutate();
  };
});

// function to handle device rotation / page resize
function flipGallery() {
  function mutate(vertical = false) {
    if (document.startViewTransition)
    document.startViewTransition(() =>
    vertical ?
    gallery.classList.add('portrait') :
    gallery.classList.remove('portrait'));
  }

  mutate(window.innerWidth <= 768);
}

// throttled resize observer
// waits for 100ms of no resizing before firing flipGallery()
window.onresize = () => {
  clearTimeout(window.resizeEndTimer);
  window.resizeEndTimer = setTimeout(flipGallery, 100);
};
    
</script>
				
			

CSS

				
					@import "https://unpkg.com/open-props/easings.min.css";

@layer demo.view-transition {
  ::view-transition-group(*) {
    animation-duration: .5s;
    animation-timing-function: var(--ease-5);
  }
  
  .hub > * {
    @media (prefers-reduced-motion: no-preference) {
      &:nth-child(1) {
        view-transition-name: gallery-item-1;
      }
      &:nth-child(2) {
        view-transition-name: gallery-item-2;
      }
      &:nth-child(3) {
        view-transition-name: gallery-item-3;
      }
      &:nth-child(4) {
        view-transition-name: gallery-item-4;
      }
      &:nth-child(5) {
        view-transition-name: gallery-item-5;
      }
      &:nth-child(6) {
        view-transition-name: gallery-item-6;
      }
      &:nth-child(7) {
        view-transition-name: gallery-item-7;
      }
    }
}

@layer demo.layout {
  .hub {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(5, 15vw);
    grid-template-rows: repeat(3, 15vw);
    
    &.portrait {
      grid-template-columns: repeat(3, 20vw);
      grid-template-rows: repeat(5, 20vw);
    }
    
    /* selected promotion */
    > :has(:checked) {
      grid-column: 1 / 4;
      grid-row: 1 / 4;
    }
    
    /* pile the label, input and image */
    > * {
      display: grid;
      
      > * {
        grid-area: 1/1;
      }
      
      > label {
        opacity: 0;
        cursor: pointer;
        -webkit-tap-highlight-color: transparent;
      }
      
      > input {
        border-radius: 0;
        outline-offset: 5px;
        outline-color: deeppink;
        outline-color: color(display-p3 1 0 1);
      } 
    }
  }
}

@layer demo.support {
  * {
    box-sizing: border-box;
    margin: 0;
  }

  html {
    block-size: 100%;
    color-scheme: dark light;
  }

  body {
    min-block-size: 100%;
    font-family: system-ui, sans-serif;


    place-content: center;
  }
  
  fieldset {
    border: none;
    padding: 0;
    margin: 0;
  }
  
  img {
    max-inline-size: 100%;
  }
  
  html {
    view-transition-name: none;
  }
}

.particle-container {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: -1; /* Coloca las partículas detrás del contenido */
  overflow: hidden;
}

.particle {
  position: absolute;
  width: 10px;
  height: 10px;
  background: rgba(255, 255, 255, 0.8); /* Partículas blancas */
  border-radius: 50%;
  animation: moveParticle 5s linear infinite;
  opacity: 0.5;
}

/* Animación para las partículas */
@keyframes moveParticle {
  0% {
    transform: translateY(0) translateX(0);
    opacity: 1;
  }
  100% {
    transform: translateY(100vh) translateX(calc(100vw * -0.5));
    opacity: 0;
  }
}
				
			

Email Designer: la herramienta gratuita y open source para crear emails que sí funcionan

Email Designer: la herramienta gratuita y open source para crear emails que sí funcionan

🔍 Analiza tu web con Inteligencia Artificial – Gratis y al instante

¿Tu sitio está optimizado? Descúbrelo en segundos con nuestra herramienta de IA y recibe un informe de UX

CV Andrés

Desarrollo webs que cargan rápido y venden mejor. Logística mental de 7 a 3, WordPress ninja de 3 a 9.
banner

The quiet shame of vibecoding

THANK YOU for the feedback, the ideas and the honesty. Building an AI SaaS in public.

Agentes de IA para tu e-commerce

Esta app te permite diseñar y probar agentes de IA que atienden a tus clientes

Experimento Visual con Three.js, Scroll Interactivo y Partículas

Una demo 3D experimental creada con Three.js: partículas dinámicas, efecto glitch cinematográfico, movimiento reactivo y scroll interactivo.