Map Camera Follow
terrae/camera-followFreeComponent
Animate camera along a path for immersive fly-through experiences.
Install it
npx shadcn@latest add https://www.terrae.dev/camera-follow.jsonSource
1"use client"23import { useEffect, useRef, useState, type ReactNode } from "react"4import { createPortal } from "react-dom"5import type mapboxgl from "mapbox-gl"6import { mapgl } from "./map-library"7import { useMap } from "./hooks"8import type { MapPath } from "./types"910type MarkerState = {11 position: [number, number]12 bearing: number13}1415type MapCameraFollowProps = {16 path: MapPath17 duration?: number18 zoom?: number19 pitch?: number20 autoStart?: boolean21 loop?: boolean22 loopDelay?: number23 marker?: boolean | ReactNode24 markerSize?: number25 onComplete?: () => void26}2728const DEFAULT_DURATION = 2000029const DEFAULT_ZOOM = 1430const DEFAULT_PITCH = 6031const DEFAULT_LOOP_DELAY = 100032const DEFAULT_MARKER_SIZE = 483334const calculateBearing = (from: [number, number], to: [number, number]): number => {35 const dLon = ((to[0] - from[0]) * Math.PI) / 18036 const lat1 = (from[1] * Math.PI) / 18037 const lat2 = (to[1] * Math.PI) / 1803839 const y = Math.sin(dLon) * Math.cos(lat2)40 const x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon)4142 return ((Math.atan2(y, x) * 180) / Math.PI + 360) % 36043}4445const interpolatePosition = (path: MapPath, progress: number): [number, number] => {46 const totalSegments = path.length - 147 const segmentProgress = progress * totalSegments48 const segmentIndex = Math.min(Math.floor(segmentProgress), totalSegments - 1)49 const segmentFraction = segmentProgress - segmentIndex5051 const start = path[segmentIndex]52 const end = path[segmentIndex + 1]5354 return [start[0] + (end[0] - start[0]) * segmentFraction, start[1] + (end[1] - start[1]) * segmentFraction]55}5657const DefaultNavigationMarker = ({ size }: { size: number }) => {58 return (59 <svg width={size} height={size} viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">60 <path d="M24 4L8 40L24 32L40 40L24 4Z" fill="#3b82f6" stroke="white" strokeWidth="2" strokeLinejoin="round" />61 </svg>62 )63}6465export const MapCameraFollow = ({66 path,67 duration = DEFAULT_DURATION,68 zoom = DEFAULT_ZOOM,69 pitch = DEFAULT_PITCH,70 autoStart = true,71 loop = false,72 loopDelay = DEFAULT_LOOP_DELAY,73 marker,74 markerSize = DEFAULT_MARKER_SIZE,75 onComplete,76}: MapCameraFollowProps) => {77 const { map, isLoaded } = useMap()7879 const animationFrameRef = useRef<number | undefined>(undefined)80 const loopTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)81 const startTimeRef = useRef(0)82// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from terrae
All 40 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Map Animated Circleanimated-circle | terrae | Components | Free | 1 dep | |
| Map Animated Footprintanimated-footprint | terrae | Components | Free | 2 deps | |
| Map Animated Polygonanimated-polygon | terrae | Components | Free | 1 dep | |
| Map Animated Pulseanimated-pulse | terrae | Components | Free | 1 dep | |
| Map Arc Animatedarc-animated | terrae | Components | Free | 1 dep | |
| Map Blur Areablur-area | terrae | Components | Free | 1 dep | |
| Map Choroplethchoropleth | terrae | Components | Free | 1 dep | |
| Map Circlecircle | terrae | Components | Free | 1 dep |
