Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/aurora-drift/llms.txt

Use this file to discover all available pages before exploring further.

One of the most powerful patterns in Aurora Drift is the use of Framer Motion’s MotionValue system to drive continuous, pointer-reactive animations with zero React re-renders. A MotionValue is a special reactive container — it holds a number (or string) and notifies subscribers directly on the animation frame loop, completely bypassing React’s reconciliation cycle. This makes it ideal for anything that needs to update 60+ times per second in response to mouse movement, scroll, or physics output.

What Are MotionValues?

A MotionValue is created with useMotionValue(initialValue). It has a .set() method to push new values into it and a .get() method to read the current value. When you pass a MotionValue to a motion.* element’s style prop, Framer Motion subscribes to it and applies DOM updates directly — no virtual DOM diffing, no state, no re-renders.
import { useMotionValue } from 'framer-motion';
import { motion } from 'framer-motion';

function Example() {
  const x = useMotionValue(0);

  return (
    <motion.div
      style={{ x }}                        // MotionValue wired directly to the DOM
      onMouseMove={(e) => x.set(e.clientX)} // Updates go straight to the frame loop
    />
  );
}
Because MotionValue.set() does not call setState, it does not trigger React re-renders. The component that creates the MotionValue mounts once and stays mounted — only the underlying DOM transform is updated on every frame. This is what makes Aurora Drift’s pointer effects so performant, even at 60fps.

Aurora Background Parallax

AuroraBackground.js uses two MotionValues — x and y — to track the normalized mouse position across the viewport. These are then piped through useSpring to produce a smoothly lagging springX and springY, which drive the aurora SVG layer.

How the Coordinates Are Mapped

Mouse position is normalized to a ±10 pixel range (centered at 0) so the parallax shift is subtle:
const normalizedX = (clientX / window.innerWidth  - 0.5) * 20;
const normalizedY = (clientY / window.innerHeight - 0.5) * 20;
  • At the left edge of the screen, normalizedX = -10
  • At the center, normalizedX = 0
  • At the right edge, normalizedX = +10

Full Parallax Setup

import { useMotionValue } from 'framer-motion';
import { useSpring } from '../assets/use-spring';
import { motion } from 'framer-motion';

function AuroraBackground() {
  // Raw motion values initialized at center (0)
  const x = useMotionValue(0);
  const y = useMotionValue(0);

  // Spring-smoothed followers — high damping for a slow, majestic drift
  const springX = useSpring(x, { damping: 50, stiffness: 400 });
  const springY = useSpring(y, { damping: 50, stiffness: 400 });

  useEffect(() => {
    const onMouseMove = (e) => {
      const nx = (e.clientX / window.innerWidth  - 0.5) * 20;
      const ny = (e.clientY / window.innerHeight - 0.5) * 20;
      x.set(nx);
      y.set(ny);
    };

    window.addEventListener('mousemove', onMouseMove);
    return () => window.removeEventListener('mousemove', onMouseMove);
  }, [x, y]);

  return (
    <div className="fixed inset-0 z-[-1] overflow-hidden bg-navy pointer-events-none">
      {/* The aurora SVG layer shifts with the spring output */}
      <motion.div
        className="absolute inset-0 w-full h-[150vh] -top-[25vh]"
        style={{ x: springX, y: springY }}
      >
        <svg viewBox="0 0 1200 800" className="w-full h-full opacity-60 mix-blend-screen blur-3xl">
          {/* Aurora path layers animate independently via variants */}
        </svg>
      </motion.div>
    </div>
  );
}
The aurora layer is sized to 150vh (50% taller than the viewport) and offset -25vh so the ±10px spring shift never reveals the background edge. Always size parallax layers larger than their container.

Cursor Glow

CursorGlow.js creates a 300×300px radial-gradient orb that follows the cursor. The MotionValues are initialized at -100 (off-screen) so the glow doesn’t flash at the origin before the first mousemove.

Offset Calculation

The glow div is 300px × 300px. To keep it centered on the cursor, the position is offset by half its size:
x.set(e.clientX - 150);  // center horizontally
y.set(e.clientY - 150);  // center vertically

Full Cursor Glow Setup

import { useMotionValue } from 'framer-motion';
import { useSpring } from '../assets/use-spring';
import { motion } from 'framer-motion';

function CursorGlow() {
  const [isVisible, setIsVisible] = useState(false);

  // Initialize off-screen (-100) to avoid a visible flash on mount
  const rawX = useMotionValue(-100);
  const rawY = useMotionValue(-100);

  // Snappy spring config — light mass for a floaty-but-responsive feel
  const springConfig = { damping: 25, stiffness: 200, mass: 0.5 };
  const x = useSpring(rawX, springConfig);
  const y = useSpring(rawY, springConfig);

  useEffect(() => {
    // Only show on fine pointer devices (mouse, not touch)
    if (window.matchMedia('(pointer: fine)').matches) {
      setIsVisible(true);
    }

    const onMouseMove = (e) => {
      rawX.set(e.clientX - 150);
      rawY.set(e.clientY - 150);
    };

    window.addEventListener('mousemove', onMouseMove);
    document.addEventListener('mouseleave', () => setIsVisible(false));
    document.addEventListener('mouseenter', () => setIsVisible(true));

    return () => {
      window.removeEventListener('mousemove', onMouseMove);
    };
  }, [rawX, rawY]);

  if (!isVisible) return null;

  return (
    <motion.div
      className="fixed top-0 left-0 w-[300px] h-[300px] rounded-full pointer-events-none z-50 mix-blend-screen"
      style={{
        x,
        y,
        background: 'radial-gradient(circle, rgba(45,212,191,0.15) 0%, rgba(45,212,191,0) 70%)',
      }}
    />
  );
}

How the Two Patterns Compare

Aurora Parallax

Source: Mouse position normalized to ±10px
Spring: damping: 50, stiffness: 400
Effect: The aurora layer drifts slowly behind cursor movement, creating depth
Initial value: 0 (centered)

Cursor Glow

Source: clientX/Y - 150 (centered on cursor)
Spring: damping: 25, stiffness: 200, mass: 0.5
Effect: Radial glow orb trails the cursor with a soft, springy lag
Initial value: -100 (hidden off-screen)

The MotionValue Pipeline

Both patterns follow the same three-stage pipeline:
1

Raw MotionValue

Created with useMotionValue(initialValue). Updated imperatively via .set() inside an event handler. This is the “target” — where the animation wants to go.
2

Spring Follower

Created with useSpring(rawValue, config). This derived MotionValue automatically chases the raw value with physics, producing smooth interpolated output on every animation frame.
3

DOM Application

The spring output is passed to a motion.* element’s style prop (e.g., style={{ x: springX }}). Framer Motion applies it as a CSS transform directly — no React updates involved.

Going Further: useTransform

Framer Motion’s useTransform lets you map one MotionValue to another range without any extra event handlers. For example, you could map springX (which ranges ±10) to a rotation or opacity:
import { useTransform } from 'framer-motion';

// Map springX (-10 to +10) to a rotation (-5deg to +5deg)
const rotate = useTransform(springX, [-10, 10], [-5, 5]);

// Map springX to a subtle opacity shift
const opacity = useTransform(springX, [-10, 10], [0.8, 1.0]);

return <motion.div style={{ rotate, opacity }} />;
Chaining useTransform on top of a spring output is one of the most expressive patterns in the Framer Motion toolkit — all computed values stay inside the frame loop, never touching React state.

Performance Characteristics

ApproachRe-renders on mouse moveFrame loop updatesSuitable for 60fps?
useState + CSSYes — every pixelVia React reconcilerNo — jank at high frequency
useMotionValue + springNeverDirect DOM via RAFYes
CSS transitions on class changeNoBrowser compositorYes, but no physics control

Build docs developers (and LLMs) love