<!-- fork https://codepen.io/martinlaxenaire/pen/GVROee -->
<!-- div that will hold our WebGL canvas -->
<div id="canvas"></div>
<div id="content">
<h1 id="title">Islands</h1>
<!-- drag slider -->
<div id="planes">
<div class="plane-wrapper">
<span class="plane-title">Bahamas</span>
<div class="plane">
<img src="https://source.unsplash.com/2fFd7SIVpz8/1280x720" alt="Photo by Gregory Culmer on Unsplash" data-sampler="planeTexture" crossorigin />
</div>
</div>
<div class="plane-wrapper">
<span class="plane-title">Philippines</span>
<div class="plane">
<img src="https://source.unsplash.com/SDaJRmZYMDA/1280x720" alt="Photo by James Connolly on Unsplash" data-sampler="planeTexture" crossorigin />
</div>
</div>
<div class="plane-wrapper">
<span class="plane-title">Indonesia</span>
<div class="plane">
<img src="https://source.unsplash.com/DxmBSgUYKis/1600x900" alt="Photo by sutirta budiman on Unsplash" data-sampler="planeTexture" crossorigin />
</div>
</div>
<div class="plane-wrapper">
<span class="plane-title">Maldives</span>
<div class="plane">
<img src="https://source.unsplash.com/lT9rqfG7lcQ/1280x720" alt="Photo by Jailam Rashad on Unsplash" data-sampler="planeTexture" crossorigin />
</div>
</div>
<div class="plane-wrapper">
<span class="plane-title">Greece</span>
<div class="plane">
<img src="https://source.unsplash.com/QXW1YEMhq_4/1280x720" data-sampler="planeTexture" alt="Photo by Chris Karidis on Unsplash" crossorigin />
</div>
</div>
<div class="plane-wrapper">
<span class="plane-title">Fiji</span>
<div class="plane">
<img src="https://source.unsplash.com/3jtm7BMsaPA/1280x720" alt="Photo by Lili Ortiz on Unsplash" data-sampler="planeTexture" crossorigin />
</div>
</div>
<div class="plane-wrapper">
<span class="plane-title">Thailand</span>
<div class="plane">
<img src="https://source.unsplash.com/Mwg_MdX-Jx4/1280x720" data-sampler="planeTexture" alt="Photo by Samule Sun on Unsplash" crossorigin />
</div>
</div>
</div>
<div id="drag-tip">
Drag to explore
</div>
</div>
<script id="slider-planes-vs" type="x-shader/x-vertex">
#ifdef GL_ES
precision mediump float;
#endif
// default mandatory attributes
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;
// those projection and model view matrices are generated by the library
// it will position and size our plane based on its HTML element CSS values
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
// this is generated by the library based on the sampler name we provided
// it will be used to map adjust our texture coords so the texture will fit the plane
uniform mat4 planeTextureMatrix;
// texture coord varying that will be passed to our fragment shader
varying vec2 vTextureCoord;
void main() {
// apply our vertex position based on the projection and model view matrices
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
// varying
// use texture matrix and original texture coords to generate accurate texture coords
vTextureCoord = (planeTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
}
</script>
<script id="slider-planes-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
// our texture coords varying
varying vec2 vTextureCoord;
// our texture sampler (see how its name matches the data-sampler attribute on our image tags)
uniform sampler2D planeTexture;
// our opacity uniform that goes from 0 to 1
uniform float uOpacity;
void main( void ) {
// map our texture to the varying texture coords
vec4 finalColor = texture2D(planeTexture, vTextureCoord);
// the distance from this point to the left edge is a float from 0 to 1
float distanceToLeft = distance(vec2(0.0, vTextureCoord.y), vTextureCoord);
// calculate an effect that goes from 0 to 1 depenging on uOpacity and distanceToLeft
float spreadFromLeft = clamp((uOpacity * (1.0 - distanceToLeft) - 1.0) + uOpacity * 2.0, 0.0, 1.0);
// handle pre-multiplied alpha on rgb values and use spreadFromLeft as alpha.
finalColor = vec4(vec3(finalColor.rgb * spreadFromLeft), spreadFromLeft);
// this is it
gl_FragColor = finalColor;
}
</script>
<script id="distortion-vs" type="x-shader/x-vertex">
#ifdef GL_ES
precision mediump float;
#endif
// default mandatory attributes
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;
// our displacement texture matrix uniform
uniform mat4 displacementTextureMatrix;
// mouse position and direction uniforms
uniform vec2 uMousePos;
uniform float uDirection;
// custom varyings
varying vec2 vTextureCoord;
// varying vec2 vDispTextureCoord;
varying vec2 vMouseTexCoords;
void main() {
gl_Position = vec4(aVertexPosition, 1.0);
// varyings
vTextureCoord = aTextureCoord;
// vDispTextureCoord = (displacementTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
// we will handle our mouse coords here for better performance
// get our texture coords for both directions
vec2 mouseHorizontalTexCoords = (uMousePos + 1.0) / 2.0;
mouseHorizontalTexCoords.y = 0.5;
vec2 mouseVerticalTexCoords = (uMousePos + 1.0) / 2.0;
mouseVerticalTexCoords.x = 0.5;
// use the right value for the right direction
vMouseTexCoords = mix(mouseHorizontalTexCoords, mouseVerticalTexCoords, uDirection);
}
</script>
<script id="distortion-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
// varyings
varying vec2 vTextureCoord;
// varying vec2 vDispTextureCoord;
varying vec2 vMouseTexCoords;
// our render texture is basically what's being drawn in our canvas
uniform sampler2D renderTexture;
// the displacement texture we've loaded into our shader pass
// uniform sampler2D displacementTexture;
// all our uniforms
uniform float uDragEffect;
uniform vec2 uMousePos;
// uniform vec2 uOffset;
uniform float uDirection;
uniform vec3 uBgColor;
void main( void ) {
vec2 textureCoords = vTextureCoord;
// repeat and offset our displacement map texture coords for both slider directions
// vec2 horizontalPhase = fract(vec2(vDispTextureCoord.x + uOffset.x, 0.0) / vec2(1.0, 1.0));
// vec2 verticalPhase = fract(vec2(vDispTextureCoord.x * (uOffset.x / 3000.0), vDispTextureCoord.y + uOffset.y) / vec2(1.0, 1.0));
// use the correct repeated and offseted texture coords
// vec2 phase = mix(horizontalPhase, vec2(0, 0), uDirection);
// vec4 displacement = texture2D(displacementTexture, verticalPhase);
// use our varying mouse texture coords
vec2 mouseTexCoords = vMouseTexCoords;
float distanceToMouse = distance(mouseTexCoords, textureCoords);
// calculate an effect that goes from 0 to 1 depenging on uDragEffect and distanceToMouse
float spreadFromMouse = clamp((uDragEffect * (1.0 - distanceToMouse) - 1.0) + uDragEffect * 2.0, 0.0, 1.0);
// calculate our fish eye like distortions
vec2 fishEye = (vec2(textureCoords - mouseTexCoords).xy) * pow(distanceToMouse, 2.5);
// add a displacement based on our map and our time uniform
// float displacementEffect = displacement.r * 1.25;
// spread our fish eye and displacement effects from our mouse
// calculate for both directions
vec2 horizontalTexCoords = textureCoords;
horizontalTexCoords.x -= spreadFromMouse * fishEye.x * 0.25 * (1.0 - uDirection);
horizontalTexCoords.y -= spreadFromMouse * fishEye.y * 0.25 * uDirection;
vec2 verticalTexCoords = textureCoords;
verticalTexCoords.x -= spreadFromMouse * fishEye.x * 0.25 * (1.0 - uDirection);
verticalTexCoords.y -= spreadFromMouse * fishEye.y * 0.25 * uDirection;
// use the right value for the right direction
textureCoords = mix(horizontalTexCoords, verticalTexCoords, uDirection);
// get our final colored and BW vec4
vec4 finalColor = texture2D(renderTexture, textureCoords);
// float grey = dot(finalColor.rgb, vec3(0.299, 0.587, 0.114));
// vec4 finalGrey = vec4(vec3(grey), 1.0);
// mix our both vec4 based on our spread value
//finalColor = mix(finalColor, finalGrey, spreadFromMouse * finalColor.a);
// apply a grey background where we don't have nothing to draw
finalColor = mix(vec4(uBgColor.r * spreadFromMouse / 255.0, uBgColor.g * spreadFromMouse / 255.0, uBgColor.b * spreadFromMouse / 255.0, spreadFromMouse), finalColor, finalColor.a);
gl_FragColor = finalColor;
}
</script>
@media screen {
html, body {
min-height: 100%;
}
body {
margin: 0;
font-size: 18px;
font-family: 'Arvo', Verdana, sans-serif;
background: #ece5d1;
line-height: 1.4;
overflow: hidden;
}
/*** canvas ***/
/* our canvas will have the size of our window */
#canvas {
position: fixed;
top: 0;
right: 0;
left: 0;
height: 100vh;
z-index: 1;
}
/*** content ***/
#content {
position: relative;
z-index: 2;
overflow: hidden;
}
#title {
position: fixed;
top: 20px;
right: 20px;
left: 20px;
z-index: 1;
pointer-events: none;
font-size: 1.5em;
line-height: 1;
margin: 0;
text-transform: uppercase;
color: #032f4d;
text-align: center;
}
#planes {
/* width of items * number of items */
width: calc(((100vw / 1.75) + 10vw) * 7);
padding: 0 2.5vw;
height: 100vh;
display: flex;
align-items: center;
cursor: move;
}
.plane-wrapper {
position: relative;
width: calc(100vw / 1.75);
height: 70vh;
/* margin: auto 5vw; */
text-align: center;
}
/* disable pointer events and text selection during drag */
#planes.dragged .plane-wrapper {
pointer-events: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.plane-title {
position: absolute;
top: 50%;
left: 50%;
z-index: 1;
transform: translate3D(-50%, -50%, 0);
font-size: 4vw;
font-weight: 700;
line-height: 1.2;
text-transform: uppercase;
color: #032f4d;
text-stroke: 1px white;
-webkit-text-stroke: 1px white;
opacity: 0;
transition: color 0.5s, opacity 0.5s;
}
#planes.dragged .plane-title {
color: transparent;
}
.plane-wrapper.loaded .plane-title, .no-curtains .plane-title {
opacity: 1;
}
.plane {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.plane img {
/* hide original images if there's no WebGL error */
display: none;
/* prevent original image from dragging */
pointer-events: none;
-webkit-user-drag: none;
-khtml-user-drag: none;
-moz-user-drag: none;
-o-user-drag: none;
user-drag: none;
}
#drag-tip {
position: fixed;
right: 20px;
bottom: 20px;
left: 20px;
z-index: 1;
pointer-events: none;
font-size: 0.9em;
text-transform: uppercase;
color: #032f4d;
text-align: center;
}
/*** handling WebGL errors ***/
.no-curtains #planes {
transition: background-color 0.5s;
}
.no-curtains #planes.dragged {
background-color: #03879a;
}
.no-curtains .plane-title {
opacity: 1;
}
.no-curtains .plane {
display: flex;
overflow: hidden;
transition: filter 0.5s;
}
.no-curtains #planes.dragged .plane {
filter: grayscale(1);
}
.no-curtains .plane img {
display: block;
min-width: 100%;
min-height: 100%;
object-fit: cover;
}
}
@media screen and (orientation: portrait) {
#content {
max-height: 100vh;
}
#planes {
overflow: hidden;
width: 100vw;
padding: 2.5vh 0;
height: auto;
flex-direction: column;
}
.plane-wrapper {
width: 70vw;
height: calc(100vh / 1.75);
/* margin: 5vw 0; */
}
.plane-title {
font-size: 10vw;
}
}
class Slider {
/*** CONSTRUCTOR ***/
constructor(options = {}) {
// our options
this.options = {
// slider state and values
// the div we are going to translate
element: options.element || document.getElementById("planes"),
// easing value, the lower the smoother
easing: options.easing || 0.1,
// translation speed
// 1: will follow the mouse
// 2: will go twice as fast as the mouse, etc
dragSpeed: options.dragSpeed || 1,
// duration of the in animation
duration: options.duration || 750,
};
// if we are currently dragging
this.isMouseDown = false;
// if the slider is currently translating
this.isTranslating = false;
// current position
this.currentPosition = 0;
// drag start position
this.startPosition = 0;
// drag end position
this.endPosition = 0;
// slider translation
this.translation = 0;
this.animationFrame = null;
// set up the slider
this.setupSlider();
}
/*** HELPERS ***/
// lerp function used for easing
lerp(value1, value2, amount) {
amount = amount < 0 ? 0 : amount;
amount = amount > 1 ? 1 : amount;
return (1 - amount) * value1 + amount * value2;
}
// return our mouse or touch position
getMousePosition(e) {
var mousePosition;
if(e.targetTouches) {
if(e.targetTouches[0]) {
mousePosition = [e.targetTouches[0].clientX, e.targetTouches[0].clientY];
}
else if(e.changedTouches[0]) {
// handling touch end event
mousePosition = [e.changedTouches[0].clientX, e.changedTouches[0].clientY];
}
else {
// fallback
mousePosition = [e.clientX, e.clientY];
}
}
else {
mousePosition = [e.clientX, e.clientY];
}
return mousePosition;
}
// set the slider boundaries
// we will translate it horizontally in landscape mode
// vertically in portrait mode
setBoundaries() {
if(window.innerWidth >= window.innerHeight) {
// landscape
this.boundaries = {
max: -1 * this.options.element.clientWidth + window.innerWidth,
min: 0,
sliderSize: this.options.element.clientWidth,
referentSize: window.innerWidth,
};
// set our slider direction
this.direction = 0;
}
else {
// portrait
this.boundaries = {
max: -1 * this.options.element.clientHeight + window.innerHeight,
min: 0,
sliderSize: this.options.element.clientHeight,
referentSize: window.innerHeight,
};
// set our slider direction
this.direction = 1;
}
}
/*** HOOKS ***/
// this is called once our mousedown / touchstart event occurs and the drag started
onDragStarted(mousePosition) {
}
// this is called while we are currently dragging the slider
onDrag(mousePosition) {
}
// this is called once our mouseup / touchend event occurs and the drag started
onDragEnded(mousePosition) {
}
// this is called continuously while the slider is translating
onTranslation() {
}
// this is called once the translation has ended
onTranslationEnded() {
}
// this is called after our slider has been resized
onSliderResized() {
}
/*** ANIMATIONS ***/
// this will translate our slider HTML element and set up our hooks
translateSlider(translation) {
translation = Math.floor(translation * 100) / 100;
// should we translate it horizontally or vertically?
var direction = this.direction === 0 ? "translateX" : "translateY";
// apply translation
this.options.element.style.transform = direction + "(" + translation + "px)";
// if the slider translation is different than the translation to apply
// that means the slider is still translating
if(this.translation !== translation) {
// hook function to execute while we are translating
this.onTranslation();
}
else if(this.isTranslating && !this.isMouseDown) {
// if those conditions are met, that means the slider is no longer translating
this.isTranslating = false;
// hook function to execute after translation has ended
this.onTranslationEnded();
}
// finally set our translation
this.translation = translation;
}
// this is our request animation frame loop where we will translate our slider
animate() {
// interpolate values
var translation = this.lerp(this.translation, this.currentPosition, this.options.easing);
// apply our translation
this.translateSlider(translation);
this.animationFrame = requestAnimationFrame(this.animate.bind(this));
}
/*** EVENTS ***/
// on mouse down or touch start
onMouseDown(e) {
// start dragging
this.isMouseDown = true;
// apply specific styles
this.options.element.classList.add("dragged");
// get our touch/mouse start position
var mousePosition = this.getMousePosition(e);
// use our slider direction to determine if we need X or Y value
this.startPosition = mousePosition[this.direction];
// drag start hook
this.onDragStarted(mousePosition);
}
// on mouse or touch move
onMouseMove(e) {
// if we are not dragging, we don't do nothing
if(!this.isMouseDown) return;
// get our touch/mouse position
var mousePosition = this.getMousePosition(e);
// get our current position
this.currentPosition = this.endPosition + ((mousePosition[this.direction] - this.startPosition) * this.options.dragSpeed);
// if we're not hitting the boundaries
if(this.currentPosition > this.boundaries.min && this.currentPosition < this.boundaries.max) {
// if we moved that means we have started translating the slider
this.isTranslating = true;
}
else {
// clamp our current position with boundaries
this.currentPosition = Math.min(this.currentPosition, this.boundaries.min);
this.currentPosition = Math.max(this.currentPosition, this.boundaries.max);
}
// drag hook
this.onDrag(mousePosition);
}
// on mouse up or touchend
onMouseUp(e) {
// we have finished dragging
this.isMouseDown = false;
// remove specific styles
this.options.element.classList.remove("dragged");
// update our end position
this.endPosition = this.currentPosition;
// send our mouse/touch position to our hook
var mousePosition = this.getMousePosition(e);
// drag ended hook
this.onDragEnded(mousePosition);
}
// on resize we will need to apply old translation value to new sizes
onResize(e) {
// get our old translation ratio
var ratio = this.translation / this.boundaries.sliderSize;
// reset boundaries and properties bound to window size
this.setBoundaries();
// reset all translations
this.options.element.style.transform = "tanslate3d(0, 0, 0)";
// calculate our new translation based on the old translation ratio
var newTranslation = ratio * this.boundaries.sliderSize;
// clamp translation to the new boundaries
newTranslation = Math.min(newTranslation, this.boundaries.min);
newTranslation = Math.max(newTranslation, this.boundaries.max);
// apply our new translation
this.translateSlider(newTranslation);
// reset current and end positions
this.currentPosition = newTranslation;
this.endPosition = newTranslation;
// call our resize hook
this.onSliderResized();
}
/*** SET UP AND DESTROY ***/
// set up our slider
// init its boundaries, add event listeners and start raf loop
setupSlider() {
this.setBoundaries();
// event listeners
// mouse events
window.addEventListener("mousemove", this.onMouseMove.bind(this), {
passive: true,
});
window.addEventListener("mousedown", this.onMouseDown.bind(this));
window.addEventListener("mouseup", this.onMouseUp.bind(this));
// touch events
window.addEventListener("touchmove", this.onMouseMove.bind(this), {
passive: true,
});
window.addEventListener("touchstart", this.onMouseDown.bind(this), {
passive: true,
});
window.addEventListener("touchend", this.onMouseUp.bind(this));
// resize event
window.addEventListener("resize", this.onResize.bind(this));
// launch our request animation frame loop
this.animate();
}
// will be called silently to cleanly remove the slider
destroySlider() {
// remove event listeners
// mouse events
window.removeEventListener("mousemove", this.onMouseMove, {
passive: true,
});
window.removeEventListener("mousedown", this.onMouseDown);
window.removeEventListener("mouseup", this.onMouseUp);
// touch events
window.removeEventListener("touchmove", this.onMouseMove, {
passive: true,
});
window.removeEventListener("touchstart", this.onMouseDown, {
passive: true,
});
window.removeEventListener("touchend", this.onMouseUp);
// resize event
window.removeEventListener("resize", this.onResize);
// cancel request animation frame
cancelAnimationFrame(this.animationFrame);
}
// call this method publicly to destroy our slider
destroy() {
// destroy everything related to the slider
this.destroySlider();
}
};
class WebGLSlider extends Slider {
/*** CONSTRUCTOR ***/
constructor(options) {
super(options);
// tweening
this.animation = null;
// value from 0 to 1 to pass as uniform to the WebGL
// will be tweened on mousedown / touchstart and mouseup / touchend events
this.effect = 0;
// our WebGL variables
this.curtains = null;
this.planes = [];
this.shaderPass = null;
// set up the WebGL part
this.setupWebGL();
}
/*** WEBGL INIT ***/
// set up WebGL context and scene
setupWebGL() {
// set up our WebGL context, append the canvas to our wrapper and create a requestAnimationFrame loop
// the canvas will be our scene containing all our planes
// this is the scene we will post process
this.curtains = new Curtains({
container: "canvas"
});
this.curtains.onError(function() {
// onError handles all errors during WebGL context initialization or plane creation
// we will add a class to the document body to display original images (see CSS)
document.body.classList.add("no-curtains");
});
// planes and shader pass
this.setupPlanes();
this.setupShaderPass();
}
/*** PLANES CREATION ***/
setupPlanes() {
// Planes
// each plane is bound to a HTML element to copy its size and position
// in this case this will be the slider inner items
// it will automatically create a WebGL texture for each image, canvas and video child of that element
var planeElements = document.getElementsByClassName("plane");
// our planes params
// we just pass our shaders tag ID and a uniform to animate opacity on load
var params = {
vertexShaderID: "slider-planes-vs",
fragmentShaderID: "slider-planes-fs",
uniforms: {
opacity: {
name: "uOpacity", // variable name inside our shaders
type: "1f", // this means our uniform is a float
value: 0,
},
},
};
// add all our planes and handle them
for(var i = 0; i < planeElements.length; i++) {
// addPlane method adds a plane to our WebGL scene
// takes 2 params: our HTML referent element and the params set above
// it returns a Plane class object if creation is successful, false otherwise
var plane = this.curtains.addPlane(planeElements[i], params);
// if our plane has been successfully created
if(plane) {
// push it into our planes array
this.planes.push(plane);
// onReady is called once our plane is ready and all its texture have been created
plane.onReady(function() {
// inside our onReady function scope, this represents our plane
var currentPlane = this;
// add a "loaded" class to display the title
currentPlane.htmlElement.closest(".plane-wrapper").classList.add("loaded");
// animate plane opacity once they are loaded
var opacity = {
value: 0,
};
anime({
targets: opacity,
value: 1,
easing: "linear",
duration: 750,
update: function() {
// continualy increase opacity from 0 to 1
currentPlane.uniforms.opacity.value = opacity.value;
},
});
});
}
}
}
/*** SHADER PASS CREATION ***/
setupShaderPass() {
// Shader pass
// we will post process our scene
// that means we will apply shaders to our whole scene
// like for regular planes we will need params
// they will contain vertex and fragment shaders ID and our uniforms
var shaderPassParams = {
vertexShaderID: "distortion-vs",
fragmentShaderID: "distortion-fs",
uniforms: {
// apply the whole effect
// 0: no effect
// 1: full effect
dragEffect: {
name: "uDragEffect", // variable name inside our shaders
type: "1f", // this means our uniform is a float
value: 0,
},
// our mouse position (in WebGL clip space coordinates)
mousePos: {
name: "uMousePos",
type: "2f", // this means our uniform is a length 2 array of floats
value: [0, 0],
},
// direction of our slider
// 0: horizontal drag
// 1: vertical drag
direction: {
name: "uDirection",
type: "1f",
value: this.direction,
},
// the background color when effect is applied
bgColor: {
name: "uBgColor",
type: "3f", // this means our uniform is a length 3 array of floats
value: [3, 135, 154], // rgb values
},
// our displacement texture offset
// offset: {
// name: "uOffset",
// type: "2f",
// value: [0, 0],
// },
},
};
// addShaderPass adds a shader pass (Frame Buffer Object) to our WebGL scene
// returns a ShaderPass class object if successful, false otherwise
this.shaderPass = this.curtains.addShaderPass(shaderPassParams);
// if our shader pass has been successfully created
// if(this.shaderPass) {
// var self = this;
// // onRender is called at each requestAnimationFrame call
// this.shaderPass.onRender(function() {
// // we will continuously offset our displacement texture on secondary axis
// var secondaryDirection = self.direction === 0 ? 1 : 0;
// self.shaderPass.uniforms.offset.value[secondaryDirection] = self.shaderPass.uniforms.offset.value[secondaryDirection] + 1;
// });
// }
}
/*** HELPER ***/
// this will update our shader pass mouse position uniform
updateMousePosUniform(mousePosition) {
// if our shader pass exists, update the mouse position uniform
if(this.shaderPass) {
// mouseToPlaneCoords converts window coordinates to WebGL clip space
var relativeMousePos = this.shaderPass.mouseToPlaneCoords(mousePosition[0], mousePosition[1]);
this.shaderPass.uniforms.mousePos.value = [relativeMousePos.x, relativeMousePos.y];
}
}
/*** HOOKS ***/
// this is called once our mousedown / touchstart event occurs and the drag started
onDragStarted(mousePosition) {
// pause and remove previous animation
if(this.animation) this.animation.pause();
anime.remove(slider);
// get a ref
var self = this;
// animate our mouse down effect
this.animation = anime({
targets: self,
effect: 1,
easing: 'easeOutCubic',
duration: self.options.duration,
update: function() {
if(self.shaderPass) {
// update our shader pass uniforms
self.shaderPass.uniforms.dragEffect.value = self.effect;
}
}
});
// enableDrawing to re-enable drawing again if we disabled it earlier
this.curtains.enableDrawing();
// update our shader pass mouse position uniform
this.updateMousePosUniform(mousePosition);
}
// this is called while we are currently dragging the slider
onDrag(mousePosition) {
// update our shader pass mouse position uniform
this.updateMousePosUniform(mousePosition);
}
// this is called once our mouseup / touchend event occurs and the drag started
onDragEnded(mousePosition) {
// calculate duration based on easing
var duration = 100 / this.options.easing;
var easing = 'linear';
// if there's no movement just tween the shader pass effect
if(Math.abs(this.translation - this.currentPosition) < 5) {
easing = 'easeOutCubic';
duration = this.options.duration;
}
// pause remove previous animation
if(this.animation) this.animation.pause();
anime.remove(slider);
// get a ref
var self = this;
this.animation = anime({
targets: self,
effect: 0,
easing: easing,
duration: duration,
update: function() {
if(self.shaderPass) {
// update drag effect
self.shaderPass.uniforms.dragEffect.value = self.effect;
}
}
});
// update our shader pass mouse position uniform
this.updateMousePosUniform(mousePosition);
}
// this is called continuously while the slider is translating
onTranslation() {
// keep our WebGL planes position in sync with their HTML elements
for(var i = 0; i < this.planes.length; i++) {
// updatePosition is a useful method that apply the HTML element position to our WebGL plane
this.planes[i].updatePosition();
}
// shader pass displacement texture offset
// if(this.shaderPass) {
// // we will offset our displacement effect on main axis so it follows the drag
// var offset = ((this.direction - 1) * 2 + 1) * this.translation / this.boundaries.referentSize;
// this.shaderPass.uniforms.offset.value[this.direction] = offset;
// }
}
// this is called once the translation has ended
onTranslationEnded() {
// we will stop rendering our WebGL until next drag occurs
if(this.curtains) {
this.curtains.disableDrawing();
}
}
// this is called after our slider has been resized
onSliderResized() {
// update our direction uniform
if(this.shaderPass) {
// update direction
this.shaderPass.uniforms.direction.value = this.direction;
}
}
/*** DESTROY ***/
// destroy all WebGL related things
destroyWebGL() {
// if you want to totally remove the WebGL context uncomment next line
// and remove what's after
//this.curtains.dispose();
// if you want to only remove planes and shader pass and keep the context available
// that way you could re init the WebGL later to display the slider again
if(this.shaderPass) {
this.curtains.removeShaderPass(this.shaderPass);
}
for(var i = 0; i < this.planes.length; i++) {
this.curtains.removePlane(this.planes[i]);
}
}
// call this method publicly to destroy our slider and the WebGL part
// override the destroy method of the Slider class
destroy() {
// destroy everything related to WebGL and the slider
this.destroyWebGL();
this.destroySlider();
}
}
// custom options
var options = {
easing: 0.1,
duration: 500,
dragSpeed: 1.75,
}
// let's go!
var slider = new WebGLSlider(options);
This Pen doesn't use any external CSS resources.