I have embarked on the journey of crafting a testimonial section that bears resemblance to the testimonials displayed on the website www.runway.com. Leveraging styled-components, I've made progress in piling up the cards at the center of the screen, where they gracefully ascend from the bottom to the middle with a seamless linear animation upon entering the view. However, what captivates me about the aforementioned website is its scrolling functionality - as users gradually scroll down, the cards elegantly emerge one after another from the bottom of the screen.
//
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%);
}
to {
transform: rotate(var(--rx));
}
}
img{
width: 0%;
position:absolute;
transform:rotate(0deg);
}
.f-image-25{
width:15%;
--rx: -3deg;
z-index:25;
animation: splash 1s normal forwards ease-in-out;
}
.f-image-35{
width:17%;
--rx: -3deg;
z-index:35;
animation: splash 1s normal forwards ease-in-out;
}
.f-image-40{
width:20%;
--rx: 3deg;
z-index:40;
animation: splash 1s normal forwards ease-in-out;
}
.f-image-30{
width:17%;
--rx: -6deg;
z-index:30;
animation: splash 1s normal forwards ease-in-out;
}
.f-image-20{
width:15%;
--rx: 10deg;
z-index:20;
animation: splash 1s normal forwards ease-in-out;
}
`;
const Testimonial = () => {
const [isAnimated, setIsAnimated] = useState(false);
const options = {
root: null,
margin: '0px',
threshold: 0.5
}
const observerCallback = (entries) => {
const [mockImg] = entries
if (!isAnimated) {
setIsAnimated(true);
if (mockImg.isIntersecting) {
document.getElementById('f-image-40').classList.add('f- image-40')
document.getElementById('f-image-35').classList.add('f-image-35')
document.getElementById('f-image-30').classList.add('f-image-30')
document.getElementById('f-image-25').classList.add('f-image-25')
document.getElementById('f-image-20').classList.add('f-image-20')
}
}
}
useEffect(() => {
const fImage = document.getElementById('f-image')
const observer = new IntersectionObserver(observerCallback, options)
observer.observe(fImage)
return () => {
observer.unobserve(fImage);
}
}, [])
return (
<TestimonialSection>
<TestimonialDiv>
<BottomCenter id='f-image'>
<img alt="testimonial-card" id='f-image-25' src={ticket}></img>
<img alt="testimonial-card" id='f-image-35' src={ticket}></img>
<img alt="testimonial-card" id='f-image-40' src={ticket}></img>
<img alt="testimonial-card" id='f-image-30' src={ticket}></img>
<img alt="testimonial-card" id='f-image-20' src={ticket}></img>
</BottomCenter>
</TestimonialDiv>
</TestimonialSection>
)
}
export default Testimonial