shader-mouse
v1.0.0
Published
Mouse UV tracking for Three.js and React Three Fiber shaders
Maintainers
Readme
shader-mouse
shader-mouse tracks pointer position as UV coordinates on 3D meshes and updates shader uniforms automatically. Create interactive GLSL effects, hover states, and click-driven visuals with accurate UV coordinates that work seamlessly across any geometry.
✨ Features
| Feature | Description |
|---------|-------------|
| 🎯 Universal geometry support | Works with PlaneGeometry, SphereGeometry, BoxGeometry, TorusGeometry, and custom shapes |
| 🎨 Accurate UV mapping | Uses raycast intersection UV for perfect coordinate tracking on any mesh |
| 🌊 Seamless plane projection | Fallback system for continuous effects across multiple flat surfaces |
| ⚡ Optimized performance | Pointer events, cached DOMRect, pre-bound handlers, minimal recomputation |
| 🔧 Flexible uniforms | Auto-detects and updates vec2, vec3, vec4 uniforms |
| 📱 Touch & mobile ready | Full touch/pointer event support with passive listeners |
| 🎭 Hover & click states | Built-in interaction detection (hover/click only when over mesh) |
| 🎨 Framework agnostic | Works with vanilla Three.js and React Three Fiber |
| 📦 Multi-format builds | ESM, CommonJS, UMD bundles + TypeScript declarations |
| 🌐 CDN ready | Direct browser usage via UNPKG/JSDelivr |
📦 Installation
# npm
npm install shader-mouse three
# yarn
yarn add shader-mouse three
# pnpm
pnpm add shader-mouse threeCDN Usage (UMD)
<script src="https://unpkg.com/[email protected]/build/three.min.js"></script>
<script src="https://unpkg.com/shader-mouse/dist/shader-mouse.umd.js"></script>
<script>
// ShaderMouse available as global
const { ShaderMouse } = window.ShaderMouse;
// ... use as in examples
</script>🚀 Quick Start (Three.js)
import * as THREE from 'three'
import { ShaderMouse } from 'shader-mouse'
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 100)
camera.position.z = 3
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(innerWidth, innerHeight)
document.body.appendChild(renderer.domElement)
const material = new THREE.ShaderMaterial({
uniforms: {
uMouse: { value: new THREE.Vector4(0, 0, 0, 0) } // xy=uv, z=hover, w=click
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform vec4 uMouse;
varying vec2 vUv;
void main() {
float dist = distance(vUv, uMouse.xy);
float spot = smoothstep(0.3, 0.0, dist);
vec3 color = vec3(0.2) + vec3(spot) + vec3(0.3) * uMouse.z * spot;
gl_FragColor = vec4(color, 1.0);
}
`
})
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material)
scene.add(mesh)
const shaderMouse = new ShaderMouse({ camera, domElement: renderer.domElement })
shaderMouse.add(mesh)
const animate = () => {
requestAnimationFrame(animate)
renderer.render(scene, camera)
}
animate()⚛️ React Three Fiber
import { useRef, useEffect, useMemo } from 'react'
import { Canvas, useThree } from '@react-three/fiber'
import * as THREE from 'three'
import { ShaderMouse } from 'shader-mouse'
function Plane({ shaderMouse }) {
const meshRef = useRef()
const uniforms = useMemo(() => ({
uMouse: { value: new THREE.Vector4(0, 0, 0, 0) }
}), [])
useEffect(() => {
if (meshRef.current && shaderMouse) {
shaderMouse.add(meshRef.current)
return () => shaderMouse.remove(meshRef.current)
}
}, [shaderMouse])
return (
<mesh ref={meshRef}>
<planeGeometry args={[2, 2]} />
<shaderMaterial uniforms={uniforms} vertexShader={'...'} fragmentShader={'...'} />
</mesh>
)
}
function Scene() {
const { camera, gl } = useThree()
const shaderMouseRef = useRef()
useEffect(() => {
shaderMouseRef.current = new ShaderMouse({ domElement: gl.domElement, camera })
return () => shaderMouseRef.current?.dispose()
}, [camera, gl])
return <Plane shaderMouse={shaderMouseRef.current} />
}
export default function App() {
return (
<Canvas camera={{ position: [0, 0, 3] }}>
<Scene />
</Canvas>
)
}📖 API Reference
Constructor Options
interface ShaderMouseOptions {
domElement?: HTMLElement // Target element (default: document.body)
camera: Camera // Three.js camera (required)
autoUpdate?: boolean // Auto-update on RAF (default: true)
}ShaderMouse Methods
add(object: Object3D, uniformName?: string): this
Adds a mesh to mouse tracking.
object: Three.js Object3D (mesh)uniformName: Shader uniform name (default: "uMouse")
shaderMouse.add(mesh, 'uMousePosition')remove(object: Object3D): this
Removes a mesh from mouse tracking.
shaderMouse.remove(mesh)update(): void
Manually update all tracked objects (only needed if autoUpdate: false).
shaderMouse.update()getState(object: Object3D): MouseState | null
Get current mouse state for specific object.
const state = shaderMouse.getState(mesh)
// { uv: Vector2, isHover: boolean, isPressed: boolean }resize(): void
Update cached DOM element bounds (call on window resize).
window.addEventListener('resize', () => shaderMouse.resize())dispose(): void
Clean up resources and remove event listeners.
shaderMouse.dispose()Properties
enabled: boolean
Enable/disable mouse tracking.
shaderMouse.enabled = false // Pause trackingcamera: Camera
Get/set active camera.
shaderMouse.camera = newCamera🎨 Uniform Types
The library automatically detects and updates different uniform types:
vec2 - UV Only
uniform vec2 uMouse; // x=u, y=vvec3 - UV + State
uniform vec3 uMouse; // x=u, y=v, z=state (0=none, 0.5=hover, 1=click)vec4 - UV + Hover + Click
uniform vec4 uMouse; // x=u, y=v, z=hover, w=click🎮 Interactive Examples
Spotlight Effect
uniform vec4 uMouse;
varying vec2 vUv;
void main() {
float dist = distance(vUv, uMouse.xy);
float spotlight = smoothstep(0.3, 0.0, dist);
vec3 color = vec3(0.1);
color += vec3(0.5) * spotlight;
color += vec3(0.3) * uMouse.z * spotlight; // Hover glow
color += vec3(1.0) * uMouse.w * spotlight; // Click flash
gl_FragColor = vec4(color, 1.0);
}Ripple Effect
uniform vec4 uMouse;
uniform float uTime;
varying vec2 vUv;
void main() {
float dist = distance(vUv, uMouse.xy);
float ripple = sin(dist * 20.0 - uTime * 5.0) * 0.5 + 0.5;
vec3 color = vec3(ripple * uMouse.z); // Only show on hover
gl_FragColor = vec4(color, 1.0);
}Displacement Effect
// Vertex Shader
uniform vec4 uMouse;
varying vec2 vUv;
void main() {
vUv = uv;
vec3 pos = position;
float dist = distance(uv, uMouse.xy);
float displacement = smoothstep(0.3, 0.0, dist) * uMouse.w; // On click
pos.z += displacement * 0.5;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}🏗️ Advanced Usage
Multiple Meshes
const shaderMouse = new ShaderMouse({ camera, domElement: canvas })
// Different uniform names
shaderMouse.add(mesh1, 'uMouse')
shaderMouse.add(mesh2, 'uPointer')
shaderMouse.add(mesh3, 'uCursor')Manual Updates
const shaderMouse = new ShaderMouse({
camera,
domElement: canvas,
autoUpdate: false // Disable auto-update
})
// Manual control in render loop
function animate() {
shaderMouse.update()
renderer.render(scene, camera)
requestAnimationFrame(animate)
}Custom DOM Element
const canvas = document.getElementById('my-canvas')
const shaderMouse = new ShaderMouse({
camera,
domElement: canvas // Custom target
})Geometry Support
Works with any Three.js geometry:
// All supported
new THREE.PlaneGeometry(2, 1) // ✅ Any size
new THREE.SphereGeometry(1, 32, 16) // ✅ Curved surfaces
new THREE.BoxGeometry(1, 1, 1) // ✅ Multiple faces
new THREE.TorusGeometry(1, 0.3) // ✅ Complex shapes
new THREE.CylinderGeometry(1, 1, 2) // ✅ Custom geo🔧 TypeScript Support
Full TypeScript support with exported types:
import { ShaderMouse, type ShaderMouseOptions, type MouseState, type TrackedObject } from 'shader-mouse'
const options: ShaderMouseOptions = {
camera: myCamera,
domElement: myCanvas,
autoUpdate: true
}
const shaderMouse = new ShaderMouse(options)
const state: MouseState = shaderMouse.getState(mesh)!⚡ Performance Tips
- Use pointer events: Built-in for better mobile performance
- Cached DOM rect: Automatically cached, manually update with
resize() - Disable auto-update: Use
autoUpdate: falsefor manual control - Limit tracked objects: Only add meshes that need mouse interaction
- Passive listeners: All events use
{ passive: true }
📁 Example Projects
Check the examples/ folder:
examples/vanilla/- Three.js + Viteexamples/react/- React Three Fiber + Vite
To run examples:
cd examples/vanilla && npm install && npm run dev
# or
cd examples/react && npm install && npm run dev🤝 Contributing
Contributions are welcome! Feel free to:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
📄 License
MIT © Aayush Chouhan
