Skip to content

Instantly share code, notes, and snippets.

@aneeskodappana
Created March 15, 2025 20:18
Show Gist options
  • Save aneeskodappana/d75f38afc4451f924f1aad6a750bd205 to your computer and use it in GitHub Desktop.
Save aneeskodappana/d75f38afc4451f924f1aad6a750bd205 to your computer and use it in GitHub Desktop.
ThreeJS Google earth like navigation
import { useRef, useEffect } from "react";
import { Canvas, useThree } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";
import { OrbitControls as OrbitControlsImpl } from "three-stdlib";
import * as THREE from "three";
import gsap from "gsap";
import "./App.css"; // Added import
function Scene() {
const { camera, gl, scene, raycaster } = useThree();
const controlsRef = useRef<OrbitControlsImpl>(null);
const pointer = useRef(new THREE.Vector2());
const isDragging = useRef(false);
const mouseDownPos = useRef(new THREE.Vector2());
const onMouseDown = (event: MouseEvent) => {
mouseDownPos.current.set(event.clientX, event.clientY);
isDragging.current = false;
};
const onMouseMove = (event: MouseEvent) => {
const dx = event.clientX - mouseDownPos.current.x;
const dy = event.clientY - mouseDownPos.current.y;
const threshold = 5;
if (Math.sqrt(dx * dx + dy * dy) > threshold) {
isDragging.current = true;
}
};
const onClick = (event: MouseEvent) => {
if (isDragging.current) return;
pointer.current.set(
(event.clientX / window.innerWidth) * 2 - 1,
-(event.clientY / window.innerHeight) * 2 + 1
);
raycaster.setFromCamera(pointer.current, camera);
const intersects = raycaster.intersectObjects(scene.children);
if (controlsRef.current === null) {
return;
}
if (intersects.length === 0) {
return;
}
const clickedPoint = intersects[0].point;
gsap.to(controlsRef.current.target, {
x: clickedPoint.x,
y: clickedPoint.y,
z: clickedPoint.z,
duration: 1,
ease: "power2.inOut",
onUpdate: () => controlsRef.current!.update(),
});
const direction = clickedPoint.clone().sub(camera.position).normalize();
const currentDistance = camera.position.distanceTo(clickedPoint);
const targetDistance = Math.max(1, currentDistance * 0.3);
const targetPosition = clickedPoint
.clone()
.sub(direction.multiplyScalar(targetDistance));
gsap.to(camera.position, {
x: targetPosition.x,
y: targetPosition.y,
z: targetPosition.z,
duration: 1,
ease: "power2.inOut",
onUpdate: () => controlsRef.current!.update(),
});
};
useEffect(() => {
const canvas = gl.domElement;
canvas.addEventListener("mousedown", onMouseDown);
canvas.addEventListener("mousemove", onMouseMove);
canvas.addEventListener("click", onClick);
return () => {
canvas.removeEventListener("mousedown", onMouseDown);
canvas.removeEventListener("mousemove", onMouseMove);
canvas.removeEventListener("click", onClick);
};
}, [gl]);
return (
<>
<OrbitControls
minPolarAngle={0.1}
maxPolarAngle={1.5}
ref={controlsRef}
enablePan={false}
enableZoom={false}
/>
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0, 0]}>
<planeGeometry args={[50, 50]} />
<meshStandardMaterial color="gray" />
</mesh>
<mesh position={[0, 1, 0]}>
<boxGeometry args={[2, 2, 2]} />
<meshStandardMaterial color="red" />
</mesh>
<mesh position={[5, 1, 5]}>
<sphereGeometry args={[1, 32, 32]} />
<meshStandardMaterial color="blue" />
</mesh>
</>
);
}
export default function App() {
return (
<div className="canvas-container">
<Canvas camera={{ position: [0, 5, 10], fov: 60 }}>
<Scene />
<ambientLight intensity={0.5} />
<pointLight position={[10, 10, 10]} />
</Canvas>
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment