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

React中使用styled-components实现滚动触发的Testimonial卡片逐个动画

实现滚动触发的卡片逐个入场动画(Testimonial板块)

要实现滚动到区域时卡片从底部逐个出现的效果,只需对现有代码做以下几处关键修改:

修改后的完整代码

import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import ticket from "./assets/ticket0.webp";

const TestimonialSection = styled.section``;

const TestimonialDiv = styled.div`
height: 80vh;
margin: auto;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
min-height: 70%;
`;

const BottomCenter = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 100%;
@keyframes splash {
    from {
        transform: translate(0%, 100%);
        opacity: 0;
    }
    to { 
        transform: rotate(var(--rx));
        opacity: 1;
    }
}

img{
    width: 0%;
    position:absolute;
    transform:rotate(0deg);
    opacity: 0;
    transition: width 0.3s ease;
}

.f-image-25{
    width:15%;
    --rx: -3deg;
    z-index:25;
    animation: splash 1s normal forwards ease-in-out;
    animation-delay: 0ms;
}
.f-image-35{
    width:17%;
   --rx: -3deg;
    z-index:35;
    animation: splash 1s normal forwards ease-in-out;
    animation-delay: 200ms;
}
.f-image-40{
    width:20%;
    --rx: 3deg;
    z-index:40;
    animation: splash 1s normal forwards ease-in-out;
    animation-delay: 400ms;
}
.f-image-30{
    width:17%;
    --rx: -6deg;
    z-index:30;
    animation: splash 1s normal forwards ease-in-out;
    animation-delay: 600ms;
}
.f-image-20{
    width:15%;
    --rx: 10deg;
    z-index:20;
    animation: splash 1s normal forwards ease-in-out;
    animation-delay: 800ms;
}
`;

const Testimonial = () => {
const [isAnimated, setIsAnimated] = useState(false);
const options = {
    root: null,
    margin: '0px',
    threshold: 0.2
}

const observerCallback = (entries) => {
    const [entry] = entries
    // 当元素进入视口且未触发过动画时,启动动画
    if (entry.isIntersecting && !isAnimated) {
        setIsAnimated(true);
    }
}

useEffect(() => {
    const container = document.getElementById('testimonial-container')
    const observer = new IntersectionObserver(observerCallback, options)
    observer.observe(container)
    return () => {
        observer.unobserve(container);
    }
}, [])

return (
    <TestimonialSection id="testimonial-container">
        <TestimonialDiv>
            <BottomCenter>
                <img 
                    alt="testimonial-card" 
                    className={isAnimated ? 'f-image-25' : ''} 
                    src={ticket}
                />
                <img 
                    alt="testimonial-card" 
                    className={isAnimated ? 'f-image-35' : ''} 
                    src={ticket}
                />
                <img 
                    alt="testimonial-card" 
                    className={isAnimated ? 'f-image-40' : ''} 
                    src={ticket}
                />
                <img 
                    alt="testimonial-card" 
                    className={isAnimated ? 'f-image-30' : ''} 
                    src={ticket}
                />
                <img 
                    alt="testimonial-card" 
                    className={isAnimated ? 'f-image-20' : ''} 
                    src={ticket}
                />
            </BottomCenter>
        </TestimonialDiv>
    </TestimonialSection>
)
}

export default Testimonial

关键改动说明

  1. 添加动画延迟(animation-delay)
    给每个卡片的动画类设置不同的延迟时间,实现逐个入场的效果,延迟间隔可根据需求调整(这里用200ms间隔)。

  2. 优化动画效果
    在splash动画中加入opacity过渡,让卡片入场更自然,避免突然出现的生硬感。

  3. 替换DOM操作为React状态控制类名
    用isAnimated状态直接控制卡片的className,符合React的声明式编程范式,避免直接操作DOM带来的潜在问题。

  4. 调整Intersection Observer阈值
    将threshold设为0.2,意味着当元素有20%进入视口时就触发动画,让用户更早看到动画效果,提升交互体验。

  5. 修复原代码的类名拼写错误
    原代码中f- image-40多了空格,已修正为正确的f-image-40。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 00:00:57