<section>
    <div class="canvas"></div>
</section>
html, body {
    padding: 0;
    margin: 0;
}

video, canvas, iframe {
    display: block;
}

.canvas {
    width: 100vw;
    height: 100vh;
    canvas {
        display: block;
        width: 100%;
        height: 100%;
    }
}
View Compiled
const canvas = document.querySelector(".canvas");

init(canvas);

function init(ele) {
  if (ele != null) {
    ele.camera = createCamera(ele);
    ele.scene = createScene(ele);
    ele.renderer = createRenderer(ele);

    // ====================================
    // add objects ========================
    // ====================================

    const objects = {
      plane: createPlane(),
      "cube 1": createCube({ x: -0.95, y: 0.05, z: -0.95 }, 45),
      "cube 2": createCube({ x: 0.95, y: 0.05, z: -0.95 }, -45),
      "cube 3": createCube({ x: -0.95, y: 0.05, z: 0.95 }, 135),
      "cube 4": createCube({ x: 0.95, y: 0.05, z: 0.95 }, -135)
    };

    for (const prop in objects) {
      objects[prop].name = prop;
      ele.scene.add(objects[prop]);
    }

    function createCube(position, yRotation) {
      const size = 0.1;
      const geometry = new THREE.BoxGeometry(size, size, size);
      const material = new THREE.MeshBasicMaterial(0xff00ce);
      const cube = new THREE.Mesh(geometry, material);

      cube.position.x = position.x;
      cube.position.y = position.y;
      cube.position.z = position.z;

      cube.clickable = true;

      cube.animation = gsap.timeline({
        paused: true,
        onUpdate: function () {
          ele.camera.updateProjectionMatrix();
        }
      });

      cube.animation.to(ele.camera.position, 2, { x: 0, y: 0.12, z: 0 });
      cube.animation.to(
        ele.camera.rotation,
        2,
        {
          x: THREE.MathUtils.degToRad(0),
          y: THREE.MathUtils.degToRad(yRotation),
          z: THREE.MathUtils.degToRad(0)
        },
        0
      );
      cube.animation.to(ele.camera, 2, { zoom: 6 }, 0);

      return cube;
    }

    // ====================================
    // controls ===========================
    // ====================================

    ele.raycaster = new THREE.Raycaster();
    ele.mouse = new THREE.Vector2();

    mouseMove();

    function mouseMove() {
      window.addEventListener("mousemove", function (e) {
        ele.mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
        ele.mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;

        ele.raycaster.setFromCamera(ele.mouse, ele.camera);

        ele.intersects = ele.raycaster.intersectObjects(
          ele.scene.children,
          true
        );

        // changes onject is 'clickable'
        container.style.cursor = isClickable(ele.intersects) ? "pointer" : "";
      });
    }

    cubeClick();

    function cubeClick() {
      window.addEventListener("click", function (e) {
        if (isClickable(ele.intersects)) {
          ele.intersects[0].object.animation.play();
        }
      });
    }

    function isClickable(arr) {
      const clickable =
        arr.length && "clickable" in arr[0].object && arr[0].object.clickable;

      return clickable;
    }

    // ====================================
    // boilerplate stuff ==================
    // ====================================

    render();
    resize();

    function render(time) {
      requestAnimationFrame(render);
      ele.renderer.render(ele.scene, ele.camera);
    }

    function resize() {
      window.addEventListener("resize", function () {
        ele.renderer.setSize(window.innerWidth, window.innerHeight);
        ele.camera.aspect = window.innerWidth / window.innerHeight;
        ele.camera.updateProjectionMatrix();
      });
    }
  }
}

function createPlane() {
  const geometry = new THREE.PlaneGeometry(2, 2);
  const material = new THREE.MeshBasicMaterial({
    color: "orange",
    side: THREE.DoubleSide
  });
  const plane = new THREE.Mesh(geometry, material);

  plane.position.set(0, 0, 0);
  plane.rotation.x = THREE.MathUtils.degToRad(90);

  return plane;
}

// ====================================
// boilerplate stuff ==================
// ====================================

function createCamera(node) {
  const fov = 100; // Camera frustum vertical field of view.
  const aspect = window.innerWidth / window.innerHeight; // Camera frustum aspect ratio.
  const near = 0.01; // Camera frustum near plane.
  const far = 5; // Camera frustum far plane.

  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);

  camera.position.z = 2;
  camera.position.y = THREE.MathUtils.degToRad(60);

  camera.lookAt(0, 0, 0);

  return camera;
}

function createScene(node) {
  const scene = new THREE.Scene();

  return scene;
}

function createRenderer(node) {
  const renderer = new THREE.WebGLRenderer({ antialias: true });

  renderer.setSize(window.innerWidth, window.innerHeight);

  node.appendChild(renderer.domElement);

  return renderer;
}
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js
  2. https://unpkg.co/gsap@3/dist/gsap.min.js