You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现与示例GIF一致的带动画UI卡片组件?

问题描述

我尝试创建了带背景渐变和动画的卡片组件,但效果与提供的GIF相差甚远。请问如何实现平滑动画、精准渐变,以及如图所示的卡片弹出效果?是否需要使用动画库?能否提供实现指导或解决方案?

[注意:请点击GIF查看,它可能未在问题区域正常渲染]
卡片动画效果

组件代码

ScrollableCardList 组件

import { useCallback, useEffect, useRef, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import Card from "../Card";
import demoCardData from "../../constants/cardData";

export const ScrollableCardList = ({
  cardData: propCardData,
}: {
  cardData?: Array<any>;
}) => {
  const data = propCardData || demoCardData;
  const containerRef = useRef<HTMLDivElement>(null);
  const [activeIndex, setActiveIndex] = useState(0);

  const handleScroll = useCallback(() => {
    if (!containerRef.current) return;

    const scrollTop = containerRef.current.scrollTop;
    const scrollHeight = containerRef.current.scrollHeight;
    const clientHeight = containerRef.current.clientHeight;

    const scrollRatio = scrollTop / (scrollHeight - clientHeight);
    const newActiveIndex = Math.floor(scrollRatio * data.length);

    setActiveIndex(newActiveIndex);
  }, [data]);

  useEffect(() => {
    if (!containerRef.current) return;

    containerRef.current.addEventListener("scroll", handleScroll);

    return () => {
      if (!containerRef.current) return;
      containerRef.current.removeEventListener("scroll", handleScroll);
    };
  }, [handleScroll]);

  return (
    <div
      ref={containerRef}
      className="flex flex-col items-center h-screen overflow-auto"
      style={{ scrollBehavior: "smooth" }}
    >
      {data.map((card, index) => (
        <Card
          key={uuidv4()}
          imageUrl={card.imageUrl}
          avatarImageUrl={card.avatarImageUrl}
          titleText={card.titleText}
          subTitleText={card.subTitleText}
          isActive={index === activeIndex}
        />
      ))}
    </div>
  );
};

Card 组件

import { Circular, Rectangular, SubTitle, Title } from "./placeholders";

type CardProps = {
  isActive?: boolean;
  cardContainerStyles?: string;
  rectangleContainerStyles?: string;
  circleContainerStyles?: string;
  imageUrl?: string;
  avatarImageUrl?: string;
  titleText?: string;
  subTitleText?: string;
};
const Card = ({
  isActive,
  cardContainerStyles,
  rectangleContainerStyles,
  circleContainerStyles,
  imageUrl,
  avatarImageUrl,
  titleText,
  subTitleText,
}: CardProps) => {
  const activeClass = isActive ? "scale-110" : "";
  // Ensure transition-transform is part of the base class string
  const cardBaseStyles = "transition-transform duration-300 ease-in-out";
  const cardDynamicStyles =
    cardContainerStyles ||
    "w-80 min-h-80 rounded-2xl shadow-4xl p-4 my-4 bg-gradient-to-br from-rose-500 to-rose-400";
  const cardStyles = `${cardBaseStyles} ${activeClass} ${cardDynamicStyles}`;
  const rectangleStyles =
    rectangleContainerStyles ||
    "bg-gradient-to-br from-rose-400 to-[#db7483] w-[100%] h-36 rounded-2xl shadow-2xl";
  const circleStyles =
    circleContainerStyles ||
    "w-12 h-12 rounded-full bg-gradient-to-br from-rose-400 to-rose-300 shadow-md";

  return (
    <div className={cardStyles}>
      <Rectangular rectangleStyles={rectangleStyles} imageUrl={imageUrl} />
      <div className="flex items-center mt-5">
        <Circular circleStyles={circleStyles} avatarImageUrl={avatarImageUrl} />
        <div className="flex-grow ml-4">
          <Title titleText={titleText} />
          <SubTitle subTitleText={subTitleText} />
        </div>
      </div>
    </div>
  );
};

export default Card;
解决方案

一、平滑动画与卡片弹出效果优化

  1. 替换生硬的激活判断逻辑
    当前通过滚动比例取整切换激活卡片的方式会导致动画突兀,改成基于卡片视口位置的实时计算:
  • 在ScrollableCardList的滚动监听中,用getBoundingClientRect()获取每个卡片的位置,对比视口中心坐标,找到最接近中心的卡片作为激活项,实现平滑过渡。
  1. 优化动画过渡细节
  • 给卡片添加多属性过渡,除了缩放,加上透明度、阴影变化,让层级感更自然:
    const cardBaseStyles = "transition-all duration-400 cubic-bezier(0.34, 1.56, 0.64, 1)";
    
  • 调整激活/非激活状态的样式,弱化非激活卡片,强化激活卡片的视觉突出:
    const activeClass = isActive 
      ? "scale-108 shadow-xl opacity-100 z-10" 
      : "scale-95 shadow-md opacity-80";
    

二、精准渐变实现

  1. 统一渐变色值体系
    当前卡片和内部元素的渐变存在色差,统一色值确保视觉连贯,比如:
  • 卡片背景:bg-gradient-to-br from-rose-500 to-fuchsia-600
  • 内部矩形:bg-gradient-to-br from-fuchsia-600 to-rose-400
  1. 指定精确渐变角度
    如果默认方向不符合需求,用具体角度定义渐变,比如bg-gradient-[135deg] from-rose-500 to-fuchsia-600,让渐变方向完全匹配目标效果。

三、是否需要动画库?

不需要额外动画库,原生CSS过渡+React状态足够实现当前需求。如果后续要做更复杂的时序动画,可以考虑framer-motion,但当前场景没必要。

四、关键代码调整示例

修改ScrollableCardList的滚动监听逻辑

const handleScroll = useCallback(() => {
  if (!containerRef.current) return;
  
  const viewportCenter = window.innerHeight / 2;
  let closestIndex = 0;
  let minDistance = Infinity;

  containerRef.current.querySelectorAll('.card').forEach((card, index) => {
    const rect = card.getBoundingClientRect();
    const cardCenter = rect.top + rect.height / 2;
    const distance = Math.abs(cardCenter - viewportCenter);
    
    if (distance < minDistance) {
      minDistance = distance;
      closestIndex = index;
    }
  });

  setActiveIndex(closestIndex);
}, [data]);

记得给Card组件根div加上card类名,方便选择器获取。

调整Card组件样式

const cardBaseStyles = "transition-all duration-400 cubic-bezier(0.34, 1.56, 0.64, 1)";
const activeClass = isActive 
  ? "scale-108 shadow-xl opacity-100 z-10" 
  : "scale-95 shadow-md opacity-80";
const cardDynamicStyles = cardContainerStyles || 
  "w-80 min-h-80 rounded-2xl p-4 my-4 bg-gradient-to-br from-rose-500 to-fuchsia-600";

内容的提问来源于stack exchange,提问作者Habi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 23:32:33