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;
  }
}
				
			
COMPARTIR EN:
Facebook
LinkedIn
WhatsApp

Explorando la Sexualidad a Partir de los 30: Impacto en Hombres y Mujeres

COMPARTIR EN:
Facebook
LinkedIn
WhatsApp

Atención Dental a Domicilio: La Solución Ideal en Santiago de Chile

COMPARTIR EN:
Facebook
LinkedIn
WhatsApp

Dominando Laravel Eloquent: El ORM que Revoluciona el Desarrollo Web

COMPARTIR EN:
Facebook
LinkedIn
WhatsApp

Domina el Web Scraping con Python: Una Guía Práctica

COMPARTIR EN:
Facebook
LinkedIn
WhatsApp

 Layout Explorations with Gsap

Mi formación no vino de una academia tradicional, sino del compromiso constante con el aprendizaje práctico. Cursos, documentación, errores y muchas líneas de código me llevaron a entender no solo cómo funciona la web, sino cómo hacer que funcione mejor.
COMPARTIR EN:
Facebook
LinkedIn
WhatsApp

El Futuro del SEO: Cómo la IA Transformará las Estrategias en 2025