Méschac Irung

Logo Cloud

Component 112

About

Here is Logo Cloud Blocks

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Logo Cloud to your project and fits it to your theme.

Code

How Logo Cloud is used: the demo's code. The component's own source comes with a free account.

Usage.tsx


'use client';

import { animate, motion, useMotionValue } from 'motion/react';
import React, { CSSProperties, useEffect, useState } from 'react';
import useMeasure from 'react-use-measure';


import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}



type InfiniteSliderProps = {
    children: React.ReactNode;
    gap?: number;
    speed?: number;
    speedOnHover?: number;
    direction?: 'horizontal' | 'vertical';
    reverse?: boolean;
    className?: string;
};

function InfiniteSlider({
    children,
    gap = 16,
    speed = 100,
    speedOnHover,
    direction = 'horizontal',
    reverse = false,
    className,
}: InfiniteSliderProps) {
    const [currentSpeed, setCurrentSpeed] = useState(speed);
    const [ref, { width, height }] = useMeasure();
    const translation = useMotionValue(0);
    const [isTransitioning, setIsTransitioning] = useState(false);
    const [key, setKey] = useState(0);

    useEffect(() => {
        let controls;
        const size = direction === 'horizontal' ? width : height;
        if (size === 0) return;

        const contentSize = size + gap;
        const from = reverse ? -contentSize / 2 : 0;
        const to = reverse ? 0 : -contentSize / 2;

        const distanceToTravel = Math.abs(to - from);
        const duration = distanceToTravel / currentSpeed;

        if (isTransitioning) {
            const remainingDistance = Math.abs(translation.get() - to);
            const transitionDuration = remainingDistance / currentSpeed;
            controls = animate(translation, [translation.get(), to], {
                ease: 'linear',
                duration: transitionDuration,
                onComplete: () => {
                    setIsTransitioning(false);
                    setKey((prevKey) => prevKey + 1);
                },
            });
        } else {
            controls = animate(translation, [from, to], {
                ease: 'linear',
                duration: duration,
                repeat: Infinity,
                repeatType: 'loop',
                repeatDelay: 0,
                onRepeat: () => {
                    translation.set(from);
                },
            });
        }

        return () => controls?.stop();
    }, [key, translation, currentSpeed, width, height, gap, isTransitioning, direction, reverse]);

    const hoverProps = speedOnHover
        ? {
              onHoverStart: () => {
                  setIsTransitioning(true);
                  setCurrentSpeed(speedOnHover);
              },
              onHoverEnd: () => {
                  setIsTransitioning(true);
                  setCurrentSpeed(speed);
              },
          }
        : {};

    return (
        <div className={cn('overflow-hidden', className)}>
            <motion.div
                className="flex w-max"
                style={{
                    ...(direction === 'horizontal' ? { x: translation } : { y: translation }),
                    gap: `${gap}px`,
                    flexDirection: direction === 'horizontal' ? 'row' : 'column',
                }}
                ref={ref}
                {...hoverProps}>
                {children}
                {children}
            </motion.div>
        </div>
    );
}


export type BlurredInfiniteSliderProps = InfiniteSliderProps & {
    fadeWidth?: number;
    containerClassName?: string;
};

export function BlurredInfiniteSlider({
    children,
    fadeWidth = 80,
    containerClassName,
    ...sliderProps
}: BlurredInfiniteSliderProps) {

    const maskStyle: CSSProperties = {
        maskImage: `linear-gradient(to right, transparent, black ${fadeWidth}px, black calc(100% - ${fadeWidth}px), transparent)`,
        WebkitMaskImage: `linear-gradient(to right, transparent, black ${fadeWidth}px, black calc(100% - ${fadeWidth}px), transparent)`,
    };

    return (
        <div
            className={cn('relative w-full', containerClassName)}
            style={maskStyle}
        >
            <InfiniteSlider {...sliderProps}>{children}</InfiniteSlider>
        </div>
    );
}


const LOGOS = [
    { src: "https://cdn.21st.dev/larsen66/logo-cloud/assets/openai.1785940836015.svg", alt: "OpenAI Logo", height: 24 },
    { src: "https://cdn.21st.dev/larsen66/logo-cloud/assets/nvidia.1785940836015.svg", alt: "Nvidia Logo", height: 20 },
    { src: "https://cdn.21st.dev/larsen66/logo-cloud/assets/column.1785940836015.svg", alt: "Column Logo", height: 16 },
    { src: "https://cdn.21st.dev/larsen66/logo-cloud/assets/github.1785940836015.svg", alt: "GitHub Logo", height: 16 },
    { src: "https://cdn.21st.dev/larsen66/logo-cloud/assets/nike.1785940836015.svg", alt: "Nike Logo", height: 20 },
    { src: "https://cdn.21st.dev/larsen66/logo-cloud/assets/lemonsqueezy.1785940836015.svg", alt: "Lemon Squeezy Logo", height: 20 },
    { src: "https://cdn.21st.dev/larsen66/logo-cloud/assets/laravel.1785940836015.svg", alt: "Laravel Logo", height: 16 },
    { src: "https://cdn.21st.dev/larsen66/logo-cloud/assets/lilly.1785940836015.svg", alt: "Lilly Logo", height: 28 },
];

export default function LogoCloudDemoPage() {
    return (
        <main className="min-h-screen w-full flex items-center justify-center bg-white dark:bg-black text-black dark:text-white">
            <section className="bg-background overflow-hidden py-16 w-full">
                <div className="m-auto max-w-7xl px-6">
                    {/* Эта структура отвечает за адаптивную верстку */}
                    <div className="flex flex-col items-center md:flex-row">
                        {/* На экранах 'md' и шире этот блок будет слева с линией */}
                        <div className="flex-shrink-0 text-center md:text-right md:max-w-44 md:border-r md:border-gray-200 dark:md:border-gray-800 md:pr-6">
                            <p className="text-sm text-gray-600 dark:text-gray-400">
                                Powering the best teams
                            </p>
                        </div>
                        {/* На маленьких экранах этот блок будет под текстом */}
                        <div className="w-full py-6 md:w-auto md:flex-1">
                            <BlurredInfiniteSlider
                                speedOnHover={20}
                                speed={40}
                                gap={112}
                                fadeWidth={80} // Ширина затухания по краям
                            >
                                {LOGOS.map((logo) => (
                                    <div key={logo.src} className="flex">
                                        <img
                                            className="mx-auto w-fit dark:invert"
                                            src={logo.src}
                                            alt={logo.alt}
                                            style={{ height: `${logo.height}px` }}
                                            width="auto"
                                        />
                                    </div>
                                ))}
                            </BlurredInfiniteSlider>
                        </div>
                    </div>
                </div>
            </section>
        </main>
    );
}

More by Méschac Irung

View all
  • Features 8

    Features 8

    3.1K
  • Hero Section 1

    Hero Section 1

    3K
  • Hero Section 5

    Hero Section 5

    2.6K
  • Hero Section 6

    Hero Section 6

    883
  • Hero Section 2

    Hero Section 2

    876
  • Hero Section 4

    Hero Section 4

    626
  • Features 9

    Features 9

    594
  • Hero Section 3

    Hero Section 3

    580