How to build a globe contact section in React with SVG and Framer Motion
A React SVG globe contact section renders latitude ellipses and longitude ellipses as dashed paths, then plots office pins as concentric circles from lat/lng coordinates mapped to SVG space. Framer Motion's whileInView drives every element in on scroll, and a slow conic-gradient div rotates continuously at 60s to add depth without JavaScript physics.
- Stack: React + Framer Motion 11 + lucide-react, ~155 lines total, zero canvas or third-party globe library.
- Globe drawn in pure SVG: 5 latitude ellipses, 6 longitude ellipses, office circles animated with scale keyframes.
- pathLength animation traces each grid line from 0 to 1 on scroll entry, staggered by delay.
- Accessible: all text is in the DOM (SVG <text> elements and the right panel), not baked into an image.
- On narrow screens the two-column grid stacks vertically; the globe stays 320px square, centred.
Contact Globe is a split-layout React section that pairs an animated SVG globe on the left with a heading, description, and icon-labelled contact info on the right. It signals global presence without importing a map library or canvas renderer, the whole globe is a handful of SVG ellipses, a conic-gradient shimmer, and some Framer Motion scroll triggers.
Anatomy
The section is a two-column CSS grid (1fr 1fr, 4rem gap). The left column holds the Globe sub-component: a 320px SVG that draws the outer circle, dashed latitude ellipses, dashed longitude ellipses, and per-office marker groups (a large faint pulse circle plus a solid dot plus a city label). A rotating conic-gradient div sits absolutely over the SVG with pointer-events:none for the glow effect. The right column stacks the eyebrow label, an italic serif h2, a paragraph, and a vertical list of contact rows, each composed of an icon box and two text lines.
How it works
Every animated element uses whileInView so it fires once as the section scrolls into the viewport. The grid lines use Framer Motion's pathLength property, each ellipse starts at pathLength:0 and animates to pathLength:1, so the lines draw themselves on entry. Office dots use a scale keyframe sequence [0, 1.6, 1] to produce a small elastic pop. Contact rows slide in from x:20 with an incremental delay (0.2 + i * 0.08s). The conic-gradient rotation is a separate `animate={{ rotate: 360 }}` with `repeat: Infinity` and ease:"linear" at 60s, giving a slow ambient shimmer without impacting scroll performance.
How to build it in React
Draw the globe grid in SVG
Create a 320px SVG. Draw the outer border circle, then map over an array of latitudes (-60, -30, 0, 30, 60) to produce ellipses whose vertical radius is (lat/90)*r and whose horizontal radius uses cosine projection. Do the same for longitudes with sine projection. Use strokeDasharray="3 6" for the grid texture.
const latLines = [-60, -30, 0, 30, 60]; latLines.map((lat) => { const y = cy - (lat / 90) * r; const rx = Math.cos((lat * Math.PI) / 180) * r; return <ellipse cx={cx} cy={y} rx={rx} ry={rx * 0.15} strokeDasharray="3 6" />; })Animate grid lines on scroll with pathLength
Wrap each ellipse in a motion.ellipse. Set initial={{ pathLength: 0 }} and whileInView={{ pathLength: 1 }} with viewport={{ once: true }}. Stagger latitude lines at delay:0.2 and longitude lines at delay:0.4 so the grid draws itself layer by layer.
<motion.ellipse initial={{ pathLength: 0 }} whileInView={{ pathLength: 1 }} viewport={{ once: true }} transition={{ duration: 1.2, delay: 0.2, ease: [0.16, 1, 0.3, 1] }} />Place office pins from lat/lng coordinates
Map each office to SVG x/y using a linear projection scaled by 0.7 to keep pins inside the sphere. Render two circles per pin: a large faint one with scale keyframes [0, 1.6, 1] for the elastic pulse, and a small solid one. Add a motion.text above for the city label.
const x = cx + (office.lng / 180) * r * 0.7; const y = cy - (office.lat / 90) * r * 0.7; // pulse circle <motion.circle r={12} opacity={0.15} whileInView={{ scale: [0, 1.6, 1] }} transition={{ duration: 0.8, delay: 0.6 + i * 0.15 }} /> // solid dot <motion.circle r={4} whileInView={{ scale: 1 }} initial={{ scale: 0 }} />Add the ambient conic-gradient shimmer
Position a div absolutely over the SVG with borderRadius:"50%" and a conic-gradient that runs from transparent to the accent colour and back. Set opacity to 0.06 and pointer-events:none, then animate rotate from 0 to 360 with repeat:Infinity and ease:"linear" at 60s. It adds a subtle light sweep without any canvas or WebGL.
<motion.div animate={{ rotate: 360 }} transition={{ duration: 60, repeat: Infinity, ease: "linear" }} style={{ position: "absolute", inset: 0, borderRadius: "50%", background: "conic-gradient(from 0deg, transparent 0%, var(--color-accent) 5%, transparent 10%)", opacity: 0.06, pointerEvents: "none", }} />
When to use it
Reach for this section when a product or agency has multiple offices and wants to communicate global reach without a heavy map embed. It works well as the primary contact block on an About or Contact page, paired with a form or a CTA below. Avoid it when the company is single-location or when page weight is a concern, even though SVG is lightweight, the animation budget is non-trivial on low-end devices. On touch screens the layout stacks correctly, but the globe is purely visual with no interactivity.
Used by
- Stripe, Uses globe-style visualisations on its careers and infrastructure pages to illustrate worldwide coverage of its payments network.
- Cloudflare, Displays an animated network globe as a hero visual on its homepage and CDN product pages to convey edge presence across continents.
- Vercel, Features an edge-network globe on its infrastructure marketing pages, showing deployment locations as glowing dots on a dark sphere.
- Miro, Uses world-map and globe visuals on its Enterprise and About pages to reinforce the message of a globally distributed team and customer base.
FAQ
Does this use a real map projection?
No. It uses a simple linear mapping: longitude maps to x and latitude maps to y, both scaled by 0.7r to stay inside the sphere. Real geographic accuracy would need a proper equirectangular or Mercator projection, but for a contact section the approximation is indistinguishable at a glance.
Can I replace the SVG globe with a canvas or Three.js globe?
Yes, the Globe sub-component is self-contained and receives only an offices array. Swap it for any renderer you like. The right panel is independent and will not change.
Why does each grid line animate separately instead of the whole globe fading in?
The staggered pathLength draw reveals the globe structure progressively, which reads as the globe being constructed in real time. A single fade-in loses that storytelling quality. The delays are intentionally short (0.2-0.4s) so it doesn't feel slow.
How do I make the layout responsive?
The current grid is set with gridTemplateColumns:"1fr 1fr" inline. To stack on mobile, switch to a media query or a Tailwind class like `grid-cols-1 md:grid-cols-2`. The Globe component is already centred with margin:"0 auto" so it sits correctly in a single column.