<header class="header">
    <nav class="navbar">
      <span>darthvaders</span>
      <span>menu</span>
    </nav>
    <h1 class="header__title">loading 0%</h1>
    <div class="headerActions">
      <ul class="headerActions__list">
        <li>ki.</li>
        <li>ara.</li>
        <li>sh.</li>
      </ul>
      <a target="_blank" href="https://twitter.com/kiarash_zar" class="headerActions__action">join the club</a>
      <span class="headerActions__control">sound off</span>
    </div>
  </header>
*, *::after, *::before {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  font-size: 62.5%;
}

html, body {
  height: 100%;
  touch-action: none;
}

body {
  position: relative;
  font-family: 'Montserrat', sans-serif;
  background-image: radial-gradient(rgba(0, 0, 0, 0.8) 0%, #000 35%);
  color: rgb(241, 241, 241);
  text-transform: uppercase;
  user-select: none;
}


.header {
  width: 100%;
  height: 100%;
  position: absolute;
  padding: 5rem 4rem;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.header__title {
  font-size: 8vw;
  align-self: center;
  width: 100%;
  text-align: center;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: -1;
}

.navbar {
  display: flex;
  justify-content: space-between;
  font-size: 1.5rem;
  flex: 1;
}

.headerActions {
  display: flex;
  align-items: flex-end;
  flex: 1;
}

.headerActions__list {
  list-style-type: none;
  display: flex;
  flex: 1;
  font-size: 1.2rem;
}

.headerActions__list > li:not(:last-child) {
  margin-right: 1rem;
}

.headerActions__action {
  background: #fff;
  outline: none;
  border: none;
  color: #000;
  font-size: 1.5rem;
  padding: 2rem 2.5rem;
  text-transform: uppercase;
  font-family: inherit;
  transition: 0.2s;
  position: relative;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  text-decoration: none;
}

.headerActions__action:hover {
  box-shadow: 0 1rem 3rem rgba(255, 255, 255, 0.3);
}

.headerActions__control {
  display: flex;
  flex: 1;
  justify-content: flex-end;
  font-size: 1.2rem;
}

@media (max-width: 1200px) {
  html {
    font-size: 58%;
  }
}

@media (max-width: 768px) {
  html {
    font-size: 55%;
  }
}

@media (max-width: 768px) and (orientation: portrait) {
  .header__title {
    font-size: 11vw;
  }
}

@media (max-width: 356px) {
  html {
    font-size: 50%;
  }
}

@media (min-aspect-ratio: 3/1) {
  html {
    font-size: 35%;
  }
}
const headerTitle = document.querySelector('.header__title');
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });

renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.z = 8;

const mainLight = new THREE.AmbientLight (0x404040, 10);
scene.add(mainLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 100);
directionalLight.position.set(0, 5, 0);
directionalLight.castShadow = true;
scene.add(directionalLight);

const pointLight = new THREE.PointLight(0xc4c4c4, 20);
pointLight.position.set(0, 250, 500);
scene.add(pointLight);

const loadingManager = new THREE.LoadingManager();
loadingManager.onProgress = (_, loaded, total) => {
  const loadingPercentage = Math.floor(loaded / total) * 100;
  headerTitle.textContent = loadingPercentage === 100 ? 'darthvaders' : `loading ${loadingPercentage}%`;
};

const loader = new THREE.GLTFLoader(loadingManager);
const dracoLoader = new THREE.DRACOLoader();
dracoLoader.setDecoderPath('https://darthvader.surge.sh/draco/');
loader.setDRACOLoader(dracoLoader);

const position = new THREE.Vector3();

const initializeAnimation = model => {
  let isInitiallyAnimating = true;

  const render = () => {
    renderer.render(scene, camera);
  };

  render();

  gsap.to(model.rotation, 1, {
    z: 0,
    onComplete: () => {
      isInitiallyAnimating = false;
    },
    onUpdate: render,
  }).delay(0.2);

  const limitTo = (value, { maximum, minimum }) =>  {
    if (value > maximum) return maximum;
    else if (value < minimum) return minimum;
    return value;
  };

  const handlePointerMove = ({ clientX, clientY }) => {
    if (isInitiallyAnimating) return;
    const centerPoint = {
      x: window.innerWidth / 2,
      y: window.innerHeight / 2,
    };
    const z = (clientX - centerPoint.x) / 1000;
    const x = -1 * Math.PI / 2 + (clientY - centerPoint.y) / 1000;
    gsap.to(model.rotation, 0.4, {
      x,
      z: limitTo(z, { maximum: Math.PI / 2.2, minimum: -Math.PI / 2.2 }),
      onUpdate: render,
    });
  };

  window.addEventListener('pointermove', handlePointerMove);
};

const handleLoad = gltf => {
  const model = gltf.scene.children[0];
  model.rotation.z = Math.PI;
  scene.add(model);
  initializeAnimation(model);
};

const handleWindowResize = () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.render(scene, camera);
};

loader.load('https://darthvader.surge.sh/model.gltf', handleLoad);
window.addEventListener('resize', handleWindowResize);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/three.js/109/three.min.js
  2. https://darthvader.surge.sh/GLTFLoader.js
  3. https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/js/loaders/DRACOLoader.js
  4. https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/gsap-latest-beta.min.js