动效与背景 · 自然有机与图像特效 · 公开
Great UI Diagonal Marquee Carousel
将多排风景卡片以对角线角度持续无缝滚动,交替方向和错开的速度形成层次感,并以顶部、底部柔和渐变自然收边。
#React#TypeScript#Tailwind CSS#Carousel#Marquee#CSS 动画
Prompt 原文6,656 字符
You are given a task to integrate an existing React component in the codebase
The codebase should support:
- shadcn project structure
- Tailwind CSS
- Typescript
If it doesn't, provide instructions on how to setup project via shadcn CLI, install Tailwind or Typescript.
Determine the default path for components and styles.
If default path for components is not /components/ui, provide instructions on why it's important to create this folder
Copy-paste this component to /components/ui folder:
```tsx
great-ui-diagonal-marquee-carousel.tsx
"use client";
import React from "react";
import { cn } from "@/lib/utils";
export interface CardItem {
id: string | number;
url: string;
title: string;
}
export interface DiagonalMarqueeCarouselProps {
cards?: CardItem[];
angle?: number;
baseSpeed?: number;
alternateDirections?: boolean;
className?: string;
cardClassName?: string;
fadeClassName?: string;
}
const DEFAULT_CARDS: CardItem[] = [
{
id: 2,
url: "https://ik.imagekit.io/ybq4azred/landscape_mountain_1784924486724.png",
title: "Landscape",
},
{
id: 3,
url: "https://ik.imagekit.io/ybq4azred/nature_sunlight_1784924506267.png",
title: "Nature",
},
{
id: 4,
url: "https://ik.imagekit.io/ybq4azred/forest_autumn_1784924537778.png",
title: "Forest",
},
{
id: 5,
url: "https://ik.imagekit.io/ybq4azred/forest_bridge_1784924559843.png",
title: "Bridge",
},
{
id: 6,
url: "https://ik.imagekit.io/ybq4azred/ocean_sunset_1784924582456.png",
title: "Ocean",
},
{
id: 7,
url: "https://ik.imagekit.io/ybq4azred/valley_aerial_1784924609773.png",
title: "Valley",
},
];
const Card = ({ card, className }: { card: CardItem; className?: string }) => {
return (
<div
className={cn(
"group relative h-[300px] w-[400px] shrink-0 cursor-pointer overflow-hidden rounded-xl shadow-2xl",
className,
)}
>
<img
src={card.url}
alt={card.title}
className="h-full w-full object-cover"
/>
<div className="absolute inset-0 bg-black/40" />
</div>
);
};
const MarqueeRow = ({
cards,
speed,
direction,
cardClassName,
}: {
cards: CardItem[];
speed: number;
direction: 1 | -1;
cardClassName?: string;
}) => {
const animationClass =
direction === -1 ? "animate-marquee-left" : "animate-marquee-right";
return (
<div className="flex w-full overflow-hidden">
<div
className={cn(
"flex shrink-0 cursor-pointer hover:[animation-play-state:paused]",
animationClass,
)}
style={{ "--speed": `${speed}s` } as React.CSSProperties}
>
<div className="flex shrink-0">
{cards.map((card, idx) => (
<div key={`${card.id}-${idx}`} className="shrink-0 pr-8">
<Card card={card} className={cardClassName} />
</div>
))}
</div>
<div className="flex shrink-0">
{cards.map((card, idx) => (
<div key={`${card.id}-${idx}-copy`} className="shrink-0 pr-8">
<Card card={card} className={cardClassName} />
</div>
))}
</div>
</div>
</div>
);
};
export default function DiagonalMarqueeCarousel({
cards = DEFAULT_CARDS,
angle = -25,
baseSpeed = 120,
alternateDirections = true,
className = "",
cardClassName = "",
fadeClassName = "",
}: DiagonalMarqueeCarouselProps) {
const rotationStyle = {
transform: `rotate(${angle}deg)`,
};
const rowCards = [...cards, ...cards, ...cards];
const rowCardsReverse = [...rowCards].reverse();
return (
<div
className={cn(
"relative flex h-screen w-full items-center justify-center overflow-hidden",
className,
)}
>
<style
dangerouslySetInnerHTML={{
__html: `
@keyframes marquee-left {
0% { transform: translate3d(0, 0, 0); }
100% { transform: translate3d(-50%, 0, 0); }
}
@keyframes marquee-right {
0% { transform: translate3d(-50%, 0, 0); }
100% { transform: translate3d(0, 0, 0); }
}
.animate-marquee-left {
animation: marquee-left var(--speed) linear infinite;
}
.animate-marquee-right {
animation: marquee-right var(--speed) linear infinite;
}
`,
}}
/>
<div
className="absolute z-0 flex w-[200vw] flex-col gap-8"
style={rotationStyle}
>
<MarqueeRow
cards={rowCards}
speed={baseSpeed}
direction={-1}
cardClassName={cardClassName}
/>
<MarqueeRow
cards={rowCardsReverse}
speed={baseSpeed - 15 > 20 ? baseSpeed - 15 : 30}
direction={alternateDirections ? 1 : -1}
cardClassName={cardClassName}
/>
<MarqueeRow
cards={rowCards}
speed={baseSpeed + 15}
direction={-1}
cardClassName={cardClassName}
/>
<MarqueeRow
cards={rowCardsReverse}
speed={baseSpeed - 6 > 20 ? baseSpeed - 6 : 35}
direction={alternateDirections ? 1 : -1}
cardClassName={cardClassName}
/>
<MarqueeRow
cards={rowCards}
speed={baseSpeed + 24}
direction={-1}
cardClassName={cardClassName}
/>
</div>
<div
className={cn(
"pointer-events-none absolute inset-x-0 top-0 z-10 h-1/4 bg-gradient-to-b from-white to-transparent dark:from-neutral-950",
fadeClassName,
)}
/>
<div
className={cn(
"pointer-events-none absolute inset-x-0 bottom-0 z-10 h-1/4 bg-gradient-to-t from-white to-transparent dark:from-neutral-950",
fadeClassName,
)}
/>
</div>
);
}
/**
* Great UI Component
*
* Built with React, TypeScript, Tailwind CSS, and Framer Motion.
* Designed to be accessible, customizable, and production-ready.
*
* Website: https://great-ui.com
* GitHub: https://github.com/Saurabh-2607/GreatUI
* X (Great UI): https://x.com/GreatUIHQ
*
* Released under the MIT License.
* Contributions, issues, and feature requests are always welcome.
*
* Author: Saurabh Sharma
* X: https://x.com/srbh_s
*/
demo.tsx
"use client"
import DiagonalMarqueeCarousel from "@/components/ui/great-ui-diagonal-marquee-carousel"
export default function DiagonalMarqueeCarouselPreview() {
return <DiagonalMarqueeCarousel className="absolute -inset-5 h-[calc(100%+2.5rem)] max-h-none w-[calc(100%+2.5rem)] max-w-none" />
}
```
最后更新于 2026/8/13 11:16:48