Créer une section about React en split photo/texte
Une section about React en split place une image portrait tall à gauche et le texte mission/vision à droite dans une grille CSS deux colonnes. Framer Motion's whileInView déclenche une animation d'opacité et de glissement horizontal sur chaque colonne à mesure que la section entre dans le viewport.
- Stack : React + Framer Motion + lucide-react, ~173 lignes, zéro dépendance supplémentaire.
- Animation : whileInView avec once:true, durée 0,6 s, courbe de Bézier personnalisée [0.16, 1, 0.3, 1], décalage de 0,1 s entre les colonnes.
- Bloc image : ratio 4/5 fixe, bordures arrondies via token CSS, fond coloré avec la couleur d'accent en placeholder.
- Accessible : hiérarchie sémantique h2 + h3, ancre standard pour le CTA, aucun hack ARIA requis.
- Caveat responsive : la grille deux colonnes est en style inline ; ajoute une media query ou un breakpoint Tailwind pour empiler les colonnes sur mobile.
About Split Photo est une section React qui associe une grande image portrait à un texte mission/vision concis dans une mise en page deux colonnes. Elle ancre la crédibilité d'une marque ou d'une équipe au-dessus de la ligne de flottaison de toute page about, laissant le visuel faire la moitié du travail narratif pendant que le texte reste facile à parcourir.
Anatomie
Une section pleine largeur enveloppe un conteneur centré limité à `--container-max-width`. À l'intérieur, une grille CSS à deux colonnes `1fr 1fr` avec 4rem de gap contient deux blocs animés. Le bloc de gauche est une motion.div avec un ratio 4/5 fixe, des coins arrondis via `--radius-lg`, et un fond coloré avec la couleur d'accent comme placeholder d'image. Le bloc de droite empile un h2 avec un accent italique en serif, deux sous-sections étiquetées (Mission, Vision) avec des titres en capitales tracées, et un lien CTA en forme de pilule avec une icône ArrowRight.
Comment ça marche
Chaque colonne est une `motion.div` qui démarre décalée : la photo commence à `{ opacity: 0, x: -24 }` et le texte à `{ opacity: 0, x: 24 }`. Les deux s'animent vers `{ opacity: 1, x: 0 }` à l'entrée dans le viewport (`whileInView`, `viewport={{ once: true }}`). La courbe de Bézier personnalisée `[0.16, 1, 0.3, 1]` donne au mouvement un démarrage vif et une arrivée douce, donnant l'impression que les colonnes se clipsent en place. Un délai de 0,1 s sur la colonne de texte crée un effet de décalage sans utilitaire d'orchestration tiers.
Comment le coder en React
Mettre en place la grille deux colonnes
Enveloppe le contenu de la section dans un div avec `display: grid`, `gridTemplateColumns: '1fr 1fr'`, `gap: '4rem'` et `alignItems: 'center'`. Utilise les tokens CSS pour le padding et la largeur max afin que la mise en page s'adapte à chaque preset de thème sans surcharges supplémentaires.
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "4rem", alignItems: "center", maxWidth: "var(--container-max-width)", padding: "0 var(--container-padding-x)", margin: "0 auto", }}>Animer la colonne photo à l'entrée dans le viewport
Remplace le div de la photo par une `motion.div`. Définis `initial={{ opacity: 0, x: -24 }}` et `whileInView={{ opacity: 1, x: 0 }}` avec `viewport={{ once: true }}`. Utilise le tableau d'ease personnalisé pour un rendu vif et naturel. Fixe le ratio à 4/5 pour que le bloc reste proportionnel à toute largeur de colonne.
const EASE = [0.16, 1, 0.3, 1] as const; <motion.div initial={{ opacity: 0, x: -24 }} whileInView={{ opacity: 1, x: 0 }} viewport={{ once: true }} transition={{ duration: 0.6, ease: EASE }} style={{ aspectRatio: "4/5", borderRadius: "var(--radius-lg)" }} />Décaler la colonne de texte
Enveloppe le contenu texte dans une deuxième `motion.div` avec `initial={{ opacity: 0, x: 24 }}` (glissement depuis la droite) et un `delay: 0.1` dans la transition. Les 100 ms d'offset suffisent à lire comme un décalage intentionnel sans faire attendre l'utilisateur.
<motion.div initial={{ opacity: 0, x: 24 }} whileInView={{ opacity: 1, x: 0 }} viewport={{ once: true }} transition={{ duration: 0.6, delay: 0.1, ease: EASE }} />Structurer le texte mission/vision
Donne à chaque sous-section un h3 en capitales avec tracking (font-size 0,75 rem, letter-spacing 0,1 em, couleur d'accent) comme étiquette, puis un paragraphe pour le corps du texte. Garde le h2 au-dessus et utilise un span serif italique pour la partie accentuée du titre. Termine par un lien CTA en pilule.
Quand l'utiliser
Utilise cette mise en page sur les pages about d'agences, de studios ou de SaaS où un visuel fort ancre l'histoire de la marque. Elle fonctionne aussi bien en milieu de page comme interlude entre des blocs de fonctionnalités qu'en haut d'une route /about dédiée. Évite-la quand l'image est indisponible ou purement décorative, une section about sans image évite l'embarras du placeholder. Sur mobile, empile toujours les colonnes verticalement pour que la photo ne soit pas écrasée en bande étroite.
Utilisé par
- Stripe, Utilise un split photo/texte large sur sa page About pour ancrer le leadership et la culture d'entreprise.
- Notion, Associe photographie éditoriale et texte axé sur la mission dans une mise en page deux colonnes sur sa page about.
- Loom, Mise en page split avec photo d'équipe à gauche et valeurs à droite, un analogue direct de ce pattern.
FAQ
Comment empiler les deux colonnes sur mobile ?
La grille est définie via des styles inline, donc ajoute une media query CSS ou une classe responsive Tailwind pour passer `gridTemplateColumns` à `1fr` en dessous du breakpoint `md`. Une approche rapide est de remplacer le style inline par une classe Tailwind comme `grid-cols-1 md:grid-cols-2`.
Puis-je remplacer le placeholder par une vraie image ?
Oui. Remplace le div coloré par un `<Image>` Next.js ou une balise `<img>` classique avec `object-fit: cover` et `width: 100%; height: 100%` à l'intérieur de la motion.div. Conserve `aspectRatio: '4/5'` sur le conteneur pour éviter tout saut de mise en page.
Pourquoi l'animation ne se déclenche-t-elle qu'une seule fois ?
`viewport={{ once: true }}` indique à Framer Motion de déclencher whileInView une seule fois et de laisser l'élément dans son état animé. Supprime cette option si tu veux que les colonnes se réinitialisent et se réaniment à chaque passage au scroll.
Comment passer le CTA de rempli à contourné ?
Supprime `background: 'var(--color-accent)'` du lien et ajoute `border: '2px solid var(--color-accent)'` ainsi que `color: 'var(--color-accent)'`. Ajuste l'état hover en conséquence via une classe CSS ou Framer Motion whileHover.