JSFiddle - React, Tailwind, and code Playground

by Scott Kaye

HTML

<img src="https://i.imgur.com/GoWC74k.jpg" class="bubbles" />

CSS

html,
body {
  margin: 0;
  padding: 0;
  overflow: hidden;
}

img {
  max-width: 100vw;
  max-height: 100vh;
}

JavaScript

function addBubblesOverlay(element) {
  const rect = element.getBoundingClientRect();
  const scene = new THREE.Scene();
  const camera = new THREE.OrthographicCamera(rect.width / -2, rect.width / 2, rect.height / 2, rect.height / -2, 0, 1000);
  const renderer = new THREE.WebGLRenderer({
    antialias: true,
    alpha: true
  });
  renderer.setSize(rect.width, rect.height);

  const wrapperElement = document.createElement("div");
  element.parentNode.insertBefore(wrapperElement, element);
  wrapperElement.appendChild(renderer.domElement);
  wrapperElement.appendChild(element);

  renderer.domElement.style.position = "absolute";
  renderer.domElement.style.mixBlendMode = "overlay";
  renderer.domElement.style.opacity = 0.5;

  function rand(min, max) {
    return Math.floor(Math.random() * (max - min + 1) + min);
  }

  // Create geometries
  const geometries = new Array(5)
    .fill(0)
    .map(() => new THREE.CircleGeometry(rand(rect.width / 20, rect.width / 10), 32));

  const meshWrappers = new Array(30)
    .fill(0)
    .map(() => {
      const geometry = geometries[(Math.random() * geometries.length) | 0];
      const material = new THREE.MeshBasicMaterial({
        color: 0xffffff,
        transparent: true
      });

      return {
        isAppearing: false,
        vx: 0,
        vy: 0,
        mesh: new THREE.Mesh(geometry, material)
      };
    });

  function initMeshWrapper(wrapper) {
    wrapper.mesh.position.x = (window.innerWidth / -2) + Math.random() * window.innerWidth;
    wrapper.mesh.position.y = (window.innerHeight / -2) + Math.random() * window.innerHeight;
    wrapper.mesh.material.opacity = 0;
    wrapper.vx = (Math.random() - 0.5) / 2;
    wrapper.vy = (Math.random() - 0.5) / 2;
    wrapper.isAppearing = true;
  }

  // Initialize scene
  meshWrappers.forEach(wrapper => {
    initMeshWrapper(wrapper);

    // Special case for the first run
    wrapper.mesh.material.opacity = Math.random();
    wrapper.isAppearing = false;

   ...