Retour au catalogue

Social Proof Animated Logos

Logos clients qui se dessinent au scroll via SVG path animation (strokeDasharray/strokeDashoffset). Chaque logo s'anime quand visible avec useInView et stagger.

social-proofmedium Both Responsive a11y
minimalelegantcorporatesaasagencyuniversalcentered
Theme
"use client";

import { useRef } from "react";
import { motion, useInView } from "framer-motion";

interface LogoShape {
  name: string;
  path: string;
  viewBox?: string;
}

interface SocialProofAnimatedLogosProps {
  title?: string;
  subtitle?: string;
  logos?: LogoShape[];
}

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

function AnimatedLogo({ logo, delay }: { logo: LogoShape; delay: number }) {
  const ref = useRef<HTMLDivElement>(null);
  const inView = useInView(ref, { once: true, margin: "-40px" });

  return (
    <motion.div
      ref={ref}
      initial={{ opacity: 0, y: 16 }}
      animate={inView ? { opacity: 1, y: 0 } : {}}
      transition={{ duration: 0.6, delay, ease: EASE }}
      style={{
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        gap: "0.75rem",
      }}
    >
      <div
        style={{
          width: "80px",
          height: "80px",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          borderRadius: "var(--radius-lg, 12px)",
          background: "var(--color-background-card, var(--color-background-alt))",
          border: "1px solid var(--color-border)",
          transition: "border-color var(--duration-normal) var(--ease-out)",
        }}
      >
        <svg
          viewBox={logo.viewBox ?? "0 0 24 24"}
          fill="none"
          style={{ width: "36px", height: "36px" }}
        >
          <motion.path
            d={logo.path}
            stroke="var(--color-foreground-muted)"
            strokeWidth={1.5}
            strokeLinecap="round"
            strokeLinejoin="round"
            initial={{ pathLength: 0, opacity: 0 }}
            animate={inView ? { pathLength: 1, opacity: 1 } : {}}
            transition={{
              pathLength: { duration: 1.4, delay: delay + 0.2, ease: "easeInOut" },
              opacity: { duration: 0.3, delay: delay + 0.1 },
            }}
          />
        </svg>
      </div>
      <span
        style={{
          fontSize: "0.75rem",
          fontWeight: 500,
          color: "var(--color-foreground-muted)",
          letterSpacing: "0.02em",
        }}
      >
        {logo.name}
      </span>
    </motion.div>
  );
}

export default function SocialProofAnimatedLogos({
  title = "Preuve sociale",
  subtitle = "",
  logos = [],
}: SocialProofAnimatedLogosProps) {
  const ref = useRef<HTMLElement>(null);
  const inView = useInView(ref, { once: true, margin: "-60px" });

  return (
    <section
      ref={ref}
      style={{
        padding: "var(--section-padding-y) 0",
        background: "var(--color-background)",
      }}
    >
      <div
        style={{
          maxWidth: "var(--container-max-width)",
          margin: "0 auto",
          padding: "0 var(--container-padding-x)",
          textAlign: "center",
        }}
      >
        {subtitle && (
          <motion.p
            initial={{ opacity: 0 }}
            animate={inView ? { opacity: 1 } : {}}
            transition={{ duration: 0.4, ease: EASE }}
            style={{
              fontSize: "0.75rem",
              fontWeight: 600,
              textTransform: "uppercase",
              letterSpacing: "0.1em",
              color: "var(--color-accent)",
              marginBottom: "0.75rem",
            }}
          >
            {subtitle}
          </motion.p>
        )}

        <motion.h2
          initial={{ opacity: 0, y: 16 }}
          animate={inView ? { opacity: 1, y: 0 } : {}}
          transition={{ duration: 0.5, delay: 0.05, ease: EASE }}
          style={{
            fontFamily: "var(--font-sans)",
            fontSize: "clamp(1.5rem, 2.5vw, 2rem)",
            fontWeight: 700,
            color: "var(--color-foreground)",
            marginBottom: "3rem",
            letterSpacing: "-0.02em",
          }}
        >
          {title}
        </motion.h2>

        <div
          style={{
            display: "flex",
            flexWrap: "wrap",
            justifyContent: "center",
            gap: "2rem",
          }}
        >
          {logos.map((logo, i) => (
            <AnimatedLogo key={logo.name} logo={logo} delay={0.08 * i} />
          ))}
        </div>
      </div>
    </section>
  );
}

Avis

Social Proof Animated Logos — React Social-proof Section — Incubator