Retour au catalogue

Blog Hover Preview

Liste minimaliste de titres d'articles. Au hover, un preview card (image + extrait) apparait pres du curseur.

blogcomplex Both Responsive a11y
minimaleditorialagencysaasportfoliostacked
Theme

Créer une liste d'articles React avec preview flottant au survol

Un preview au hover en React suit le curseur avec useMotionValue, stocke l'index de la ligne survolée dans un useState, et monte une carte flottante (image + extrait) via AnimatePresence près du pointeur. La carte entre en scale depuis 0.92 et disparaît en fondu quand la ligne perd le focus.

  • Stack : React 18 + Framer Motion 11 + Lucide React, ~160 lignes, zéro dépendance supplémentaire.
  • API clé : useState, useMotionValue, AnimatePresence, motion.a.
  • La carte de preview utilise pointerEvents:none pour ne jamais bloquer les événements souris de la ligne.
  • Accessible : chaque ligne est une vraie balise ancre ; le preview flottant est décoratif et masqué aux lecteurs d'écran.
  • Sur mobile, la liste s'affiche sans preview (pas d'état hover sur écran tactile).

Blog Hover Preview est une liste d'articles minimaliste où chaque ligne survolée fait apparaître une petite carte flottante près du curseur, avec l'image et l'extrait de l'article. Le layout reste propre et typographique au repos ; la carte de preview ajoute de la profondeur sans alourdir l'interface. C'est le pattern des sites éditoriaux et d'agences qui veulent un rendu haut de gamme sans carrousel lourd.

Anatomie

La section se compose de trois parties. Un header animé contient le label de section et un titre serif italique, qui entrent avec une animation opacity/y. En dessous, un conteneur relative accueille les lignes d'articles et la carte de preview flottante. Chaque ligne est une motion.a en flex row : un numéro en police monospace, un titre gras (qui glisse de 12px vers la droite au survol), un tag catégorie, et une icône ArrowUpRight qui pivote de 45 degrés au survol. La carte de preview flottante est une motion.div en absolu positionnée via les coordonnées useMotionValue, contenant une vignette en background-image et un bloc texte avec extrait et date.

Comment ça marche

L'effet repose sur deux motion values (mouseX, mouseY) et un seul état (hoveredIndex). A chaque mousemove sur le conteneur de lignes, getBoundingClientRect convertit les coordonnées du pointeur en valeurs locales au conteneur et les écrit dans mouseX/mouseY. Ces valeurs alimentent directement les propriétés left/top de la carte de preview absolue, ce qui la maintient près du curseur sans re-render. Quand une ligne est survolée, setHoveredIndex se déclenche et AnimatePresence monte la carte avec une entrée scale(0.92) vers scale(1). A la sortie de la ligne, hoveredIndex revient à null et AnimatePresence démonte la carte avec la transition inverse. Le décalage du titre utilise une motion.h3 séparée qui anime sa propriété x entre 0 et 12 selon si la ligne est celle survolée.

Comment le coder en React

  1. Initialise les motion values et l'index survolé

    Crée mouseX et mouseY avec useMotionValue(0) et un state hoveredIndex initialisé à null. Sur le div conteneur des lignes, attache un handler onMouseMove qui lit la position du pointeur relativement au conteneur et l'écrit dans les deux motion values.

    const mouseX = useMotionValue(0);
    const mouseY = useMotionValue(0);
    const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
    
    const handleMouseMove = (e: React.MouseEvent) => {
      const rect = e.currentTarget.getBoundingClientRect();
      mouseX.set(e.clientX - rect.left + 24);
      mouseY.set(e.clientY - rect.top - 160);
    };
  2. Construis la ligne d'article avec l'état hover

    Chaque ligne est une motion.a. Au mouseEnter écris l'index de la ligne dans hoveredIndex, au mouseLeave remet à null. A l'intérieur, entoure le titre d'une motion.h3 et anime son x entre 0 et 12 selon si l'index correspond à hoveredIndex. Fais de même pour la rotation de l'icône ArrowUpRight.

    <motion.h3
      animate={{ x: hoveredIndex === i ? 12 : 0 }}
      transition={{ duration: 0.3, ease: EASE }}
    >
      {article.title}
    </motion.h3>
  3. Monte la carte flottante avec AnimatePresence

    Dans le conteneur relative, après les lignes, ajoute un bloc AnimatePresence. Quand hoveredIndex n'est pas null, rends une motion.div positionnée en absolu avec style left et top liés à mouseX et mouseY. Donne-lui un scale initial de 0.92 et une opacité 0, anime vers scale 1 et opacité 1, et mets pointerEvents à none pour ne pas interférer avec les lignes en dessous.

    <AnimatePresence>
      {hoveredIndex !== null && (
        <motion.div
          initial={{ opacity: 0, scale: 0.92 }}
          animate={{ opacity: 1, scale: 1 }}
          exit={{ opacity: 0, scale: 0.92 }}
          transition={{ duration: 0.2 }}
          style={{
            position: "absolute",
            left: mouseX,
            top: mouseY,
            pointerEvents: "none",
            zIndex: 40,
          }}
        />
      )}
    </AnimatePresence>
  4. Remplis la carte avec l'image et l'extrait

    Dans la motion.div, rends un div de hauteur fixe avec backgroundImage réglé sur l'URL d'image de l'article survolé, suivi d'un bloc avec padding affichant l'extrait et la date. Garde la largeur de la carte autour de 280px et ajoute un box-shadow pour la profondeur. La carte reflètera toujours l'article survolé en cours car elle lit depuis articles[hoveredIndex].

Quand l'utiliser

Utilise ce pattern sur les sections blog d'agences, portfolios ou SaaS où tu veux que la liste d'articles soit éditoriale et premium. Il fonctionne mieux avec 4 à 8 articles, chacun avec une image soignée. Evite-le si les articles manquent d'images de qualité, si ton audience est majoritairement mobile (le hover ne se déclenche pas sur tactile), ou si la liste est assez longue pour qu'une grille ou un layout carte offre une meilleure lisibilité.

Utilisé par

  • Stripe, Utilise une liste d'articles empilée minimaliste avec hiérarchie typographique et interactions subtiles sur les lignes dans son blog d'ingénierie.
  • Vercel, Listing d'articles propre avec highlights au survol et métadonnées de preview enrichies apparaissant à l'interaction sur la ligne.
  • Linear, Utilise des listes d'articles en lignes avec des transitions d'état au survol qui font remonter du contexte supplémentaire près du pointeur.

FAQ

Le preview au hover fonctionne-t-il sur mobile ?

Non. Les écrans tactiles ne déclenchent pas les événements hover ou mousemove, donc la carte flottante n'apparaît jamais. La liste d'articles est entièrement fonctionnelle sur mobile ; seul le preview décoratif est absent.

Pourquoi utiliser useMotionValue plutôt que useState pour la position du curseur ?

useMotionValue met à jour le style DOM directement sans déclencher un re-render React à chaque événement mousemove. Avec useState, un curseur rapide empilement des dizaines de renders par seconde et ferait tressauter la carte ; useMotionValue maintient la carte calée sur le curseur à 60fps.

Comment empêcher la carte de déborder du bord du viewport ?

Ajoute un clamp dans handleMouseMove : plafonne mouseX pour que x + largeur de la carte ne dépasse jamais la largeur du conteneur, et plafonne mouseY symétriquement. Un simple Math.min sur les deux axes suffit dans la plupart des cas.

Peut-on remplacer l'image par un aperçu vidéo ?

Oui. Remplace le div en background-image par un élément vidéo muet en lecture automatique. Associe-le à une prop key égale à l'URL de l'article pour que React démonte et remonte l'élément vidéo à chaque changement de ligne survolée, relançant la lecture depuis le début.

"use client";

import { useState } from "react";
import { motion, AnimatePresence, useMotionValue } from "framer-motion";
import { ArrowUpRight } from "lucide-react";

interface Article {
  title: string;
  excerpt: string;
  category: string;
  date: string;
  image: string;
  url: string;
}

interface BlogHoverPreviewProps {
  heading?: string;
  subtitle?: string;
  articles?: Article[];
}

const EASE = [0.16, 1, 0.3, 1] as const;

Code complet réservé à Pro

Code source intégral, export multi-framework et playground.

Passer en Pro, 9,99€/mois

Avis

Liste d'articles React avec carte de preview au hover, Code