<body class="text-center">

  <div id="page">
    <div class="cover-container d-flex h-100 p-3 mx-auto flex-column">
      <header class="masthead mb-auto">
        <div class="inner">
          <h3 class="masthead-brand">Background #1</h3>
          <nav class="nav nav-masthead justify-content-center">
            <a class="nav-link" href="https://codepen.io/soju22/" target="_blank">Codepen Profile</a>
            <a class="nav-link" href="https://codepen.io/collection/AGZywR" target="_blank">ThreeJS Collection</a>
          </nav>
        </div>
      </header>

      <main role="main" class="inner cover">
        <h2>Interactive Background</h2>
        <p class="lead">This simple interactive background is made with <a href="https://threejs.org"
            target="_blank">ThreeJS</a> and a <a
            href="https://threejs.org/docs/#api/en/geometries/PlaneBufferGeometry"
            target="_blank">PlaneBufferGeometry</a> animated with Simplex noise.</p>

        <form style="width: 80%; margin: 0 auto;">
          <div class="form-row">
            <div class="form-group col-sm-6">
              <label for="noiseInput" class="form-label">Noise Coef</label>
              <input type="range" min="1" max="100" class="custom-range" id="noiseInput">
            </div>
            <div class="form-group col-sm-6">
              <label for="heightInput" class="form-label">Height Coef</label>
              <input type="range" min="1" max="100" class="custom-range" id="heightInput">
            </div>
          </div>
          <div class="form-group col-md-12">
            <a href="#" id="trigger" class="btn btn-sm btn-secondary">Random Colors</a>
          </div>
        </form>
      </main>

      <footer class="mastfoot mt-auto">
      </footer>
    </div>
  </div>

  <canvas id="background"></canvas>

</body>
html,
body {
  height: 100%;
}

body {
  display: flex;
  justify-content: center;
  color: #212529;
  background-color: #fff;
  font-family: 'Montserrat', sans-serif;
  /* box-shadow: inset 0 0 2rem rgba(0, 0, 0, .5); */
}

.cover-container {
  max-width: 42em;
}

.masthead {
  margin-bottom: 2rem;
}

.masthead-brand {
  margin-bottom: 0;
}

.nav-masthead .nav-link {
  padding: .25rem 0;
  font-weight: 700;
  color: #212529;
  background-color: transparent;
  border-bottom: .25rem solid transparent;
}

.nav-masthead .nav-link:hover,
.nav-masthead .nav-link:focus {
  border-bottom-color: rgba(0, 0, 0, .25);
}

.nav-masthead .nav-link+.nav-link {
  margin-left: 1rem;
}

@media (min-width: 48em) {
  .masthead-brand {
    float: left;
  }

  .nav-masthead {
    float: right;
  }
}

.cover {
  padding: 0 1.5rem;
}

#background {
  position: fixed;
  z-index: -1;
}

App({ el: 'background' });

function App(conf) {
  conf = {
    fov: 75,
    cameraZ: 75,
    xyCoef: 50,
    zCoef: 10,
    lightIntensity: 0.9,
    ambientColor: 0x000000,
    light1Color: 0x0E09DC,
    light2Color: 0x1CD1E1,
    light3Color: 0x18C02C,
    light4Color: 0xee3bcf,
    ...conf
  };

  let renderer, scene, camera, cameraCtrl;
  let width, height, cx, cy, wWidth, wHeight;
  const TMath = THREE.Math;

  let plane;
  const simplex = new SimplexNoise();

  const mouse = new THREE.Vector2();
  const mousePlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
  const mousePosition = new THREE.Vector3();
  const raycaster = new THREE.Raycaster();

  const noiseInput = document.getElementById('noiseInput');
  const heightInput = document.getElementById('heightInput');

  init();

  function init() {
    renderer = new THREE.WebGLRenderer({ canvas: document.getElementById(conf.el), antialias: true, alpha: true });
    camera = new THREE.PerspectiveCamera(conf.fov);
    camera.position.z = conf.cameraZ;

    updateSize();
    window.addEventListener('resize', updateSize, false);

    document.addEventListener('mousemove', e => {
      const v = new THREE.Vector3();
      camera.getWorldDirection(v);
      v.normalize();
      mousePlane.normal = v;
      mouse.x = (e.clientX / width) * 2 - 1;
      mouse.y = - (e.clientY / height) * 2 + 1;
      raycaster.setFromCamera(mouse, camera);
      raycaster.ray.intersectPlane(mousePlane, mousePosition);
    });

    initScene();
    initGui();
    animate();
  }

  function initGui() {
    noiseInput.value = 101 - conf.xyCoef;
    heightInput.value = conf.zCoef * 100 / 25;

    noiseInput.addEventListener('input', e => {
      conf.xyCoef = 101 - noiseInput.value;
    });
    heightInput.addEventListener('input', e => {
      conf.zCoef = heightInput.value * 25 / 100;
    });

    document.getElementById('trigger').addEventListener('click', e => {
      updateLightsColors();
    });
  }

  function initScene() {
    scene = new THREE.Scene();
    initLights();

    let mat = new THREE.MeshLambertMaterial({ color: 0xffffff, side: THREE.DoubleSide });
    // let mat = new THREE.MeshPhongMaterial({ color: 0xffffff });
    // let mat = new THREE.MeshStandardMaterial({ color: 0x808080, roughness: 0.5, metalness: 0.8 });
    let geo = new THREE.PlaneBufferGeometry(wWidth, wHeight, wWidth / 2, wHeight / 2);
    plane = new THREE.Mesh(geo, mat);
    scene.add(plane);

    plane.rotation.x = -Math.PI / 2 - 0.2;
    plane.position.y = -25;
    camera.position.z = 60;
  }

  function initLights() {
    const r = 30;
    const y = 10;
    const lightDistance = 500;

    // light = new THREE.AmbientLight(conf.ambientColor);
    // scene.add(light);

    light1 = new THREE.PointLight(conf.light1Color, conf.lightIntensity, lightDistance);
    light1.position.set(0, y, r);
    scene.add(light1);
    light2 = new THREE.PointLight(conf.light2Color, conf.lightIntensity, lightDistance);
    light2.position.set(0, -y, -r);
    scene.add(light2);
    light3 = new THREE.PointLight(conf.light3Color, conf.lightIntensity, lightDistance);
    light3.position.set(r, y, 0);
    scene.add(light3);
    light4 = new THREE.PointLight(conf.light4Color, conf.lightIntensity, lightDistance);
    light4.position.set(-r, y, 0);
    scene.add(light4);
  }

  function animate() {
    requestAnimationFrame(animate);

    animatePlane();
    animateLights();

    renderer.render(scene, camera);
  };

  function animatePlane() {
    gArray = plane.geometry.attributes.position.array;
    const time = Date.now() * 0.0002;
    for (let i = 0; i < gArray.length; i += 3) {
      gArray[i + 2] = simplex.noise4D(gArray[i] / conf.xyCoef, gArray[i + 1] / conf.xyCoef, time, mouse.x + mouse.y) * conf.zCoef;
    }
    plane.geometry.attributes.position.needsUpdate = true;
    // plane.geometry.computeBoundingSphere();
  }

  function animateLights() {
    const time = Date.now() * 0.001;
    const d = 50;
    light1.position.x = Math.sin(time * 0.1) * d;
    light1.position.z = Math.cos(time * 0.2) * d;
    light2.position.x = Math.cos(time * 0.3) * d;
    light2.position.z = Math.sin(time * 0.4) * d;
    light3.position.x = Math.sin(time * 0.5) * d;
    light3.position.z = Math.sin(time * 0.6) * d;
    light4.position.x = Math.sin(time * 0.7) * d;
    light4.position.z = Math.cos(time * 0.8) * d;
  }

  function updateLightsColors() {
    conf.light1Color = chroma.random().hex();
    conf.light2Color = chroma.random().hex();
    conf.light3Color = chroma.random().hex();
    conf.light4Color = chroma.random().hex();
    light1.color = new THREE.Color(conf.light1Color);
    light2.color = new THREE.Color(conf.light2Color);
    light3.color = new THREE.Color(conf.light3Color);
    light4.color = new THREE.Color(conf.light4Color);
    // console.log(conf);
  }

  function updateSize() {
    width = window.innerWidth; cx = width / 2;
    height = window.innerHeight; cy = height / 2;
    if (renderer && camera) {
      renderer.setSize(width, height);
      camera.aspect = width / height;
      camera.updateProjectionMatrix();
      const wsize = getRendererSize();
      wWidth = wsize[0];
      wHeight = wsize[1];
    }
  }

  function getRendererSize() {
    const cam = new THREE.PerspectiveCamera(camera.fov, camera.aspect);
    const vFOV = cam.fov * Math.PI / 180;
    const height = 2 * Math.tan(vFOV / 2) * Math.abs(conf.cameraZ);
    const width = height * cam.aspect;
    return [width, height];
  }
}
View Compiled

External CSS

  1. https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css
  2. https://fonts.googleapis.com/css?family=Montserrat

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/three.js/100/three.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.0.3/chroma.min.js