Code Playground

Open in CodeSandbox
import { random, range } from 'lodash';
import './reset.css';
import './styles.css';

const PRECONFIGURED_PARTICLES = [
  {
    angle: 210,
    distance: 55,
  },
  {
    angle: 234,
    distance: 30,
  },
  {
    angle: 225,
    distance: 75,
  },
  {
    angle: 240,
    distance: 50,
  },
];

window.addEventListener('click', (event) => {
  const x = event.clientX;
  const y = event.clientY;

  // Skip this effect for folks with motion sensitivities.
  // (In the course, we explore the accessibility implicationsof
  // this effect in much more detail!)
  const motionEnabled = window.matchMedia(
    '(prefers-reduced-motion: no-preference)'
  ).matches;
  if (!motionEnabled) {
    return;
  }

  const particles = [];

  range(4).forEach((index) => {
    const particle = document.createElement('img');
    particle.setAttribute('alt', '');
    particle.setAttribute(
      'src',
      'https://sandpack-bundler.vercel.app/img/wand-sparkle.svg'
    );
    particle.setAttribute('aria-hidden', 'true');
    particle.classList.add('star');

    particle.style.top = y + 'px';
    particle.style.left = x + 'px';

    // Grab the preconfigured angle/distance, but add a bit
    // of random variation:
    let { angle, distance } = PRECONFIGURED_PARTICLES[index];
    angle += random(-8, 8);
    distance += random(-5, 5);

    // Other dynamic values, like spin and size:
    const rotation = random(-45, -270);
    const size = random(13, 18);

    particle.style.setProperty('--size', size + 'px')
    particle.style.setProperty('--angle', angle + 'deg');
    particle.style.setProperty('--distance', distance + 'px');
    particle.style.setProperty('--rotation', rotation + 'deg');

    // Generate a travel duration. The “normalize” function is
    // used to derive the duration from the total distance traveled.
    // We add a small bit of randomness to that as well.
    particle.style.setProperty(
      '--pop-duration',
      normalize(distance, 20, 85, 1000, 1400) +
        random(-300, 300) + 'ms'
    );

    // The fade transition settings are separate, and
    // generated here:
    particle.style.setProperty(
      '--fade-duration',
      random(700, 900) + 'ms'
    );
    particle.style.setProperty(
      '--fade-delay',
      normalize(distance, 20, 85, 250, 500) + 'ms'
    );

    document.body.appendChild(particle);
    particles.push(particle);
  });

  // Clean up the particles after they’ve finished fading away:
  window.setTimeout(
    () => {
      particles.forEach((particle) => {
        particle.remove();
      });
    },
    3000
  );
});

const normalize = (
  number,
  currentScaleMin,
  currentScaleMax,
  newScaleMin,
  newScaleMax
) => {
  // FIrst, normalize the value between 0 and 1.
  const standardNormalization =
    (number - currentScaleMin) / (currentScaleMax - currentScaleMin);

  // Next, transpose that value to our desired scale.
  return (
    (newScaleMax - newScaleMin) * standardNormalization + newScaleMin
  );
};