Trying to create a marquee effect that displays a list of items in a vertical auto-scroll. After reaching the end of the list, I want it to loop back to the beginning seamlessly for continuous animation.
The code snippet below shows my progress so far - an animation set to run for 25 seconds and then all items are added back to the list using React setState after each cycle.
This is the code attempted:
import styled, { keyframes } from "styled-components";
import React from "react";
// Keyframes for the animation
const marqueeTop = keyframes`
0% {
top: 0%;
}
100% {
top: -100%;
}
`;
// Styled components
const MarqueeWrapper = styled.div`
overflow: hidden;
margin: 0 auto !important;
`;
const MarqueeBlock = styled.div`
width: 100%;
height: 44vh;
flex: 1;
overflow: hidden;
box-sizing: border-box;
position: relative;
float: left;
`;
const MarqueeInner = styled.div`
position: relative;
display: inline-block;
animation: ${marqueeTop} 25s linear infinite;
animation-timing-function: linear;
&:hover {
animation-play-state: paused; /* Pause the animation on hover */
}
`;
const MarqueeItem = styled.div`
transition: all 0.2s ease-out;
`;
export default class MarqueeContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
itemsToShow: this.props.items, // gets items from parent
};
}
// Re-add all items back for re-rendering
scrollItems = () => {
setInterval(() => {
this.setState((prevState) => {
const newItems = [...prevState.itemsToShow];
newItems.push(this.props.items);
return { itemsToShow: newItems };
});
}, 25000);
};
componentDidMount() {
this.scrollItems();
}
render() {
return (
<MarqueeWrapper>
<MarqueeBlock>
<MarqueeInner>
{this.state.itemsToShow.map((item, index) => (
<MarqueeItem key={index}>{item}</MarqueeItem>
))}
</MarqueeInner>
</MarqueeBlock>
</MarqueeWrapper>
);
}
}
While it somewhat works, I've observed that at the end of each 25-second cycle, the list "refreshes" and the animation restarts from the first item, which appears unnatural. Is there a way to achieve the desired animation without this reset? Any alternative methods are welcome!
Thank you!
Edit 1: I have provided a demonstration of the current functionality. For a list of 39 items, I aim for item 0 to follow item 39 smoothly, but currently, the list refreshes with item 0 at the top instead. See GIF demo
Edit 2:
Here's the revised code that combines MrSrv7's approach with some adjustments to reintegrate items into the array after a set period:
import styled, { keyframes } from "styled-components";
import React from "react";
const marqueeTop = keyframes`
0% {
transform: translateY(0);
}
100% {
transform: translateY(-100%);
}
`;
const MarqueeWrapper = styled.div`
overflow: hidden;
margin: 0 auto !important;
`;
const MarqueeBlock = styled.div`
width: 100%;
height: 44vh;
flex: 1;
overflow: hidden;
box-sizing: border-box;
position: relative;
float: left;
`;
const MarqueeInner = styled.div`
position: relative;
display: inline-block;
animation: ${marqueeTop} 120s linear infinite;
animation-timing-function: linear;
&:hover {
animation-play-state: paused;
}
`;
const MarqueeItem = styled.div`
transition: all 1s ease-out;
`;
export default class MarqueeContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
itemsToShow: this.generateMarqueeItems(),
};
}
scrollItems = () => {
setInterval(() => {
this.setState({ itemsToShow: this.generateMarqueeItems() });
}, 120000);
};
componentDidMount() {
this.scrollItems();
}
generateMarqueeItems = () => {
const items = this.props.items;
const visibleItemsCount = 5;
const marqueeItems = Array.from({ length: visibleItemsCount }, () => items).flat();
return marqueeItems;
};
render() {
return (
<MarqueeWrapper>
<MarqueeBlock>
<MarqueeInner>{this.state.itemsToShow && this.state.itemsToShow.map((item, index) => <MarqueeItem key={index}>{item}</MarqueeItem>)}</MarqueeInner>
</MarqueeBlock>
</MarqueeWrapper>
);
}
}