About this animation
A VPN status panel with a rotating globe and rolling session timer, with configurable locations, status labels, colors, and fonts. Your app supplies the connection state; this is presentation UI rather than a VPN client.
Why this one
Connecting becomes visible: points travel from the world into the button, then a 550 ms light pulse follows the horizon when the connection settles. A location change rotates the globe and starts a fresh connection. The world-to-button flow and the horizon pulse add two visual layers to a conventional connection switch.
How it works
- A measured, scrollable layout keeps text and controls in flow, caps the panel width at 420 pt, and sizes the globe from its container.
- Latitude/longitude samples project onto a sphere. A deterministic noise-band mask distinguishes stylized land from ocean; it is decorative geography, not a map.
- Two
Pointsbatches draw all globe dots in one Canvas. Shared values drive rotation, particles, the continuous ring, and horizon light on the UI thread. - The uncontrolled machine moves from disconnected to connecting to connected. A second tap cancels or disconnects; changing locations requests a fresh connection.
- Each timer digit animates independently to an exact integer. Separators own their widths; accessibility text updates only once per elapsed second.
- The demo runs: 1 s idle → 1.6 s connect → 3 s connected → Tokyo/reconnect → disconnect → repeat. Any touch stops automation.
Install
npx expo install @shopify/react-native-skia expo-blur expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-workletsUse a GestureHandlerRootView in your host app. On web, initialize CanvasKit before rendering this component (Expo/Skia's LoadSkiaWeb or equivalent host loader). The component has no external assets or catalog imports. Icons: Lucide (ISC), copied from the Native Animation kit. The demo uses the kit's Nimbus cloud tile, never an invented arrow logo.
Usage
import { VpnConnectGlobe } from './vpn-connect-globe';
<VpnConnectGlobe
locations={[
{ id: 'lisbon', label: 'Lisbon · PT', longitude: -9.14 },
{ id: 'tokyo', label: 'Tokyo · JP', longitude: 139.69 },
]}
onConnect={() => requestConnection()}
onDisconnect={() => closeConnection()}
/>Customize or manage actual connection results from your host:
const connection = useRef<VpnConnectGlobeRef>(null);
<VpnConnectGlobe
ref={connection}
status={networkStatus}
location={selectedLocationId}
locations={availableLocations}
onLocationChange={item => setSelectedLocationId(item.id)}
onConnect={connectNetwork}
onDisconnect={disconnectNetwork}
connectDuration={1600}
rotationSpeed={0.025}
colors={{ accent: '#A493FF', land: '#D0C7FF', muted: '#A5A9CD' }}
fontFamily="YourRegularFont"
semiboldFontFamily="YourSemiboldFont"
monoFontFamily="YourMonoFont"
/>Import VpnConnectGlobeRef from the same folder. Its methods are connect(), disconnect(), and selectLocation(id). In controlled mode, these methods and taps request changes through callbacks; the host must update status and location. Controlled connecting stays connecting until the host reports a result. In uncontrolled mode, completion is simulated after connectDuration.
Props
| Prop | Type | Default | Purpose |
|---|---|---|---|
status | VpnConnectionStatus | uncontrolled | disconnected, connecting, or connected |
onConnect, onDisconnect | () => void | — | Connection requests; cancel also requests disconnect |
onStatusChange | (status) => void | — | Visual state changes |
locations | readonly VpnLocation[] | Nearby · US | IDs, display labels, longitudes, optional dot colors |
location, onLocationChange | string, (item) => void | uncontrolled, — | Controlled selection; tapping cycles locations |
colors | Partial<VpnGlobeColors> | night palette | Background/end, surface, button, text, muted, border, accent, ocean, land, brand start/end |
title, subtitle | string | Browse from anywhere, / privately | Heading and dimmed continuation |
brand | string | Private network | Wordmark |
connectionLabel | string | Secure connection | Timer caption |
statusLabels | Record<VpnConnectionStatus, string> | Connect / Connecting… / Connected | Visible and announced states |
footer | string | Your connection. Your world. | Supporting copy |
settingsLabel, favoriteLabel, locationLabel | string | Connection settings / Favorite connection / Change location | Accessible labels |
onSettings, onFavorite | () => void | — | Optional actions; otherwise their icons are decorative |
onInteraction | () => void | — | User touch/accessibility activation; stop host automation |
fontFamily, semiboldFontFamily, monoFontFamily | string | system | Host-loaded text fonts |
connectDuration | number | 1600 | Connection duration in ms (minimum 200) |
initialSeconds | number | 0 | Initial elapsed time for an initially connected state |
rotationSpeed | number | 0.035 | Ambient radians per second |
style | StyleProp<ViewStyle> | — | Root overrides; defaults to flex 1 |
testID | string | vpn-connect-globe | Root; controls append -connect, -location, -settings, -favorite |
Compatibility
Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Worklets 0.13.0, and Skia 2.14.0. Uses modules available in this project's Expo Go. Android glass uses BlurTargetView and the SDK 31+ blur method; older Android versions keep the tinted surface fallback. Web requires host CanvasKit initialization.
The host supplies safe-area padding. Width is capped; short containers and large type can scroll instead of hiding controls. The intended layout covers 320×568, 390×844, 430×932, tablet, landscape, and a 300×400 embedded panel.
Accessibility
Interactive elements expose button roles, labels, selected state, activate actions, and at least 48 pt touch areas. Major status changes are announced. The timer has a single spoken value; its visual digit layers are excluded from accessibility. All important text is configurable.
Reduced motion removes ambient rotation, spark flights, horizon sweeps, and digit travel. State content fades over 200 ms; timer digits crossfade. No dragging is required: tap to connect/disconnect or cycle locations.




