Créer une section contact avec carte intégrée en React
Une section contact React avec carte intégrée affiche une iframe Google Maps dans un conteneur arrondi, puis présente adresse, email et téléphone dans une barre d'infos sous la carte. Framer Motion anime les deux blocs à l'entrée dans le viewport au scroll.
- Stack : React + Framer Motion + lucide-react + Tailwind v4, ~170 lignes, zéro dépendance ajoutée.
- L'iframe a loading='lazy' et referrerPolicy='no-referrer-when-downgrade' pour la performance et la vie privée.
- Toutes les couleurs viennent de propriétés CSS custom (--color-background, --color-accent, etc.), aucune valeur codée en dur.
- Accessible : l'iframe a un attribut title ; email et téléphone sont de vrais liens anchor (mailto:/tel:).
- Responsive par défaut, la carte passe de h-80 à h-96 au breakpoint lg.
Contact Map Integrated associe une carte intégrée pleine largeur à une barre compacte affichant adresse, email et téléphone en une seule ligne lisible. Les deux blocs glissent à l'entrée dans le viewport via Framer Motion, le second décalé de 100 ms. Adapté à toute marque physique qui veut que les visiteurs la trouvent vite, restaurants, studios, cabinets.
Anatomie
La section contient deux motion.div dans un conteneur max-w-6xl. Le premier accueille le badge, le h2 et le subtitle optionnel. Le second est un div arrondi avec une bordure de 1 px qui encapsule deux enfants : la zone carte (une iframe ou l'icône MapPin de fallback si aucune URL n'est fournie) et la barre d'infos en dessous, qui utilise flex-wrap pour les items de contact et pousse le lien CTA à droite avec ml-auto.
Comment ça marche
Les deux blocs utilisent le même pattern Framer Motion : initial={{ opacity: 0, y: 20 }}, whileInView={{ opacity: 1, y: 0 }}, viewport={{ once: true }}. La courbe cubic-bezier [0.16, 1, 0.3, 1] donne à chaque bloc une montée rapide qui décélère doucement, plus incisive que l'easeOut standard et proche d'un spring sans passer par useSpring. Le second bloc ajoute delay: 0.1 pour un léger décalage. L'iframe gère son propre rendu cartographique ; React ne gère que le conteneur et l'interface alentour.
Comment le coder en React
Créer l'enveloppe de section et le bloc en-tête
Enveloppe tout dans une balise section avec un token CSS pour le fond. Dans un conteneur max-w-6xl, place une motion.div qui monte au scroll pour le badge, le h2 et le subtitle. Ce bloc s'anime indépendamment de la carte, ce qui laisse le temps à l'en-tête d'apparaître avant que la carte suive.
const ease: [number, number, number, number] = [0.16, 1, 0.3, 1]; <motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} transition={{ duration: 0.6, ease }} viewport={{ once: true }} > {badge && <span className="uppercase tracking-widest text-xs">{badge}</span>} <h2>{title}</h2> </motion.div>Intégrer l'iframe de carte avec un fallback
Rends l'iframe si la prop mapEmbedUrl est renseignée ; affiche sinon une icône MapPin centrée. Mets loading='lazy' pour différer l'iframe jusqu'à son entrée dans le viewport. L'attribut title de l'iframe est obligatoire pour l'accessibilité, les lecteurs d'écran l'annoncent comme landmark.
{mapEmbedUrl ? ( <iframe src={mapEmbedUrl} className="w-full h-full" style={{ border: 0 }} allowFullScreen loading="lazy" referrerPolicy="no-referrer-when-downgrade" title="Carte" /> ) : ( <div className="flex items-center justify-center h-full"> <MapPin size={40} /> </div> )}Construire la barre d'informations de contact
Sous la carte, ajoute une ligne flex-wrap avec les trois items de contact (adresse, email, téléphone) rendus conditionnellement. Chaque item associe une icône lucide à son texte ou lien anchor. Pousse le lien d'itinéraire CTA à droite avec ml-auto. Les liens téléphone utilisent tel: après suppression des espaces ; les emails utilisent mailto:.
<a href={`tel:${phone.replace(/\s/g, "")}`} className="text-sm hover:opacity-70"> {phone} </a> <a href={ctaUrl} target="_blank" rel="noopener noreferrer" className="ml-auto flex items-center gap-2"> {ctaLabel} <ExternalLink size={14} /> </a>Décaler le bloc carte de 100 ms
Encapsule le conteneur de carte arrondi dans une seconde motion.div avec la même config initial/whileInView, mais ajoute delay: 0.1 à la transition. Cela crée une révélation en deux temps, l'en-tête se pose en premier, la carte suit, sans orchestration complexe.
<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} transition={{ duration: 0.6, ease, delay: 0.1 }} viewport={{ once: true }} className="rounded-xl overflow-hidden" > {/* map + info bar */} </motion.div>
Quand l'utiliser
Utilise cette section quand la localisation physique est un signal de conversion clé, restaurants, salons, cabinets, espaces de co-working, tout service qui dépend du passage. Place-la en bas de page, après les témoignages ou la FAQ, là où les visiteurs déjà convaincus viennent confirmer l'adresse. Passe-la pour les services 100 % distanciels, les produits SaaS ou les pages où un formulaire de contact suffit.
Utilisé par
- Airbnb, Les pages d'annonces combinent une carte intégrée avec des barres d'infos de style contact affichant les détails de l'hôte et la localisation.
- OpenTable, Les pages de profil de restaurant affichent une carte interactive avec adresse, téléphone et lien d'itinéraire dans un layout empilé.
- Doctolib, Les pages praticien affichent la localisation du cabinet sur une carte à côté d'une bande d'infos avec adresse et téléphone.
- WeWork, Les pages de site intègrent des cartes au-dessus d'une barre résumant les coordonnées et l'itinéraire de chaque espace.
FAQ
Comment obtenir une URL d'intégration Google Maps ?
Ouvre Google Maps, cherche le lieu, clique sur Partager, choisis Intégrer une carte, copie la valeur src dans l'extrait iframe. Colle-la dans la prop mapEmbedUrl. L'API Maps Embed est gratuite pour cet usage, mais nécessite une clé API navigateur pour les sites à fort trafic.
Peut-on remplacer Google Maps par OpenStreetMap ?
Oui, remplace le src de l'iframe par une URL d'intégration OpenStreetMap (ex. https://www.openstreetmap.org/export/embed.html?bbox=...). Le composant affiche simplement ce que le src indique ; aucun code Google ne vit dans la couche React.
La carte impacte-t-elle les performances de la page ?
L'iframe utilise loading='lazy' pour que le navigateur diffère le chargement de la carte jusqu'à ce que la section soit proche du viewport. Pour les pages avec un budget Core Web Vitals strict, remplace l'iframe par une image de carte statique qui ouvre Google Maps au clic, cela évite tout chargement de script tiers.
Comment masquer la carte sur mobile et n'afficher que les contacts ?
Ajoute une classe Tailwind comme 'hidden sm:block' sur le div conteneur de l'iframe. La barre d'infos en dessous est indépendante du layout et s'affiche bien seule à toute largeur de viewport.