I'm trying to Create the nextJS Slideshow component using curtainsJS and GSAP. I'm following this algorithm. I'm used curtainsJS npm library to create this slide show.
but images not rendering in the canvas in code sandbox it work perfectly with HTML. here is my slideshow class component
// @ts-ignore
import { Curtains, Plane }from 'curtainsjs'
import gsap, { Power2, TweenMax } from 'gsap'
interface IObjectKeys{
[key: string]: HTMLElement | null;
}
interface IButtons extends IObjectKeys{
prevButton: HTMLElement | null;
nextButton: HTMLElement | null;
}
class WebglSlideshow{
private webGLContainer: HTMLElement | null;
private slideShow: HTMLElement | null;
private slides: HTMLCollectionOf<Element>;
private duration: number;
private activeTextureIndex: number;
private nextTextureIndex: number;
private nbSlides: number;
private isAnimating: boolean;
private curtains: any;
private slideshowPlane: any;
private buttons: IButtons ;
constructor() {
this.webGLContainer = document.getElementById('curtains-canvas')
this.slideShow = document.getElementById('slides-list')
this.slides = document.getElementsByClassName('slide')
this.buttons = {
prevButton: document.getElementById('previous-slide'),
nextButton: document.getElementById('next-slide')
}
this.duration = 1
this.activeTextureIndex = 0;
this.nextTextureIndex = 1;
this.nbSlides = this.slides.length,
this.isAnimating = false;
this.init();
}
private init() {
this.setupCurtains();
this.setupButtonPlanes();
this.setupSlideshowPlane();
this.initNavigation();
}
private setupCurtains() {
// create a new Curtains object
this.curtains = new Curtains({
container: this.webGLContainer
}).onError( () => {
// TODO handle webgl errors
});
}
private setupSlideshowPlane() {
const vs = `
#ifdef GL_ES
precision mediump float;
#endif
// default mandatory variables
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
// varyings : notice we've got 2 texture coords varyings
// one for our visible texture
// and one for the upcoming texture
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
varying vec2 vActiveTextureCoord;
varying vec2 vNextTextureCoord;
// textures matrices
uniform mat4 activeTexMatrix;
uniform mat4 nextTexMatrix;
// custom uniforms
uniform float uTransition;
void main() {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
// varyings
vTextureCoord = aTextureCoord;
vActiveTextureCoord = (activeTexMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
vNextTextureCoord = (nextTexMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
vVertexPosition = aVertexPosition;
}
`;
const fs = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
varying vec2 vActiveTextureCoord;
varying vec2 vNextTextureCoord;
// custom uniforms
uniform float uTransition;
uniform float uButtonPos;
// our textures samplers
// notice how it matches the sampler attributes of the textures we created dynamically
uniform sampler2D activeTex;
uniform sampler2D nextTex;
// Simplex 2D noise
//
vec3 permute(vec3 x) {
return mod(((x*34.0)+1.0)*x, 289.0);
}
float snoise(vec2 v){
const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);
vec2 i = floor(v + dot(v, C.yy) );
vec2 x0 = v - i + dot(i, C.xx);
vec2 i1;
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
i = mod(i, 289.0);
vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
+ i.x + vec3(0.0, i1.x, 1.0 ));
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy),
dot(x12.zw,x12.zw)), 0.0);
m = m*m ;
m = m*m ;
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
vec3 g;
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
return 130.0 * dot(m, g);
}
void main() {
vec4 noise = vec4(vec3(snoise(vTextureCoord * sqrt(2.0))), 1.0);
float distanceFromCenter = distance(vTextureCoord, vec2(uButtonPos, 0.5)) * 0.9;
// calculate an effect that goes from 0 to 1 depenging on uOpacity and distanceToLeft
float spreadFromCenter = clamp((uTransition * (1.0 - distanceFromCenter) - 1.0) + uTransition * 2.0, 0.0, 1.0);
vec4 firstImage = texture2D(activeTex, vActiveTextureCoord + noise.r * spreadFromCenter * 0.175);
vec4 secondImage = texture2D(nextTex, vNextTextureCoord - noise.r * (1.0 - spreadFromCenter) * 0.175);
// mix both texture
vec4 finalImage = mix(firstImage, secondImage, spreadFromCenter);
// handling premultiplied alpha
finalImage = vec4(finalImage.rgb * finalImage.a, finalImage.a);
gl_FragColor = finalImage;
}
`;
const slideParams = {
vertexShader: vs,
fragmentShader: fs,
uniforms: {
transition: {
name: "uTransition",
type: "1f",
value: 0,
},
buttonPos: {
name: "uButtonPos",
type: "1f",
value: 0,
},
}
}
// add the slideshow plane
this.slideshowPlane = new Plane(this.curtains,this.slideShow, slideParams);
if(this.slideshowPlane) {
this.slideshowPlane.userData = {
activeTex: this.slideshowPlane.createTexture({name:"activeTex"}),
nextTex: this.slideshowPlane.createTexture({name:"nextTex"})
}
this.slideshowPlane.onReady( () => {
this.slideshowPlane.userData.activeTex.setSource(this.slideshowPlane.images[this.activeTextureIndex]);
this.slideshowPlane.userData.nextTex?.setSource(this.slideshowPlane.images[this.nextTextureIndex]);
});
}
}
private setupButtonPlanes() {
const buttonVs = `
#ifdef GL_ES
precision mediump float;
#endif
// default mandatory variables
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
// custom uniforms
uniform float uTime;
void main() {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
// varyings
vTextureCoord = aTextureCoord;
vVertexPosition = aVertexPosition;
}
`;
const buttonFs = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
// custom uniforms
uniform float uTime;
uniform float uHoverEffect;
void main() {
vec4 color;
float circleRadius = mix(0.7, 0.8, uHoverEffect);
float distortionEffect = mix(0.025, 0.05, uHoverEffect);
vec2 distortedTextCoords = vec2(vTextureCoord.x + sin(uTime / 30.0) * cos(vTextureCoord.y * 5.0) * distortionEffect, vTextureCoord.y + cos(uTime / 30.0) * sin(vTextureCoord.x * 5.0) * distortionEffect);
float hole = step(circleRadius, distance(distortedTextCoords, vec2(0.5, 0.5)) * 2.0);
vec3 circle = vec3(1.0 - hole);
// red button
circle.r *= 0.95;
circle.g *= 0.1;
circle.b *= 0.2;
float opacity = 0.75 + (1.0 - uHoverEffect) * 0.25;
color = vec4(circle * opacity, step(0.5, circle.r) * opacity);
gl_FragColor = color;
}
`;
const buttonParams = {
vertexShader: buttonVs,
fragmentShader: buttonFs,
transparent: true,
uniforms: {
time: {
name: "uTime",
type: "1f",
value: 0,
},
hoverEffect: {
name: "uHoverEffect",
type: "1f",
value: 0,
},
}
};
for(let key in this.buttons) {
if(key === "nextButton") buttonParams.uniforms.time.value = 90;
let buttonPlane = new Plane(this.curtains, this.buttons[key], buttonParams)
if(buttonPlane) {
buttonPlane.userData = {
name: key,
grow: 0,
growTween: null,
};
buttonPlane.onReady( () => {
// mouse enter
buttonPlane.htmlElement.addEventListener("mouseenter", () => {
if (buttonPlane.userData.growTween) buttonPlane.userData.growTween.kill();
buttonPlane.userData.growTween = TweenMax.to(buttonPlane.userData, 0.5, {
grow: 1,
ease: Power2.easeOut,
onUpdate: () => {
buttonPlane.uniforms.hoverEffect.value = buttonPlane.userData.grow;
},
onComplete: () => {
buttonPlane.userData.growTween = null;
}
});
});
// mouse leave
buttonPlane.htmlElement.addEventListener("mouseleave", () => {
if (buttonPlane.userData.growTween) buttonPlane.userData.growTween.kill();
buttonPlane.userData.growTween = TweenMax.to(buttonPlane.userData, 0.5, {
grow: 0,
ease: Power2.easeOut,
onUpdate: () => {
buttonPlane.uniforms.hoverEffect.value = buttonPlane.userData.grow;
},
onComplete: () => {
buttonPlane.userData.growTween = null;
}
});
});
}).onRender( () => {
buttonPlane.uniforms.time.value++;
});
}
}
}
private initNavigation() {
// show first slide title
//this.options.slides[this.activeTextureIndex].classList.add("slide--active");
TweenMax.to(this.slides[this.activeTextureIndex].querySelector(".slide-title"), this.duration / 2, {
ease: Power2.easeIn,
opacity: 1,
scaleX: 1,
scaleY: 1,
force3D: true,
}).delay(this.duration / 2);
// going to next image
this.buttons.nextButton?.addEventListener("click", () => {
if (!this.isAnimating) {
this.isAnimating = true;
// check what will be next image
if (this.activeTextureIndex < this.nbSlides - 1) {
this.nextTextureIndex = this.activeTextureIndex + 1;
}
else {
this.nextTextureIndex = 0;
}
// apply it to our next texture
this.slideshowPlane.userData.nextTex.setSource(this.slideshowPlane.images[this.nextTextureIndex]);
// change button pos uniform
this.slideshowPlane.uniforms.buttonPos.value = 1;
// launch tween
this.changeSlide();
}
});
// going to previous image
this.buttons.prevButton?.addEventListener("click", () => {
if (!this.isAnimating) {
this.isAnimating = true;
// check what will be next image
if (this.activeTextureIndex === 0) {
this.nextTextureIndex = this.nbSlides - 1;
}
else {
this.nextTextureIndex = this.activeTextureIndex - 1;
}
// apply it to our next texture
this.slideshowPlane.userData.nextTex.setSource(this.slideshowPlane.images[this.nextTextureIndex]);
// change button pos uniform
this.slideshowPlane.uniforms.buttonPos.value = 0;
// launch tween
this.changeSlide();
}
});
}
private changeSlide() {
this.changeTitles();
TweenMax.to(this.slideshowPlane.uniforms.transition, this.duration, {
value: 1,
ease: Power2.easeOut,
onStart: () => {
this.slides[this.activeTextureIndex].classList.remove("slide--active");
},
onComplete: () => {
this.isAnimating = false;
this.activeTextureIndex = this.nextTextureIndex;
// our next texture becomes our active texture
this.slideshowPlane.userData.activeTex.setSource(this.slideshowPlane.images[this.activeTextureIndex]);
// reset transition value
this.slideshowPlane.uniforms.transition.value = 0;
// show active slide title
this.slides[this.activeTextureIndex].classList.add("slide--active");
}
});
}
private changeTitles() {
const activeTitle = this.slides[this.activeTextureIndex].querySelector(".slide-title");
const nextTitle = this.slides[this.nextTextureIndex].querySelector(".slide-title");
TweenMax.to(activeTitle, this.duration / 2, {
ease: Power2.easeIn,
opacity: 0,
scaleX: 1.15,
scaleY: 1.15,
force3D: true,
});
TweenMax.to(nextTitle, this.duration / 2, {
ease: Power2.easeOut,
opacity: 1,
scaleX: 1,
scaleY: 1,
force3D: true,
}).delay(this.duration / 2);
}
}
export default WebglSlideshow
here is my nextJS page
import React, {useEffect} from 'react'
const WebglSlideshow = require('../../components/webgl-slideshow/webgl-slideshow');
function OurWorks() {
useEffect(()=>{
new WebglSlideshow.default()
},[])
return (
<>
<div id="curtains-canvas"></div>
<div id="slideshow">
<button id="previous-slide" className="slideshow-button">➜</button>
<button id="next-slide" className="slideshow-button">➜</button>
<div id="slides-list">
<div className="slide">
<h2 className="slide-title" data-title="Hong Kong">Hong Kong</h2>
<img src="/images/home_hero_1.png"
alt="Photo by Simon Zhu on Unsplash" crossOrigin={""}/>
</div>
<div className="slide">
<h2 className="slide-title" data-title="Hong Kong">testing</h2>
<img src="/images/modern-hd-map.jpg"
alt="Photo by Simon Zhu on Unsplash" crossOrigin={""}/>
</div>
<div className="slide">
<h2 className="slide-title" data-title="Hong Kong">testing</h2>
<img src="/images/work-place.png"
alt="Photo by Simon Zhu on Unsplash" crossOrigin={""}/>
</div>
</div>
</div>
</>
)
}
export default OurWorks
please look into the code and help me. thank you