Animación de Imágenes usando WebGL con librería curtainsJS y gsap

Animación Hover realizada con WebGL para transición de imágenes usando librería curtainsjs y gsap. Imágenes usadas como displacement y modificaciones de vertex y fragment shaders
Animación de Imágenes usando WebGL con librería curtainsJS y gsap

Esta animación la estaba haciendo al mismo tiempo que otra que acabo de publicar, Animación de slideshow usando WebGL con librería curtainsJS, para poder conocer que cosas se pueden llegar a realizar sobre WebGL con la librería https://www.curtainsjs.com/

En la anterior publicación realizaba la animación entre dos texturas con una imagen displacement lanzada con evento click

En esta publicación, los eventos que lanzan la animación son mouseenter y mouseout. Se usan tres imágenes como Texturas. La primera imagen será la mostrada en estado normal, la segunda en estado hover y la última será la fusión de ambas durante la transición.

He colocado hasta 6 animaciones diferentes, con sus respectivas imágenes. Mi idea era usarlo en una Home commerce de Grid de productos, pero veo que consumen mucha GPU y lo descarto. Con el tiempo tendré que aprender más y ver si puedo optimizar algo. En Firefox, con una imagen la animación se ve limpia, pero con las 6 se nota que no es fluida.

Propiedades de clase

En el constructor de la clase WebglHover definimos atributos necesarios

class WebglHover {
  constructor(set) {
    this.canvas = set.canvas
    this.webGLCurtain = new Curtains({
      container: this.canvas,
      watchScroll: false,
      pixelRatio: Math.min(1.5, window.devicePixelRatio)
    })
    this.planeElement = set.planeElement
    this.mouse = {
      x: 0,
      y: 0
    }
    this.params = {
      vertexShader: document.getElementById("vs").textContent,
      fragmentShader: document.getElementById("fs").textContent,
      widthSegments: 40,
      heightSegments: 40, // 40*40*6 = 9600 vertices
      uniforms: {
        time: {
          name: "uTime",
          type: "1f",
          value: 0
        },
        mousepos: {
          name: "uMouse",
          type: "2f",
          value: [0, 0]
        },
        resolution: {
          name: "uReso",
          type: "2f",
          value: [innerWidth, innerHeight]
        },
        progress: {
          name: "uProgress",
          type: "1f",
          value: 0
        }
      }
    }
    this.initPlane()
  }
}

La instancia de la clase nos dara el contexto WebGL y añade el canvas a nuestro envolvente. Recibirá como parámetro un objeto con los elementos del DOM:

  1. <div class="canvas">: envolvente donde se creará el elemento canvas con curtainsJS
  2. <section class="plane">: donde colocaremos las tres imágenes con curtainsJS para crear las texturas, con su data-sampler para poder tratarlo dentro del fragment shader

Instanciamos Curtains,obtenemos el elemento DOM para el plano y un objeto con la posicón del ratón.

Con this.params = {} definimos los shaders y sus segmentos. Dentro de uniforms definimos las KEYS necesarias para poder manipular los shaders:

  • time: uTime
  • mousepos: uMouse
  • resolution: uReso
  • progress: uProgress

Finalmente llamamos al método initPlane() para iniciar la aplicación. Este método hace la llamada a otros dos métodos si el plano está listo.

Iniciar el plano WebGL

initPlane() {
  this.plane = new Plane(this.webGLCurtain, this.planeElement, this.params)

  if (this.plane) {
    this.plane.onReady(() => {
      this.update()
      this.initEvent()
    })
  }
}

En este método se crea el Plano, mediante la clase Plane de curtainsjs. Si el plano se ha creado correctamente, y cuando esté listo, hacemos llamada a update() e iniciamos los eventos initEvent()

Renderizado del plano WebGL

update() {
  this.plane.onRender(() => {
    this.plane.uniforms.time.value += 0.01

    this.plane.uniforms.resolution.value = [innerWidth, innerHeight]
  })
}

Se actualizan los valores de time y resolution del objeto paramas

Registro de eventos para WebGL

initEvent() {
  this.planeElement.addEventListener("mouseenter", () => {
    gsap.to(this.plane.uniforms.progress, .8, {
      value: 1
    })
  })

  this.planeElement.addEventListener("mouseout", () => {
    gsap.to(this.plane.uniforms.progress, .8, {
      value: 0
    })
  })
}

A los planos se registra los eventos mouseenter y mouseout. Con gsap, tanto cuando el ratón entra o sale de la imagen, se modifica el valor progress, para que se muestra una u otra imagen. Este efecto durará 800ms.

Instanciar la clase WebglHover

document.querySelectorAll('.slide').forEach(slide => {
  const canvas = slide.querySelector('.canvas')
  const planeElement = slide.querySelector('.plane')
  new WebglHover({
    canvas,
    planeElement
  })
})

Recorremos todos los elementos del DOM que tengan la clase slide y creamos las instancias pasando como parámetros la referencia a <div class="canvas"> y a <div class="plane">

Html del WebGL

<main class="slides">
  <section class="slide">
    <div class="canvas"></div>
    <div class="plane">
      <img data-sampler="texture0" src="./src/img/chair-02.jpg" crossorigin="anonymous" />
      <img data-sampler="texture1" src="./src/img/chair-01.jpg" crossorigin="anonymous" />
      <img data-sampler="map" src="./src/img/displacements/01.jpg" crossorigin="anonymous" />
    </div>
    <div class="slide__content">
      <p>Lorem ipsum dolor sit.</p>
    </div>
  </section>
  <section class="slide"></section>
</main>

La estructura del html si es importante. La envolvente slides es el contenedor de todo, dentro, pondremos tantos slide como queramos

En <div class="canvas"> es donde la librería curtainsJS va a crear el elemento canvas

Dentro de <section class="plane"> colocaremos todas las imágenes con los atributos data-sampler, recordando que la última imagen será usada para la transición entre ambas.

Código CSS del WebGL

Me ahorro la explicación, no tiene nada especial. Si se quiere ver, en el GitHub está todo. ;)