<nav class="nav nav--left js-nav">
  <ul>
    <li>
      <a href="#"><span>Mens</span></a>
    </li>
    <li>
      <a href="#"><span>Womens</span></a>
    </li>
    <li>
      <a href="#" class="is-active"><span>Collections</span></a>
    </li>
  </ul>
</nav>

<figure class="logo">
  <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/logo_copy_copy.svg">
</figure>

<nav class="nav nav--right">
  <ul>
    <li>
      <a href="https://twitter.com/Jesper_Landberg?lang=en" target="_blank"><span>Say hi</span></a>
    </li>
    <li>
      <a href="#">
        <span>Cart</span>
        <div class="cart-total">0</div>
      </a>
    </li>
  </ul>
</nav>

<div class="slider js-slider">
  <div class="slider__inner js-slider__inner"></div>
  
  <div class="slide js-slide">
    <div class="slide__content">
      <figure class="slide__img js-slide__img">
        <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/photo1.jpg">
      </figure>
       <figure class="slide__img js-slide__img">
        <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/photo2.jpg">
      </figure>
    </div>
    
    <div class="slider__text js-slider__text">
      <div class="slider__text-line js-slider__text-line"><div>Black is</div></div>
      <div class="slider__text-line js-slider__text-line"><div>timeless. Black is</div></div>
      <div class="slider__text-line js-slider__text-line"><div>the colour of</div></div>
      <div class="slider__text-line js-slider__text-line"><div>Eternity.</div></div>
    </div>
    
  </div>
  
  <div class="slide js-slide">
    <div class="slide__content">
      <figure class="slide__img js-slide__img">
        <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/photo3.jpg">
      </figure>
       <figure class="slide__img js-slide__img">
        <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/photo4.jpg">
      </figure>
    </div>
  </div>
  
  <div class="slide js-slide">
    <div class="slide__content">
      <figure class="slide__img js-slide__img">
        <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/photo5.jpg">
      </figure>
       <figure class="slide__img js-slide__img">
        <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/photo6.jpg">
      </figure>
    </div>
  </div>
  
  <nav class="slider__nav js-slider__nav">
    <div class="slider-bullet js-slider-bullet">
      <span class="slider-bullet__text js-slider-bullet__text">01</span>
      <span class="slider-bullet__line js-slider-bullet__line"></span>
    </div>
     <div class="slider-bullet js-slider-bullet">
      <span class="slider-bullet__text js-slider-bullet__text">02</span>
      <span class="slider-bullet__line js-slider-bullet__line"></span>
    </div>
     <div class="slider-bullet js-slider-bullet">
      <span class="slider-bullet__text js-slider-bullet__text">03</span>
      <span class="slider-bullet__line js-slider-bullet__line"></span>
    </div>
  </nav>
  
  <div class="scroll js-scroll">Scroll</div>
  
</div>

<div class="vert-text">
  <span>
    Wings+Horns<br>
    X Kyoto Black
  </span>
</div>
html{
  font-size: 16px;
}

html, body{
  height: 100%;
}

body{
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #111;
  font-family: 'helvetica neue', helvetica, sans-serif;
  overflow: hidden;
}

a{
  color: #fff;
  text-decoration: none;
}

.scroll{
  position: absolute;
  bottom: 2rem;
  left: 50%;
  transform: translateX(-50%);
  color: rgba(#fff, 0.5);
  font-family: 'font-2';
  font-size: calc(0.5rem + 0.35vw);
  z-index: 10;
}

.logo{
  position: absolute;
  top: 2rem;
  left: 50%;
  transform: translateX(-50%);
  padding: 0;
  margin: 0;
  z-index: 10;
  
  img{
    display: block;
    height: 1rem;
    width: auto;
  }
}

ul, li{
  list-style: none;
  padding: 0;
  margin: 0;
}

.nav{
  position: absolute;
  top: 2rem;
  z-index: 10;
  
  &--left{
    left: 1rem;
  }
  
  &--right{
    right: 1rem;
  }
  
  ul{
    display: flex;
    align-items: center;
    height: 1rem;
  }
  
  li{
    display: block;
    margin: 0 1rem;
    padding: 0;
  }
  
  a{
    position: relative;
    display: flex;
    align-items: center;
    font-size: calc(0.5rem + 0.35vw);
    font-family: 'helvetica neue', helvetica, sans-serif;
    
    span{
      position: relative;
      
      &:before{
        content: '';
        position: absolute;
        left: 0;
        bottom: -0.35rem;
        width: 100%;
        height: 1px;
        background-color: rgba(#fff, 0.25);
        transition: transform .75s ease;
        transform-origin: right;
        transform: scaleX(0);
      }
    }
    
    &:hover,
    &.is-active {

      span{
        
        &:before{
          transform: scaleX(1);
          transform-origin: left;
        }
      }
    }
  }
}

.vert-text{
  position: absolute;
  bottom: 2rem;
  right: 2rem;
  width: 15rem;
  display: flex;
  align-items: center;
  z-index: 10;
  
  span{
    display: block;
    color: #fff;
    text-transform: uppercase;
    line-height: 1.1;
    transform: rotate(-90deg) translateY(15rem);
    transform-origin: bottom left;
    font-size: 1.35rem;
  }
}

.cart-total{
  display: block;
  height: 2rem;
  width: 2rem;
  background-color: rgba(#fff, 0.25);
  border-radius: 50%;
  text-align: center;
  line-height: 2rem;
  margin-left: 1rem;
}

.slider{
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  
  &__text{
    position: absolute;
    bottom: calc(2rem + 3vw);
    left: calc(2rem + 3vw);
    z-index: 10;
    font-size: calc(1rem + 4vw);
    text-transform: uppercase;
    transform-origin: top;
    line-height: 1.075;
    color: #fff;
    font-weight: 500;
    
    &-line{
      overflow: hidden;
    }
  }
  
  &__inner{
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
  }
  
  &__nav{
    position: absolute;
    top: 50%;
    right: 0;
    transform: translateY(-50%);
    z-index: 10;
  }
  
  &-bullet{
    display: flex;
    align-items: center;
    padding: 1rem 0;
    
    &__text{
      color: #fff;
      font-size: 0.65rem;
      margin-right: 1rem;
    }
    
    &__line{
      background-color: #fff;
      height: 1px;
      width: 1rem;
    }
  }
  
  canvas{
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
  }
}

.slide{
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  overflow: hidden;
  
  &__content{  
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
  }
  
  &__img{
    position: relative;
    width: 25vw;
    height: 70vh;
    padding: 0;
    margin: 0;
    min-width: 12.5rem;
    transform-origin: top;
    
    &:first-child{
      top: -1.5rem;
    }
    
    &:last-child{
      bottom: -1.5rem;
    }
    
    img{
      display: block;
      position: absolute;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
  }
}
View Compiled
class Slider {
  constructor() {
    this.bindAll()
    
    this.vert = `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
    `

    this.frag = `
    varying vec2 vUv;

    uniform sampler2D texture1;
    uniform sampler2D texture2;
    uniform sampler2D disp;

    uniform float dispPower;
    uniform float intensity;

    uniform vec2 size;
    uniform vec2 res;

    vec2 backgroundCoverUv( vec2 screenSize, vec2 imageSize, vec2 uv ) {
      float screenRatio = screenSize.x / screenSize.y;
      float imageRatio = imageSize.x / imageSize.y;
      vec2 newSize = screenRatio < imageRatio 
          ? vec2(imageSize.x * (screenSize.y / imageSize.y), screenSize.y)
          : vec2(screenSize.x, imageSize.y * (screenSize.x / imageSize.x));
      vec2 newOffset = (screenRatio < imageRatio 
          ? vec2((newSize.x - screenSize.x) / 2.0, 0.0) 
          : vec2(0.0, (newSize.y - screenSize.y) / 2.0)) / newSize;
      return uv * screenSize / newSize + newOffset;
    }

    void main() {
      vec2 uv = vUv;
      
      vec4 disp = texture2D(disp, uv);
      vec2 dispVec = vec2(disp.x, disp.y);
      
      vec2 distPos1 = uv + (dispVec * intensity * dispPower);
      vec2 distPos2 = uv + (dispVec * -(intensity * (1.0 - dispPower)));
      
      vec4 _texture1 = texture2D(texture1, distPos1);
      vec4 _texture2 = texture2D(texture2, distPos2);
      
      gl_FragColor = mix(_texture1, _texture2, dispPower);
    }
    `
    
    this.el = document.querySelector('.js-slider')
    this.inner = this.el.querySelector('.js-slider__inner')
    this.slides = [...this.el.querySelectorAll('.js-slide')]
    this.bullets = [...this.el.querySelectorAll('.js-slider-bullet')]
    
    this.renderer = null
    this.scene = null
    this.clock = null
    this.camera = null
    
    this.images = [
      'https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/bg1.jpg',
      'https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/bg2.jpg',
      'https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/bg3.jpg'
    ]
    
    this.data = {
      current: 0,
      next: 1,
      total: this.images.length - 1,
      delta: 0
    }
    
    this.state = {
      animating: false,
      text: false,
      initial: true
    }
    
    this.textures = null
    
    this.init()
  }
  
  bindAll() {
    ['render', 'nextSlide']
    .forEach(fn => this[fn] = this[fn].bind(this))
  }
  
  setStyles() {
    this.slides.forEach((slide, index) => {
      if (index === 0) return
      
      TweenMax.set(slide, { autoAlpha: 0 })
    })
    
    this.bullets.forEach((bullet, index) => {
      if (index === 0) return
      
      const txt = bullet.querySelector('.js-slider-bullet__text')
      const line = bullet.querySelector('.js-slider-bullet__line')
      
      TweenMax.set(txt, {
        alpha: 0.25
      })
      TweenMax.set(line, {
        scaleX: 0,
        transformOrigin: 'left'
      })
    })
  }
  
  cameraSetup() {
    this.camera = new THREE.OrthographicCamera(
      this.el.offsetWidth / -2,
      this.el.offsetWidth / 2,
      this.el.offsetHeight / 2,
      this.el.offsetHeight / -2,
      1,
      1000
    )

    this.camera.lookAt(this.scene.position)
    this.camera.position.z = 1
  }

  setup() {
    this.scene = new THREE.Scene()
    this.clock = new THREE.Clock(true)
    
    this.renderer = new THREE.WebGLRenderer({ alpha: true })
    this.renderer.setPixelRatio(window.devicePixelRatio)
    this.renderer.setSize(this.el.offsetWidth, this.el.offsetHeight)
    
    this.inner.appendChild(this.renderer.domElement)
  }
  
  loadTextures() {
    const loader = new THREE.TextureLoader()
    loader.crossOrigin = ''
    
    this.textures = []
    this.images.forEach((image, index) => {
      const texture = loader.load(image + '?v=' + Date.now(), this.render)
      
      texture.minFilter = THREE.LinearFilter
      texture.generateMipmaps = false
      
      if (index === 0 && this.mat) {
        this.mat.uniforms.size.value = [
          texture.image.naturalWidth,
          texture.image.naturalHeight
        ]
      }

      this.textures.push(texture)
    })
    
    this.disp = loader.load('https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/rock-_disp.png', this.render)
    this.disp.magFilter = this.disp.minFilter = THREE.LinearFilter
    this.disp.wrapS = this.disp.wrapT = THREE.RepeatWrapping
  }
  
  createMesh() {
    this.mat = new THREE.ShaderMaterial( {
      uniforms: {
        dispPower: { type: 'f', value: 0.0 },
        intensity: { type: 'f', value: 0.5 },
        res: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
        size: { value: new THREE.Vector2(1, 1) },
        texture1: { type: 't', value: this.textures[0] },
        texture2: { type: 't', value: this.textures[1] },
        disp: { type: 't', value: this.disp }
      },
      transparent: true,
      vertexShader: this.vert,
      fragmentShader: this.frag
    })

    const geometry = new THREE.PlaneBufferGeometry(
      this.el.offsetWidth, 
      this.el.offsetHeight, 
      1
    )
    
    const mesh = new THREE.Mesh(geometry, this.mat)

    this.scene.add(mesh)    
  }
  
  transitionNext() {
    TweenMax.to(this.mat.uniforms.dispPower, 2.5, {
      value: 1,
      ease: Expo.easeInOut,
      onUpdate: this.render,
      onComplete: () => {
        this.mat.uniforms.dispPower.value = 0.0
        this.changeTexture()
        this.render.bind(this)
        this.state.animating = false
      }
    })
    
    const current = this.slides[this.data.current]
    const next = this.slides[this.data.next]
    
    const currentImages = current.querySelectorAll('.js-slide__img')
    const nextImages = next.querySelectorAll('.js-slide__img')
    
    const currentText = current.querySelectorAll('.js-slider__text-line div')
    const nextText = next.querySelectorAll('.js-slider__text-line div')
    
    const currentBullet = this.bullets[this.data.current]
    const nextBullet = this.bullets[this.data.next]
    
    const currentBulletTxt = currentBullet.querySelectorAll('.js-slider-bullet__text')
    const nextBulletTxt = nextBullet.querySelectorAll('.js-slider-bullet__text')
    
    const currentBulletLine = currentBullet.querySelectorAll('.js-slider-bullet__line')
    const nextBulletLine = nextBullet.querySelectorAll('.js-slider-bullet__line')
    
    const tl = new TimelineMax({ paused: true })
    
    if (this.state.initial) {
      TweenMax.to('.js-scroll', 1.5, {
        yPercent: 100,
        alpha: 0,
        ease: Power4.easeInOut
      })
      
      this.state.initial = false
    }
    
    tl
    .staggerFromTo(currentImages, 1.5, {
      yPercent: 0,
      scale: 1
    }, {
      yPercent: -185,
      scaleY: 1.5,
      ease: Expo.easeInOut
    }, 0.075)
    .to(currentBulletTxt, 1.5, {
      alpha: 0.25,
      ease: Linear.easeNone
    }, 0)
    .set(currentBulletLine, {
      transformOrigin: 'right'
    }, 0)
    .to(currentBulletLine, 1.5, {
      scaleX: 0,
      ease: Expo.easeInOut
    }, 0)
    
    if (currentText) {
      tl
      .fromTo(currentText, 2, {
        yPercent: 0
      }, {
        yPercent: -100,
        ease: Power4.easeInOut
      }, 0)  
    }
    
    tl
    .set(current, {
      autoAlpha: 0
    })
    .set(next, {
      autoAlpha: 1
    }, 1)
    
    if (nextText) {
      tl
      .fromTo(nextText, 2, {
        yPercent: 100
      }, {
        yPercent: 0,
        ease: Power4.easeOut
      }, 1.5)  
    }
    
    tl
    .staggerFromTo(nextImages, 1.5, {
      yPercent: 150,
      scaleY: 1.5
    }, {
      yPercent: 0,
      scaleY: 1,
      ease: Expo.easeInOut
    }, 0.075, 1)
    .to(nextBulletTxt, 1.5, {
      alpha: 1,
      ease: Linear.easeNone
    }, 1)
    .set(nextBulletLine, {
      transformOrigin: 'left'
    }, 1)
    .to(nextBulletLine, 1.5, {
      scaleX: 1,
      ease: Expo.easeInOut
    }, 1)
    
    tl.play()
  }
  
  prevSlide() {
    
  }
  
  nextSlide() {
    if (this.state.animating) return
    
    this.state.animating = true
    
    this.transitionNext()
    
    this.data.current = this.data.current === this.data.total ? 0 : this.data.current + 1
    this.data.next = this.data.current === this.data.total ? 0 : this.data.current + 1
  }
  
  changeTexture() {
    this.mat.uniforms.texture1.value = this.textures[this.data.current]
    this.mat.uniforms.texture2.value = this.textures[this.data.next]
  }
 
  listeners() {
    window.addEventListener('wheel', this.nextSlide, { passive: true })
  }
  
  render() {
    this.renderer.render(this.scene, this.camera)
  }
  
  init() {
    this.setup()
    this.cameraSetup()
    this.loadTextures()
    this.createMesh()
    this.setStyles()
    this.render()
    this.listeners()
  }
}

// Toggle active link
const links = document.querySelectorAll('.js-nav a')

links.forEach(link => {
  link.addEventListener('click', (e) => {
    e.preventDefault()
    links.forEach(other => other.classList.remove('is-active'))
    link.classList.add('is-active')
  })
})

// Init classes
const slider = new Slider()
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.2/TweenMax.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.min.js