Hace unos dos años estuve haciendo cosas con WebGL usando la librería CurtainsJS y hoy vuelvo a retomarlo basándome en una de las entradas: WebGL Slideshow. Voy a omitir mucho código ya que muchas cosas son una réplica, haré hincapié en cosas que sean diferentes, todas relacionadas con la integración de la librería swiperjs y curtainsjs
Potencia Visual: CurtainsJS y SwiperJS
Antes de entrar en el código, repasemos por qué esta combinación es tan potente para crear experiencias web inmersivas.
CurtainsJS: WebGL simplificado
Esta librería ligera permite convertir elementos HTML (imágenes, vídeos, canvas) en planos WebGL interactivos.
- Sincronización DOM-WebGL: Mapea automáticamente la posición y tamaño de los elementos HTML al canvas WebGL.
- Shaders Personalizados: Permite escribir Vertex y Fragment shaders para crear distorsiones, transiciones líquidas y efectos de post-procesado.
- Rendimiento: Aprovecha la aceleración por GPU para animaciones fluidas que serían imposibles solo con CSS.
SwiperJS: El estándar en Sliders
Es probablemente la librería de carruseles táctiles más moderna y robusta.
- Agnóstica: Funciona con Vanilla JS, React, Vue, Angular, etc.
- Modular: Carga solo lo que necesitas (Navegación, Paginación, A11y).
- Experiencia Nativa: Ofrece una sensación táctil 1:1 similar a las aplicaciones nativas móviles.
Combinarlas nos permite tener la lógica de navegación robusta de Swiper con los efectos visuales de alto impacto de CurtainsJS.
HTML para canvas WebGL
Necesitamos crear un contenedor donde se generará el canvas y otro contedor con las imágenes para las animaciones WebGL
<div class="wrapper">
<div class="canvas"></div>
<div class="slides multi-textures">
<img
style="display: none"
src="./src/img/displacement4.jpg"
crossorigin="anonymous"
data-sampler="displacement" />
</div>
</div>
La imagen será usada para establecer un patrón para la animación. Curtainsjs requiere todas las imágenes que forman la animación pero en lugar de añadir las etiquetas imagen lo haremos con javascript clonando las imágenes que añadamos a swiper
Inicialización de swiper
Swiper tiene muchas opciones. Si se añaden más opciones no sé como se comportará. Ánimo a que pruebes bajo tu responsabilidad ;)
import Swiper, { Navigation } from 'swiper';
const swiperEl = document.querySelector('.swiper');
const swiper = new Swiper(swiperEl, {
modules: [Navigation],
slidesPerView: 'auto',
spaceBetween: 16,
loop: true,
slideToClickedSlide: true,
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
on: {
beforeInit: () => {
swiperEl.querySelectorAll('img').forEach((img) => {
planeElement.appendChild(img.cloneNode());
});
},
},
});
Para dar soporte a Loop he añadido un script para clonar los nodos imágenes dentro del método beforeInit, ya que swiper duplica nodos para hacer el efecto loop
Actualización de texturas
Usando custainsjs necesitamos especificar la textura inicial (activeTexture) y la textura final (nextTexture). Con swiperjs tenemos muy fácil conocer la imagen actual y la siguiente
initEvent(activeTexture, nextTexture) {
// usamos el evento para detectar cuando se produce la transición
this.swiper.on('realIndexChange', () => {
// usamos una propiedad local para usarlo como bloqueo esperar a que las transiciones de produzcan
if (!this.slidesState.isChanging) {
this.curtains.enableDrawing();
this.slidesState.isChanging = true;
this.swiper.disable();
// cargamos la imagen usando la API de swiper: this.swiper.realIndex
nextTexture.setSource(
this.multiTexturesPlane.images[this.swiper.realIndex + 1]
);
setTimeout(() => {
this.curtains.disableDrawing();
this.slidesState.isChanging = false;
// actualizamos la textura
activeTexture.setSource(
this.multiTexturesPlane.images[this.swiper.realIndex + 1]
);
this.slidesState.transitionTimer = 0;
this.swiper.enable();
}, 1700);
}
});
}
Hay mucho más código, recomiendo ver la entrada anterior y enlaces a GitHub o Codepen