
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
r3f-navigation-controls
Advanced tools
Easy-to-use React component for joystick and pointer controls in 3D scenes (Three.js/React Three Fiber). Setup movement, camera rotation, and customizable UI.
A production-ready React component library for joystick, keyboard, and pointer controls in 3D scenes with React Three Fiber. Perfect for FPS games, exploration, and mobile 3D apps.
Desktop: WASD + Pointer Lock (FPS style) | Mobile: Left joystick + right-side drag to rotate
🎮 Dual Input Systems
📱 Smart Device Detection
🎨 Fully Customizable UI
⚡ Camera-Relative Movement
🔧 Easy Integration
<NavigationController /> componentuseNavigationSetup() hook call📦 No Dependencies
npm install r3f-navigation-controls
The Navigator component + JoystickUI handle everything automatically — mobile detection, joystick UI, pointer lock, all camera movement and rotation:
import { useRef } from 'react';
import { Canvas } from '@react-three/fiber';
import { Navigator, JoystickUI } from 'r3f-navigation-controls';
function Scene() {
return (
<>
<ambientLight intensity={0.5} />
<pointLight position={[10, 10, 10]} />
{/* Your 3D scene */}
</>
);
}
export default function App() {
const joystickDataRef = useRef({ x: 0, y: 0 });
return (
<>
<Canvas camera={{ position: [0, 5, 10] }}>
<Navigator speed={0.18} joystickDataRef={joystickDataRef} />
<Scene />
</Canvas>
<JoystickUI
joystickDataRef={joystickDataRef}
appearance={{ joystickSize: 120, primaryColor: '#00ff88' }}
/>
</>
);
}
That's it! The package automatically:
If you need more flexibility, use the individual components:
<Navigator /> (Inside Canvas)Handles all movement logic and rendering.
<Canvas>
<Navigator
speed={0.18}
joystickDataRef={joystickDataRef}
isMobileOverride={false} // Optional: force mobile/desktop mode
/>
<Scene />
</Canvas>
What it does:
null (headless, no DOM inside Canvas)Props:
speed - Movement speed (default: 0.18)joystickDataRef - Shared ref with JoystickUI (updates when joystick is dragged)isMobileOverride - Force mobile/desktop mode for testing<JoystickUI /> (Outside Canvas)Renders the joystick UI outside of Canvas.
<JoystickUI
joystickDataRef={joystickDataRef}
appearance={{
joystickSize: 120,
primaryColor: '#00ff88',
secondaryColor: '#00d4ff',
opacity: 0.6,
borderRadius: 50,
borderWidth: 3,
showLabels: true,
}}
/>
What it does:
Props:
joystickDataRef - Shared ref with AppSetup (required)appearance - Customize colors, size, opacity, etc.
joystickSize (default: 100)primaryColor (default: '#00ff88')secondaryColor (default: '#00d4ff')opacity (default: 0.6)borderRadius (default: 50)borderWidth (default: 3)showLabels (default: true)If you need more control, you can use the lower-level components:
useNavigationSetup()const {
isMobile, // boolean - device type
usePointerLock, // boolean - pointer lock active
joystickDataRef, // ref - joystick input data
requestPointerLock, // function - request pointer lock on desktop
} = useNavigationSetup();
Features:
<Navigator /> (recommended) or <NavigationController />Headless controller component (goes inside Canvas).
<Navigator
joystickDataRef={joystickDataRef}
speed={0.18}
isMobileOverride={false} // Optional: force mobile/desktop mode
/>
What it does:
null (no visible DOM)<JoystickController />Renders the joystick UI (goes outside Canvas).
<JoystickController
isMobile={true}
joystickDataRef={joystickDataRef}
appearance={{
joystickSize: 120,
primaryColor: '#00ff88',
secondaryColor: '#00d4ff',
opacity: 0.6,
borderRadius: 50,
borderWidth: 3,
showLabels: true,
}}
/>
Props:
isMobile - Show joystick when truejoystickDataRef - Shared ref with NavigationControllerappearance - Customize colors, size, positionuseKeyboard()Get keyboard state (used internally, but available).
const keyboard = useKeyboard();
// keyboard.forward, keyboard.backward, keyboard.left, keyboard.right
// keyboard.shift, keyboard.space
usePointerControls(config?)Get pointer/touch rotation data (used internally, but available).
const pointerRotation = usePointerControls({
sensitivity: 0.002,
minAngle: -Math.PI / 3,
maxAngle: Math.PI / 3,
});
| Action | Key(s) |
|---|---|
| Move Forward | W / ↑ |
| Move Backward | S / ↓ |
| Strafe Left | A / ← |
| Strafe Right | D / → |
| Look Up/Down | Mouse (after pointer lock) |
| Enable Pointer Lock | Click "Enter Pointer Lock" button |
| Exit Pointer Lock | ESC |
| Action | Input |
|---|---|
| Move | Drag left joystick |
| Rotate Camera | Drag right side of screen |
| Look around | Multi-direction drag |
<NavigationController speed={0.3} joystickDataRef={joystickDataRef} />
<JoystickController
isMobile={true}
joystickDataRef={joystickDataRef}
appearance={{
joystickSize: 140,
primaryColor: '#ff00ff',
secondaryColor: '#00ffff',
opacity: 0.8,
borderRadius: 100,
borderWidth: 4,
}}
/>
<NavigationController
joystickDataRef={joystickDataRef}
isMobileOverride={false}
/>
The package automatically detects mobile devices by checking:
Result:
For advanced use cases where you want to use individual components:
import { useRef } from 'react';
import { Canvas } from '@react-three/fiber';
import { PointerLockControls } from '@react-three/drei';
import {
NavigationController,
JoystickController,
useNavigationSetup
} from 'r3f-navigation-controls';
function Scene() {
return (
<>
<ambientLight intensity={0.5} />
<pointLight position={[10, 10, 10]} />
{/* Add your 3D models and scene here */}
</>
);
}
export default function App() {
const { isMobile, usePointerLock, joystickDataRef, requestPointerLock } = useNavigationSetup();
return (
<>
<Canvas camera={{ position: [0, 5, 10] }}>
<Scene />
<Navigator joystickDataRef={joystickDataRef} />
{!isMobile && <PointerLockControls />}
</Canvas>
{!isMobile && !usePointerLock && (
<button
onClick={requestPointerLock}
style={{
position: 'absolute',
top: '20px',
right: '20px',
padding: '10px 20px',
cursor: 'pointer',
}}
>
Enable Pointer Lock
</button>
)}
{isMobile && (
<JoystickController
isMobile={true}
joystickDataRef={joystickDataRef}
appearance={{
joystickSize: 120,
primaryColor: '#00ff88',
secondaryColor: '#00d4ff',
opacity: 0.6,
}}
/>
)}
</>
);
}
If you need to override movement behavior, you can access the raw hooks:
import { useKeyboard, usePointerControls } from 'r3f-navigation-controls';
import { useFrame } from '@react-three/fiber';
import { Vector3 } from 'three';
function CustomController() {
const keyboard = useKeyboard();
const pointerRotation = usePointerControls();
useFrame(({ camera }) => {
const forward = new Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
if (keyboard.forward) camera.position.add(forward.multiplyScalar(0.1));
});
return null;
}
Issues and PRs welcome! This project is maintained on GitHub.
MIT
Built for React Three Fiber projects using Three.js and modern React hooks.
FAQs
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.