Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
June 25, 2022 06:52 am GMT

Project Moon - GSAP Cursor Animation Navigation Menu WebGl Slider

Purpose Of Project

My JavaScript Testing ground for the foreseeable future.

Getting started

Go ahead and initialise our new project using the CodePen playground or setup your own project on Visual Studio Code with the following file structure under your src folder.

Project Moon Starter Files  |- Assets    |- CSS      |- style.css     |- JS      |- main.js  |- /src    |- index.html

Part 1: HTML

Start by editing your index.html and replace it with the following code.

<!DOCTYPE html><html lang="en">  <head>    <meta charset="UTF-8" />    <link rel="icon" type="image/svg+xml" href="favicon.svg" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <title>Project Moon | Navigation + Slider</title>    <link href="data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAAwAEAAOADAADn8wAA8+cAAPHHAAD5zwAA+I8AAPyfAAD8HwAA/j8AAP4/AAD/fwAA//8AAP//AAD//wAA" rel="icon" type="image/x-icon" />    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" rel="stylesheet">    <link rel="stylesheet" href="assets/css/style.css">  </head>  <body>    <!--Cursor-->    <div>        <div class="cursor"></div>        <div class="cursorDot"></div>    </div>    <main><!-- Start Navigation --><header id="header">    <div class="header-row">      <div class="brand-logo">        <a class="brand-text cursor-scale small" href="#">Project Moon</a>      </div>     <div class="main cursor-scale small">      <div class="bars"></div>    </div>    <div class="menu">      <div class="navBefore"></div>      <div class="nav">        <ul class="navigation">          <li><a href="#" class="cursor-scale">Home</a></li>          <li><a href="#" class="cursor-scale">About</a></li>          <li><a href="#" class="cursor-scale">Work</a></li>          <li><a href="#" class="cursor-scale">Contact</a></li>          <li><a target="_blank" href="#">EN</a></li>        </ul>      </div>    </div>    </div>  </header>  <section id="content">    <div id="planes">      <div class="plane-wrapper">        <span class="plane-title">JAPAN</span>        <div class="plane">          <img src="https://images.unsplash.com/photo-1545569341-9eb8b30979d9?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NHx8amFwYW58ZW58MHx8MHx8&auto=format&fit=crop&w=600&q=60" alt="Photo by Su San Lee on Unsplash" data-sampler="planeTexture" crossorigin />        </div>      </div>          <div class="plane-wrapper">        <span class="plane-title">AUSTRALIA</span>        <div class="plane">          <img src="https://images.unsplash.com/photo-1506973035872-a4ec16b8e8d9?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8YXVzdHJhbGlhfGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=600&q=60" alt="Photo by Dan Freeman on Unsplash" data-sampler="planeTexture" crossorigin />        </div>      </div>          <div class="plane-wrapper">        <span class="plane-title">USA</span>        <div class="plane">          <img src="https://images.unsplash.com/photo-1591437009328-f4499ddd7eb0?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTV8fHRleGFzJTIwZmxhZ3xlbnwwfHwwfHw%3D&auto=format&fit=crop&w=600&q=60" alt="Photo by Aaron Burden on Unsplash" data-sampler="planeTexture" crossorigin />        </div>      </div>          <div class="plane-wrapper">        <span class="plane-title">UK</span>        <div class="plane">          <img src="https://images.unsplash.com/photo-1513635269975-59663e0ac1ad?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8ZW5nbGFuZHxlbnwwfHwwfHw%3D&auto=format&fit=crop&w=600&q=60" alt="Photo by Ben Davies on Unsplash" data-sampler="planeTexture" crossorigin />        </div>      </div>    </div>  </section>    </main>          <!-- GSAP CDN -->         <script src="https://unpkg.co/gsap@3/dist/gsap.min.js"></script>          <!-- CurtainJS CDN -->         <script src="https://www.curtainsjs.com/build/curtains.min.js"></script>          <!-- AnimeJS CDN -->         <script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/2.2.0/anime.min.js"></script>         <!-- Core theme JS-->         <script src="assets/js/main.js"></script>        </body>  </html>

Part 2: CSS

Next step is to add the following styles and complete our style.css file.

@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800;900&family=Orbitron:wght@400;500;600;700;800;900&display=swap');/* Base reset */* {    margin: 0;    padding: 0;    box-sizing: border-box;    -webkit-box-sizing: border-box;    -moz-box-sizing: border-box;  }  /*Body styling*/body {    font-family: "Orbitron", sans-serif;    letter-spacing: 2px;    line-height: 2;    background-color: black;    -webkit-font-smoothing: antialiased;      -moz-osx-font-smoothing: grayscale;  }  /*Nav Styles*/#header{    position: fixed;    z-index: 10;    left: 0;    top: 0;    width: 100%;    height: 100vh;  }  .header-row{    padding: 0px 15px;    display: flex;    justify-content: space-between;  }  /*Brand Logo + text*/.brand-logo{    line-height: 100px;    float: left;    text-transform: uppercase;  }  .brand-text {    font-size: 2em;    line-height: 80px;    font-family: "Montserrat", cursive;    font-weight: 500;    text-decoration-line: none;    color: #fff;  }  /*Hamburger Styles*/  .main .bars {    position: fixed;    height: 30px;    width: 50px;    top: 5%;    right: 5%;    display: flex;    flex-direction: column;    align-items: center;    z-index: 9999999999;    cursor: pointer;  }  .main .bars::before {    position: absolute;    content: "";    height: 2px;    width: 90%;    background: #fff;    transition: 0.3s linear;  }  .main .bars.active::before {    transform: rotate(45deg);    width: 50%;    top: 5%;    background: #000;  }  .main .bars::after {    position: absolute;    content: "";    height: 2px;    width: 90%;    background: #fff;    top: 35%;    transition: 0.3s linear;  }  .main .bars.active::after {    transform: rotate(-45deg);    width: 50%;    top: 5%;    background: #000;  }  /*Nav Menu*/  .menu {    position: fixed;    top: 0;    left: 0;    width: 100%;    height: 100%;    z-index: 999999999;    overflow: hidden;    display: none;  }  .menu .navBefore {    position: absolute;    margin-left: 100%;    width: 100%;    height: 100%;    background: #017bf5;  }  .menu .nav {    position: relative;    margin-left: 100%;    width: 100%;    height: 100%;    background: #fff;    z-index: 1;    display: flex;    align-items: center;    justify-content: center;  }  .menu .nav ul {    opacity: 0;  }  .menu .nav ul li {    list-style: none;  }  .menu .nav ul li a {    position: relative;    font-size: 4.5rem;    text-decoration: none;    text-align: center;    color: #666;  }  .menu .nav ul li a:hover,  .menu .nav ul li.active a {    color: #000;    text-decoration-line: line-through;  }  /* Cursor Styles*/.cursor{    position: absolute;    width: 40px;    height: 40px;    margin-left: -20px;    margin-top: -20px;    border-radius: 50%;    border: 3px solid whitesmoke;    transform: translate(-50%, -50%);    transition: transform .2s ease;    pointer-events: none;    backdrop-filter: grayscale(1);    z-index: 1000;  }  .cursorDot{    position: absolute;    width: 4px;    height: 4px;    margin-left: -20px;    margin-top: -20px;    border-radius: 50%;    background-color: whitesmoke;    transform: translate(-50%, -50%);    transition: 0.1s;    pointer-events: none;    z-index: 1000;  }  .grow, .grow-small{    transform: scale(4);    background: white;    mix-blend-mode: difference;     border: none;  }  .grow-small{    transform: scale(2);  }  /*Drag Slider*/  #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: #fff;    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;}

Part 3: JavaScript

Now we can implement our JavaScript logic to our WebGL setup like so.

console.clear();let cursor = document.querySelector('.cursor');let cursorDot = document.querySelector(".cursorDot");let cursorScale = document.querySelectorAll('.cursor-scale'); let mouseX = 0;let mouseY = 0;gsap.to({}, 0.016, {  repeat: -1,  onRepeat: function(){    gsap.set(cursor, {      css: {        left: mouseX,        top: mouseY,      }    });     gsap.set(cursorDot, {      css: {        left: mouseX,        top: mouseY      }    });  }});window.addEventListener("mousemove", (e) => {  mouseX = e.clientX;  mouseY = e.clientY;});cursorScale.forEach((link) => {  link.addEventListener("mousemove", () => {    cursor.classList.add("grow");    if (link.classList.contains("small")) {      cursor.classList.remove("grow");      cursor.classList.add("grow-small");    }  });  link.addEventListener("mouseleave", () => {    cursor.classList.remove("grow");    cursor.classList.remove("grow-small");  });});window.onload = function () {  const bars = document.querySelector(".bars");  const menu = document.querySelector(".menu");  bars.addEventListener("click", function (e) {    this.classList.toggle("active");    if (this.classList.contains("active")) {      gsap.to(".menu", {        duration: 0.1,        display: "flex",        ease: "expo.in"      });      gsap.to(".navBefore", {        duration: 0.5,        marginLeft: "0",        ease: "expo.in"      });      gsap.to(".nav", {        duration: 0.8,        marginLeft: "0",        delay: 0.3,        ease: "expo.in"      });      gsap.to(".navigation", {        duration: 1,        opacity: "1",        delay: 0.8,        ease: "expo.in"      });    } else {      gsap.to(".navigation", {        duration: 0.2,        opacity: "0",        ease: "expo.in"      });      gsap.to(".nav", {        duration: 1,        marginLeft: "100%",        delay: 0.3,        ease: "expo.in"      });      gsap.to(".navBefore", {        duration: 1,        marginLeft: "100%",        delay: 0.5,        ease: "expo.in"      });      gsap.to(".menu", {        duration: 1,        display: "none",        delay: 1,        ease: "expo.in"      });    }  });};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 before our slider has been resized    onBeforeResize() {    }    // 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) {        this.onBeforeResize();        // 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 = [];        // we will keep track of the previous translation values on resize        this.previousTranslation = {            x: 0,            y: 0,        };        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) {            // we will add our displacement map texture            // first we load a new image            var image = new Image();            image.src = "https://images.unsplash.com/photo-1545569341-9eb8b30979d9?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NHx8amFwYW58ZW58MHx8MHx8&auto=format&fit=crop&w=600&q=60";            // then we set its data-sampler attribute to use in fragment shader            image.setAttribute("data-sampler", "displacementTexture");            // finally we load it into our shader pass via the loadImage method            this.shaderPass.loadImage(image);            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() {        // get our slider translation and take our previous translation into account        var planeTranslation = {            x: this.direction === 0 ? this.translation - this.previousTranslation.x : 0,            y: this.direction === 1 ? this.translation - this.previousTranslation.y : 0,        };        // keep our WebGL planes position in sync with their HTML elements        for(var i = 0; i < this.planes.length; i++) {            // in the previous CodePen we were using updatePosition the method which handles positioning automatically            // however this method internally calls getBoundingClientRect() which causes a reflow and therefore impacts performance            // so we will position our planes manually with setRelativePosition instead, which does not trigger a layout repaint call          this.planes[i].setRelativePosition(planeTranslation.x, planeTranslation.y);        }        // 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() {        // we need to update our previous translation value        this.previousTranslation = {            x: this.direction === 0 ? this.translation : 0,            y: this.direction === 1 ? this.translation : 0,        };        // reset our slides relative positions        // because during the resize their positions has already been updated internally        for(var i = 0; i < this.planes.length; i++) {            this.planes[i].setRelativePosition(0, 0);        }        // 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 optionsvar options = {    easing: 0.1,    duration: 500,    dragSpeed: 1.75,}// let's go!var slider = new WebGLSlider(options);

Recap

If you followed along then you should have completed the project and finished off your WebGL project.

Now if you made it this far, then I am linking the code to my GitHub for you to fork or clone and then the job's done.

License:

This project is under the MIT License (MIT). See the LICENSE for more information.

Contributions

Contributions are always welcome...

Fork the repository
Improve current program by
improving functionality
adding a new feature
bug fixes
Push your work and Create a Pull Request

Useful Resources

https://cdnjs.com/
https://www.curtainsjs.com/build/curtains.min.js
https://cdnjs.com/libraries/gsap
https://cdnjs.cloudflare.com/ajax/libs/animejs/2.2.0/anime.min.js


Original Link: https://dev.to/hr21don/project-moon-gsap-cursor-animation-navigation-menu-webgl-slider-57dh

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To