<main class="wrapper">
<div class="canvas"></div>
<section class="slides multi-textures">
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/displacement4.jpg" data-sampler="displacement" />
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/city/amsterdam.jpg" />
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/city/bilbao.jpg" />
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/city/golden-gate-bridge.jpg" />
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/city/valencia.jpg" />
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/city/water.jpg" />
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/city/peine.jpg" />
<nav class="nav">
<button class="btn" data-goto="prev" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H6M12 5l-7 7 7 7"/></svg>
</button>
<button class="btn" data-goto="next" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h13M12 5l7 7-7 7"/></svg>
</button>
</nav>
</section>
</main>
<div class="open-modal">
<button class="js-open-modal" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="21" x2="4" y2="14"></line><line x1="4" y1="10" x2="4" y2="3"></line><line x1="12" y1="21" x2="12" y2="12"></line><line x1="12" y1="8" x2="12" y2="3"></line><line x1="20" y1="21" x2="20" y2="16"></line><line x1="20" y1="12" x2="20" y2="3"></line><line x1="1" y1="14" x2="7" y2="14"></line><line x1="9" y1="8" x2="15" y2="8"></line><line x1="17" y1="16" x2="23" y2="16"></line></svg>
</button>
<button class="js-close-modal" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
<div class="modal">
<div class="modal__content">
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/displacement4.jpg" data-setting class="active" alt="">
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/displacement.jpg" data-setting alt="">
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/displacement1.jpg" data-setting alt="">
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/displacement2.jpg" data-setting alt="">
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/displacement3.png" data-setting alt="">
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/displacement5.png" data-setting alt="">
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/displacement6.jpg" data-setting alt="">
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/displacement7.jpg" data-setting alt="">
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/displacement8.jpg" data-setting alt="">
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/displacement9.jpg" data-setting alt="">
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/displacement10.jpg" data-setting alt="">
<img src="https://raw.githubusercontent.com/ivanalbizu/WEBGL-texture-Slideshow/main/src/img/displacement11.jpg" data-setting alt="">
</div>
</div>
html,
body {
min-height: 100%;
}
body {
margin: 0;
font-family: sans-serif;
background: #ffffff;
overflow: hidden;
}
.slides {
display: flex;
flex-wrap: wrap;
}
.wrapper {
width: 100%;
height: 100vh;
overflow: hidden;
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
grid-gap: 0;
}
.canvas {
height: 100vh;
width: 100vw;
z-index: 10;
grid-area: 1 / 1 / 1 / 1;
}
.multi-textures {
grid-area: 1 / 1 / 1 / 1;
z-index: 15;
align-content: center;
img {
display: none;
min-width: 100%;
min-height: 100%;
object-fit: cover;
}
}
.no-curtains {
.multi-textures {
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
&.image-1 .multi-textures img:nth-child(2) {
display: block;
}
&.image-2 .multi-textures img:nth-child(3) {
display: block;
}
&.image-3 .multi-textures img:nth-child(4) {
display: block;
}
&.image-4 .multi-textures img:nth-child(5) {
display: block;
}
&.image-5 .multi-textures img:nth-child(6) {
display: block;
}
}
.nav {
display: flex;
justify-content: space-between;
width: 100%;
}
.btn {
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.9);
color: #fff;
border: 1px solid rgba(0, 0, 0, 0.2);
width: 4em;
height: 4em;
margin: 0.5em .2em;
cursor: pointer;
transition: all .7s ease-in 0s;
svg,
svg path {
pointer-events: none;
}
}
// Not necessary. Use to show example textures animations
.open-modal {
position: fixed;
top: 4px;
right: 18px;
background-color: rgba(0, 0, 0, .9);
z-index: 18;
button {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
cursor: pointer;
}
.js-close-modal {
display: none;
}
}
.modal {
position: fixed;
right: 0;
width: 300px;
top: 0;
bottom: 0;
z-index: 17;
overflow-y: auto;
transform: translateX(300px);
transition: transform .3s ease-in-out;
img {
max-width: 100%;
z-index: 19;
}
&__content {
display: flex;
flex-wrap: wrap;
overflow-y: auto;
background-color: rgba(0, 0, 0, .85);
padding: 2.5em 1.5em;
img {
border: 2px dashed #fff;
width: 235px;
height: 155px;
&:not(:last-of-type) {
margin-bottom: 2em;
}
&.active {
border-style: solid;
}
}
}
}
.modal-active {
.open-modal {
.js-close-modal {
display: flex;
}
.js-open-modal {
display: none;
}
}
.modal {
transform: translateX(0px);
transition: transform .3s ease-in-out;
}
}
View Compiled
import {Curtains, Plane} from 'https://cdn.jsdelivr.net/npm/curtainsjs@7.1.0/src/index.mjs';
//import fragment from './shaders/fragment.glsl'; //path to local file, instead of creating the variables below
//import vertex from './shaders/vertex.glsl'; //path to local file, instead of creating the variables below
const fragment = `
precision mediump float;
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
varying vec2 vActiveTextureCoord;
varying vec2 vNextTextureCoord;
// custom uniforms
uniform float uTransitionTimer;
// our textures samplers
// notice how it matches the sampler attributes of the textures we created dynamically
uniform sampler2D activeTexture;
uniform sampler2D nextTexture;
uniform sampler2D displacement;
void main() {
// our displacement texture
vec4 displacementTexture = texture2D(displacement, vTextureCoord);
// slides transitions based on displacement and transition timer
vec2 firstDisplacementCoords = vActiveTextureCoord + displacementTexture.r * ((cos((uTransitionTimer + 90.0) / (90.0 / 3.141592)) + 1.0) / 1.25);
vec4 firstDistortedColor = texture2D(activeTexture, vec2(vActiveTextureCoord.x, firstDisplacementCoords.y));
// same as above but we substract the effect
vec2 secondDisplacementCoords = vNextTextureCoord - displacementTexture.r * ((cos(uTransitionTimer / (90.0 / 3.141592)) + 1.0) / 1.5);
vec4 secondDistortedColor = texture2D(nextTexture, vec2(vNextTextureCoord.x, secondDisplacementCoords.y));
// mix both texture
vec4 finalColor = mix(firstDistortedColor, secondDistortedColor, 1.0 - ((cos(uTransitionTimer / (90.0 / 3.141592)) + 1.0) / 2.0));
// handling premultiplied alpha
finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);
gl_FragColor = finalColor;
}
`;
const vertex = `
precision mediump float;
// default mandatory variables
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
// textures matrices
uniform mat4 activeTextureMatrix;
uniform mat4 nextTextureMatrix;
// varyings : notice we've got 3 texture coords varyings
// displacement texture / visible texture / upcoming texture
varying vec3 vVertexPosition;
varying vec2 vTextureCoord; // displacement
varying vec2 vActiveTextureCoord;
varying vec2 vNextTextureCoord;
// custom uniforms
uniform float uTransitionTimer;
void main() {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
// varyings
// use original texture coords for our displacement
vTextureCoord = aTextureCoord;
vActiveTextureCoord = (activeTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
vNextTextureCoord = (nextTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
vVertexPosition = aVertexPosition;
}
`;
class WebglSlides {
constructor(set) {
this.canvas = set.canvas
this.planeElement = set.planeElement
this.multiTexturesPlane = null
this.slidesState = {
activeTextureIndex: 1,
nextTextureIndex: null,
maxTextures: set.planeElement.querySelectorAll("img").length - 1, // -1 to displacement
navs: set.navs,
isChanging: false,
transitionTimer: 0,
}
this.params = {
vertexShader: vertex,
fragmentShader: fragment,
uniforms: {
transitionTimer: {
name: "uTransitionTimer",
type: "1f",
value: 0,
},
},
}
this.init()
}
init() {
this.setupCurtains()
this.initPlane()
this.update()
}
setupCurtains() {
this.curtains = new Curtains({
container: this.canvas,
watchScroll: false,
pixelRatio: Math.min(1.5, window.devicePixelRatio)
})
this.curtains.onError(() => this.error());
this.curtains.onContextLost(() => this.restoreContext());
}
initPlane() {
this.multiTexturesPlane = new Plane(this.curtains, this.planeElement, this.params)
this.multiTexturesPlane
.onLoading(texture => {
texture.setMinFilter(this.curtains.gl.LINEAR_MIPMAP_NEAREST)
})
.onReady(() => {
const activeTexture = this.multiTexturesPlane.createTexture({
sampler: "activeTexture",
fromTexture: this.multiTexturesPlane.textures[this.slidesState.activeTextureIndex]
})
const nextTexture = this.multiTexturesPlane.createTexture({
sampler: "nextTexture",
fromTexture: this.multiTexturesPlane.textures[this.slidesState.nextTextureIndex]
})
this.initEvent(activeTexture, nextTexture)
})
}
update() {
this.multiTexturesPlane.onRender(() => {
if (this.slidesState.isChanging) {
this.slidesState.transitionTimer += (90 - this.slidesState.transitionTimer) * 0.04;
if (this.slidesState.transitionTimer >= 88.5 && this.slidesState.transitionTimer !== 90) {
this.slidesState.transitionTimer = 90;
}
}
this.multiTexturesPlane.uniforms.transitionTimer.value = this.slidesState.transitionTimer;
});
}
initEvent(activeTexture, nextTexture) {
this.slidesState.navs.forEach(nav => {
nav.addEventListener('click', event => {
if (!this.slidesState.isChanging) {
this.curtains.enableDrawing()
this.slidesState.isChanging = true;
const to = event.target.getAttribute('data-goto');
this.navigationDirection(to);
nextTexture.setSource(this.multiTexturesPlane.images[this.slidesState.nextTextureIndex]);
setTimeout(() => {
this.curtains.disableDrawing();
this.slidesState.isChanging = false;
this.slidesState.activeTextureIndex = this.slidesState.nextTextureIndex;
activeTexture.setSource(this.multiTexturesPlane.images[this.slidesState.activeTextureIndex]);
this.slidesState.transitionTimer = 0;
}, 1700);
}
})
})
}
navigationDirection(to) {
if (to == 'next') {
if (this.slidesState.activeTextureIndex < this.slidesState.maxTextures) {
this.slidesState.nextTextureIndex = this.slidesState.activeTextureIndex + 1
} else {
this.slidesState.nextTextureIndex = 1
}
} else {
if (this.slidesState.activeTextureIndex <= 1) {
this.slidesState.nextTextureIndex = this.slidesState.maxTextures
} else {
this.slidesState.nextTextureIndex = this.slidesState.activeTextureIndex - 1
}
}
}
error() {
document.body.classList.add("no-curtains", "image-1");
this.slidesState.navs.forEach(nav => {
nav.addEventListener("click", event => {
const to = event.target.getAttribute('data-goto');
navigationDirection(to);
document.body.classList.remove("image-1", "image-2", "image-3", "image-4");
document.body.classList.add("image-" + this.slidesState.nextTextureIndex);
this.slidesState.activeTextureIndex = this.slidesState.nextTextureIndex;
});
})
}
restoreContext() {
this.curtains.restoreContext();
}
removePlanes() {
this.curtains.dispose();
}
}
window.addEventListener("load", () => {
const wrapper = document.querySelector('.wrapper')
const canvas = wrapper.querySelector('.canvas')
const planeElement = wrapper.querySelector('.multi-textures')
const navs = wrapper.querySelectorAll('[data-goto]')
let slide = new WebglSlides({
canvas,
planeElement,
navs
})
// Down, not necesary, only for change Displacements Texture
document.querySelector('.js-open-modal').addEventListener('click', () => {
document.body.classList.add('modal-active')
})
document.querySelector('.js-close-modal').addEventListener('click', () => {
document.body.classList.remove('modal-active')
})
const settings = document.querySelectorAll('[data-setting]');
settings.forEach(setting => {
setting.addEventListener('click', event => {
const target = event.target;
const path = target.getAttribute('src')
settings.forEach(setting => setting.classList.remove('active'))
target.classList.add('active')
console.log('path :>> ', path);
document.querySelector('[data-sampler]').src = path
slide.removePlanes()
slide = new WebglSlides({
canvas,
planeElement,
navs
})
})
})
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.