npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

r3f-navigation-controls

v1.0.3

Published

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.

Downloads

396

Readme

r3f-navigation-controls

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


✨ Features

  • 🎮 Dual Input Systems

    • Desktop: WASD keyboard + pointer lock (FPS-style mouse look)
    • Mobile: Touch joystick + drag-to-rotate camera
  • 📱 Smart Device Detection

    • Automatically detects mobile vs desktop
    • Responsive to resize/orientation changes
    • No joystick shown on PC, even with touch support
  • 🎨 Fully Customizable UI

    • Joystick size, colors, opacity, position
    • Appearance configuration for branding
  • Camera-Relative Movement

    • Forward/backward/left/right always follow where camera is looking
    • Smooth quaternion-based rotation
  • 🔧 Easy Integration

    • Single <NavigationController /> component
    • One useNavigationSetup() hook call
    • Drop into any React Three Fiber project
  • 📦 No Dependencies

    • Only requires React, Three.js, React Three Fiber
    • Lightweight and tree-shakeable

📦 Installation

npm install r3f-navigation-controls

🚀 Quick Start

Ultra-Minimal Setup (Recommended)

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:

  • ✅ Detects if device is mobile or desktop
  • ✅ Shows joystick on mobile only (rendered outside Canvas)
  • ✅ Adds PointerLockControls on desktop
  • ✅ Adds "Enter Pointer Lock" button on desktop
  • ✅ Handles all camera movement and rotation

Advanced Setup (If You Need Custom Control)

If you need more flexibility, use the individual components:


📖 API Reference

<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:

  • ✅ Detects mobile vs desktop automatically
  • ✅ Renders PointerLockControls on desktop (inside Canvas)
  • ✅ Handles all camera movement (keyboard + joystick)
  • ✅ Handles all camera rotation (pointer lock + touch drag)
  • ✅ Creates "Enter Pointer Lock" button on desktop
  • ✅ Returns 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:

  • ✅ Auto-detects mobile vs desktop
  • ✅ Renders joystick only on mobile devices
  • ✅ Completely outside Canvas (no R3F conflicts)
  • ✅ Updates joystickDataRef in real-time when dragged

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)

Advanced: Individual Components

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:

  • ✅ Mobile detection (UA + touch + coarse + viewport)
  • ✅ Pointer lock state tracking
  • ✅ Resize/orientation listeners
  • ✅ Returns ready-to-use refs and callbacks

<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:

  • Handles all camera movement (joystick on mobile, WASD on desktop)
  • Manages touch rotation on mobile
  • Returns 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 true
  • joystickDataRef - Shared ref with NavigationController
  • appearance - Customize colors, size, position

useKeyboard()

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,
});

🎮 Control Scheme

Desktop

| 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 |

Mobile

| Action | Input | |--------|-------| | Move | Drag left joystick | | Rotate Camera | Drag right side of screen | | Look around | Multi-direction drag |


🎨 Customization Examples

Fast Movement Speed

<NavigationController speed={0.3} joystickDataRef={joystickDataRef} />

Vibrant Neon Joystick

<JoystickController
  isMobile={true}
  joystickDataRef={joystickDataRef}
  appearance={{
    joystickSize: 140,
    primaryColor: '#ff00ff',
    secondaryColor: '#00ffff',
    opacity: 0.8,
    borderRadius: 100,
    borderWidth: 4,
  }}
/>

Force Desktop Mode (for testing)

<NavigationController 
  joystickDataRef={joystickDataRef} 
  isMobileOverride={false}
/>

📱 Device Detection Logic

The package automatically detects mobile devices by checking:

  1. User-Agent: Mobile OS strings (Android, iOS, Windows Phone, etc.)
  2. Touch Support + Pointer Type: Has touch AND coarse pointer
  3. Viewport Size: ≤ 900px width (small screen)

Result:

  • ✅ Joystick shown on: Real phones, tablets, mobile browsers
  • ❌ Joystick hidden on: Desktop, touch-enabled laptops (large screens)

🔌 Full Example (Manual Control)

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,
          }}
        />
      )}
    </>
  );
}

🛠️ Advanced: Custom Movement Logic

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;
}

🤝 Contributing

Issues and PRs welcome! This project is maintained on GitHub.


📄 License

MIT


🙏 Credits

Built for React Three Fiber projects using Three.js and modern React hooks.