Creating a vertical scrolling marquee to showcase a list in React - step by step guide

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>
    );
  }
}

Answer №1

Through experimentation and perseverance, I devised a solution that did not rely on setInterval. Instead, I created a function that continuously adds to the state.itemsToShow array and maps it accordingly.

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} 25s linear infinite;
  animation-timing-function: linear;
  &:hover {
    animation-play-state: paused;
  }
`;

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.generateMarqueeItems()
    };
  }

  generateMarqueeItems = () => {
    const { items } = this.props;
    const visibleItemsCount = 5;
    const marqueeItems = [];

    while (marqueeItems.length < items.length * visibleItemsCount) {
      marqueeItems.push(...items);
    }

    return marqueeItems;
  };

  render() {
    const { itemsToShow } = this.state;
    return (
      <MarqueeWrapper>
        <MarqueeBlock>
          <MarqueeInner>
            {itemsToShow &&
              itemsToShow.length > 0 &&
              itemsToShow.map((item, index) => (
                <MarqueeItem key={index}>{item}</MarqueeItem>
              ))}
          </MarqueeInner>
        </MarqueeBlock>
      </MarqueeWrapper>
    );
  }
}

I trust this information proves useful.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Transferring data to a Component as properties in Next.js

I've encountered an issue while trying to pass editvalues as props to my EditForm component, resulting in the error 'editvalues is not defined'. Important note: editvalues is an object Upon using console.log(editvalues), I can see the valu ...

Styling the content within Template Strings is not supported by VSCode

Recently, I've noticed that there are two scenarios in which my VSCode doesn't properly style the content within my template strings. One is when I'm writing CSS in a JavaScript file, and the other is when I'm fetching data from GraphQL ...

Show on smartphones, conceal on desktops - Activation/URL

I'm attempting to create a click-to-call link on my website using the following: <a class="mobile-only" href="tel:+534306464456"><p class="">Click Here to call us</p></a> I want this link to only show up on mobile devices, sp ...

Implementing jQuery slideDown effect on a nested table element

I am facing an issue where the nested table does not slide down from the top as intended when a user clicks the "Show" button. Despite setting the animation duration to 500ms with jQuery's slideDown() function, the table instantly appears. I am using ...

The React application is showing an empty page without any visible errors during the compilation process

I'm having trouble with my React app - it compiles without errors but only shows a blank page. Can someone help me figure out what's wrong with my code? I'm new to React and could use some guidance. index.js import React from 'react&ap ...

What is the process by which the browser displays pseudo-element selectors?

One thing that's been on my mind is the potential resource cost of using *:after. Would the pseudo element be the primary selector, or would all elements still be processed by the client? In simpler terms, what impact does *:after have compared to ju ...

Implementing TypeScript with styled components using the 'as' prop

I am in the process of developing a design system, and I have created a Button component using React and styled-components. To ensure consistency, I want all my Link components to match the style and receive the same props as the Button. I am leveraging t ...

What is the best way to ensure that this encompassing div adjusts its width to match that of its child elements?

My goal is to keep fave_hold at 100% of its parent div, while limiting the width of .faves to fit its child elements. These elements are generated dynamically with predefined widths. Below is the code snippet in React/JSX format: <div id='fave_h ...

Selenium Webdriver faced challenges in identifying dynamically created scripts using CSS selectors

I am in need of assistance with the following: Requirement: On the homepage, there is a navigation menu bar that changes appearance when scrolling down. This change is reflected in the body class name switching from "home" to "home scriptable persistent-o ...

Exploring the power of React Leaflet and the exciting possibilities of React Leaflet

I'm currently in the process of implementing the draw functions on a leaflet map. I started off by creating a new app with just react-leaflet installed. I used npx create-react-app and installed the following packages: npm install react react-dom lea ...

Error message encountered on localhost: preflight response contains Access-Control-Allow-Headers issue

I'm currently working on my React app locally at localhost:3000 and utilizing this code snippet: try { const response = await fetch('https://myendpoint.com/abcd/search?c=abc123', { headers: { 'Content-Type': 'application ...

Setting up global color variables for React with Material UI theming is essential for ensuring consistent and

Is there a way to customize the default material-ui theme using global CSS? Additionally, how can we incorporate HEX values for colors when setting primary and secondary colors in the theme? In my App.js root file, I have defined a custom theme and want t ...

How can I modify the preset website design in React.js (vite)?

Whenever I start a new react js project, I am always met with the default template that includes unnecessary code. I find myself spending time navigating through different files to clean it up before I can start building. I've been searching for a bla ...

React custom dropdown is failing to update its state

How can I create a custom styled dropdown without using select? I want to update the state value based on click, but it continues to show 'choose' as the default value. Any suggestions on how to make this work properly? import React from "react" ...

To open a popup menu with a text box, simply click on the text box

Whenever the text box text is clicked, a popup menu should open with a text box. Similarly, when the filter icon in the right side corner is clicked, a menu should open with a list of checkboxes. However, currently both menus are opening when clicking on b ...

Is there a method to instruct angular-cli (for angular 2) to produce a minified version of css files?

When running "ng serve" in angular-cli, the generated css is not minified as expected. Is there a specific setting to configure in angular-cli-build or an additional plugin to install? This is the content of my angular-cli-build.js file: var Angular2App ...

When trying to upload a file with ng-upload in Angular, the error 'TypeError: Cannot read properties of undefined (reading 'memes')' is encountered

Struggling with an issue for quite some time now. Attempting to upload an image using ng-upload in angular, successfully saving the file in the database, but encountering a 'Cannot read properties of undefined' error once the upload queue is comp ...

Show a dynamic dropdown box using AJAX if a specific variable is present

I am currently working on implementing an ajax call for a dynamic dropdown box. Once the variable $place is available, it triggers an ajax call to populate the dropdown box. My goal is to pass the variable $place to listplace.php, encode it as JSON data ...

Error Uploading File - Functioning in Postman but not on website interface

I'm currently pursuing the full stack certification on devchallenges.io and tackling the authentication app challenge. So far, I've successfully implemented the login and register functionality, as well as fetching the logged-in user's infor ...

What is the best way to reduce a varying percentage as the value continues to rise?

My memory of math may be a bit fuzzy, but I can't seem to recall how to solve this particular issue. In jQuery, I have been adding randomized clipping paths to my images using the following code: var max=100; var spread=5; jQuery.each( $("img"), func ...