<!--
confetti
Copyright (c) 2023 by Wakana Y.K. (https://codepen.io/wakana-k/pen/gOqqWdY)

Luxurious version : https://codepen.io/wakana-k/pen/mdvoQaV
-->
<!-- using three.js -->
<h1>CONFETTI</h1>
<script async src="https://ga.jspm.io/npm:es-module-shims@1.6.3/dist/es-module-shims.js" crossorigin="anonymous"></script>
<script type="importmap">
  {
    "imports": {      
      "three": "https://unpkg.com/three@0.159.0/build/three.module.js",
      "three/addons/": "https://unpkg.com/three@0.159.0/examples/jsm/"
    }
  }
</script>
@import url("https://fonts.googleapis.com/css2?family=Asap&display=swap");
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
html,
body {
  background: radial-gradient(
    circle farthest-corner,
    hsl(320deg, 100%, 15%),
    hsl(320deg, 100%, 2%) 100%
  );
  overscroll-behavior-x: none;
  overscroll-behavior-y: none;
}
body {
  font-family: "Asap", sans-serif;
  position: relative;
  width: 100vw;
  min-height: 100vh;
  text-align: center;
  overflow-x: hidden;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
}
h1 {
  font-size: max(35px, 7vw);
  text-shadow: 1px 1px black;
  mix-blend-mode: difference;
  z-index: 1;
}
canvas {
  -moz-user-select: none;
  -webkit-user-select: none;
  -ms-user-select: none;
  user-select: none;
  position: fixed;
  width: 100vw;
  height: 100vh;
  top: 0;
  left: 0;
  z-index: 0;
}
/*!
confetti
Copyright (c) 2023 by Wakana Y.K. (https://codepen.io/wakana-k/pen/gOqqWdY)

Luxurious version : https://codepen.io/wakana-k/pen/mdvoQaV
*/
"use strict";
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";

console.clear();

(function () {
  const worldRadius = 5;
  const confettiSize = 0.07;
  const confettiNum = 3000;
  const rotateRange_x = Math.PI / 30;
  const rotateRange_y = Math.PI / 50;
  const speed_y = 0.01;
  const speed_x = 0.003;
  const speed_z = 0.005;

  let camera, scene, renderer, controls;
  let confettiMesh;
  const dummy = new THREE.Object3D();
  const matrix = new THREE.Matrix4();
  const color = new THREE.Color();

  init();

  function init() {
    //
    camera = new THREE.PerspectiveCamera(
      35,
      window.innerWidth / window.innerHeight,
      1,
      worldRadius * 3
    );
    camera.position.z = worldRadius * Math.sqrt(2);
    //camera.position.set(-1, 1.5, 2);
    //camera.lookAt(0, 0.5, 0);

    scene = new THREE.Scene();
    //scene.background = new THREE.Color(0x666666);

    /////////////////////////////////
    // Confetti
    /////////////////////////////////
    // choose random color
    function getRandomColor() {
      let saturation = 100;
      let lightness = 50;
      const colors = [
        "hsl(0, " + saturation + "%, " + lightness + "%)",
        "hsl(30, " + saturation + "%, " + lightness + "%)",
        "hsl(60, " + saturation + "%, " + lightness + "%)",
        "hsl(90, " + saturation + "%, " + lightness + "%)",
        "hsl(120, " + saturation + "%, " + lightness + "%)",
        "hsl(150, " + saturation + "%, " + lightness + "%)",
        "hsl(180, " + saturation + "%, " + lightness + "%)",
        "hsl(210, " + saturation + "%, " + lightness + "%)",
        "hsl(240, " + saturation + "%, " + lightness + "%)",
        "hsl(270, " + saturation + "%, " + lightness + "%)",
        "hsl(300, " + saturation + "%, " + lightness + "%)",
        "hsl(330, " + saturation + "%, " + lightness + "%)"
      ];
      return colors[Math.floor(Math.random() * colors.length)];
      //return color.setHex(0xffffff * Math.random());
    }
    //
    const confettiGeometry = new THREE.PlaneGeometry(
      confettiSize / 2,
      confettiSize
    );
    const confettiMaterial = new THREE.MeshBasicMaterial({
      color: 0xffffff,
      side: THREE.DoubleSide
    });
    confettiMesh = new THREE.InstancedMesh(
      confettiGeometry,
      confettiMaterial,
      confettiNum
    );

    // set random position and rotation
    for (let i = 0; i < confettiNum; i++) {
      matrix.makeRotationFromEuler(
        new THREE.Euler(
          Math.random() * Math.PI,
          Math.random() * Math.PI,
          Math.random() * Math.PI
        )
      );
      matrix.setPosition(
        THREE.MathUtils.randFloat(-worldRadius, worldRadius),
        THREE.MathUtils.randFloat(-worldRadius, worldRadius),
        THREE.MathUtils.randFloat(-worldRadius, worldRadius)
      );
      confettiMesh.setMatrixAt(i, matrix);
      confettiMesh.setColorAt(i, color.set(getRandomColor()));
    }
    scene.add(confettiMesh);
    //
    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMap.enabled = false;
    document.body.appendChild(renderer.domElement);
    //
    controls = new OrbitControls(camera, renderer.domElement);
    controls.target.y = 0.5;
    controls.autoRotate = true; //true
    controls.autoRotateSpeed = 2;
    controls.enableDamping = true;
    controls.enablePan = false;
    controls.minDistance = 1;
    controls.maxDistance = worldRadius * Math.sqrt(2);
    controls.minPolarAngle = 0;
    controls.maxPolarAngle = Math.PI / 2;
    controls.update();
    //
    animate();
    //
    window.addEventListener("resize", onWindowResize);
  }
  function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  }
  function animate() {
    requestAnimationFrame(animate);
    controls.update();

    if (confettiMesh) {
      for (let i = 0; i < confettiNum; i++) {
        confettiMesh.getMatrixAt(i, matrix);
        matrix.decompose(dummy.position, dummy.quaternion, dummy.scale);
        // position
        //dummy.position.y -= speed_y;
        // random speedっぽくするための苦肉の策
        dummy.position.y -= speed_y * ((i % 4) + 1);

        // 下限まで落ちたら再度上に配置
        if (dummy.position.y < -worldRadius) {
          dummy.position.y = worldRadius;
          dummy.position.x = THREE.MathUtils.randFloat(
            -worldRadius,
            worldRadius
          );
          dummy.position.z = THREE.MathUtils.randFloat(
            -worldRadius,
            worldRadius
          );
        } else {
          // random positionっぽくするための苦肉の策
          if (i % 4 == 1) {
            dummy.position.x += speed_x;
            dummy.position.z += speed_z;
          } else if (i % 4 == 2) {
            dummy.position.x += speed_x;
            dummy.position.z -= speed_z;
          } else if (i % 4 == 3) {
            dummy.position.x -= speed_x;
            dummy.position.z += speed_z;
          } else {
            dummy.position.x -= speed_x;
            dummy.position.z -= speed_z;
          }
        }
        // rotation
        dummy.rotation.x += THREE.MathUtils.randFloat(0, rotateRange_x);
        dummy.rotation.z += THREE.MathUtils.randFloat(0, rotateRange_y);

        dummy.updateMatrix();
        confettiMesh.setMatrixAt(i, dummy.matrix);
      }
      confettiMesh.instanceMatrix.needsUpdate = true;
    }
    renderer.render(scene, camera);
  }
})();
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.