news/app/components/AutoCarousel.tsx

103 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as React from "react";
import Autoplay from "embla-carousel-autoplay";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
type CarouselApi,
} from "@/ui/carousel";
const imageUrls = [
"/images/carousel-1.jpg",
"/images/carousel-2.jpg",
"/images/carousel-3.jpg",
"/images/carousel-4.jpg",
"/images/carousel-5.jpg",
"/images/carousel-6.jpg",
"/images/carousel-7.jpg",
"/images/carousel-8.jpg",
"/images/carousel-9.jpg",
"/images/book1.png",
"/images/header.png",
"/images/jcdt.png",
];
export function AutoCarouselDemo() {
const [api, setApi] = React.useState<CarouselApi>();
const [current, setCurrent] = React.useState(0);
const totalSlides = imageUrls.length;
React.useEffect(() => {
if (!api) return;
setCurrent(api.selectedScrollSnap());
api.on("select", () => {
setCurrent(api.selectedScrollSnap());
});
}, [api]);
const getScaleByDistance = (index: number, current: number, total: number): number => {
const directDist = Math.abs(index - current);
const wrapDist = total - directDist;
const minDist = Math.min(directDist, wrapDist);
return Math.max(0.5, 1.4 - minDist * 0.15);
};
return (
// 最外层:允许内容溢出(关键!)
<div className="relative w-full mx-auto px-4 py-12 overflow-visible">
<Carousel
opts={{ loop: true, align: "center" }}
plugins={[
Autoplay({ delay: 3000, stopOnInteraction: false }),
]}
setApi={setApi}
className="w-full"
// 关键覆盖 Embla 默认的 overflow:hidden
style={{ overflow: 'visible' }}
>
{/* 内容区域也要 visible */}
<CarouselContent className="py-6" style={{ overflow: 'visible' }}>
{imageUrls.map((src, index) => {
const scale = getScaleByDistance(index, current, totalSlides);
const isCurrent = scale > 1.2;
return (
<CarouselItem
key={index}
// 设置基础宽度比例但不设高度
className="basis-1/3 sm:basis-1/4 md:basis-1/5 flex-shrink-0 flex justify-center"
// 关键这里不加 transform而是让子元素控制
style={{ overflow: 'visible' }}
>
{/* 这个 div 是真正要缩放的整体卡片 */}
<div
className="rounded-xl shadow-xl bg-white transition-all duration-300 ease-out w-full max-w-[180px]"
style={{
height: '240px', // 固定高宽比替代方案(更稳定)
transform: `scale(${scale})`,
transformOrigin: 'center bottom',
zIndex: isCurrent ? 20 : 1,
willChange: 'transform',
}}
>
<img
src={src}
alt={`Slide ${index + 1}`}
className="w-full h-full object-cover rounded-xl"
loading="lazy"
/>
</div>
</CarouselItem>
);
})}
</CarouselContent>
<CarouselPrevious className="absolute left-0 top-1/2 -translate-y-1/2 z-30" />
<CarouselNext className="absolute right-0 top-1/2 -translate-y-1/2 z-30" />
</Carousel>
</div>
);
}