Sand Cursor

v1.0.0
FreeWebGLCursorInteractive

An interactive WebGL sand canvas particle field that deforms dynamically on pointer movement using React Three Fiber.

Sand Cursor

Installation

npx vectorvesper add magnetic-sand

Run npx vectorvesper init once first to set up your project.

Dependencies

npm install three @react-three/fiber
  • three
  • @react-three/fiber

Usage

Import

import MagneticSand from "@/components/vv/magnetic-sand/MagneticSand";

Usage

<MagneticSand />

Client-only component — disable SSR

This component uses WebGL and must be rendered client-side only.

import dynamic from "next/dynamic";

const SandCursor = dynamic(
  () => import("@/components/vv/magnetic-sand/MagneticSand"),
  { ssr: false }
);

Props

An interactive particle canvas simulation. Hundreds of virtual sand grains align themselves in a grid, reacting to mouse proximity by jittering, heating up, and gravitating toward the cursor with springy magnetic physics. Built on WebGL for optimal performance.

<MagneticSandVisualizer />

WebGL canvas component displaying the sand particle field.

PropTypeDefaultDescription
backgroundColorstring"#ffffff"Canvas background hex color.
color1string"#000000"Primary particle color when resting (inactive state).
color2string"#00eeff"Active glow color of particles when attracted/heated by the cursor.
particleSizenumber0.0Base size scale of the sand particles. Grains expand dynamically when near the cursor.
gridResolutionnumber140.0Grid density layout. Higher values result in smaller and more numerous particles.
glowIntensitynumber0.3Brightness of the cursor attraction glow.
trailSpeednumber0.05Damping trail coefficient. Controls how long particles retain their heat/charge after the mouse leaves.
classNamestring""Extra CSS class names for the outer container.

Examples

Light Sand (Default)
<MagneticSandVisualizer />
Dark Magnetic StormAn elegant dark mode version with dense, neon-purple particles.
<MagneticSandVisualizer
  backgroundColor="#07070a"
  color1="#1a1a24"
  color2="#a855f7"
  gridResolution={180}
  glowIntensity={0.6}
/>
WebGL component. Requires browser canvas APIs. Must be imported dynamically with ssr: false.
Applies a performance optimization that limits the canvas maximum devicePixelRatio to 1.5. This maintains high quality while significantly lowering pixel shading overhead on high-DPI displays.

Source

"use client";
import React, { useRef, useEffect, useMemo } from "react";
import { Canvas, useFrame, useThree } from "@react-three/fiber";
import * as THREE from "three";

export interface MagneticSandProps {
  backgroundColor?: string;
  color1?: string;
  color2?: string;
  particleSize?: number;
  gridResolution?: number;
  glowIntensity?: number;
  trailSpeed?: number;
  className?: string;
  style?: React.CSSProperties;
}

const MagneticSandScene: React.FC<MagneticSandProps> = ({
  backgroundColor = "#ffffff",
  color1 = "#000000",
  color2 = "#00eeff",
  particleSize = 0.0,
  gridResolution = 140.0,
  glowIntensity = 0.3,
  trailSpeed = 0.05,
}) => {
  const meshRef = useRef<THREE.Mesh<THREE.BufferGeometry, THREE.ShaderMaterial>>(null);
  const { viewport } = useThree();

  const setupRef = useRef<{
    canvas: HTMLCanvasElement;
    ctx: CanvasRenderingContext2D | null;
    heatTexture: THREE.CanvasTexture;
  } | null>(null);

  if (setupRef.current === null && typeof window !== "undefined") {
    const can = document.createElement("canvas");
    can.width = 256;
    can.height = 256;
    const context = can.getContext("2d");
    if (context) {
      context.fillStyle = "black";
      context.fillRect(0, 0, 256, 256);
    }
    const texture = new THREE.CanvasTexture(can);
    texture.minFilter = THREE.LinearFilter;
    setupRef.current = { canvas: can, ctx: context, heatTexture: texture };
  }

  useEffect(() => {
View on GitHub →Report an issue