Vertical Text Slider

Slides through an list of text vertically, pausing briefly on each item before transitioning to the next.

1. Copy the component file

Create a new file called vertical-text-slider.jsx in your reusable components folder (for example /src/components/) and paste the following code into it.

vertical-text-slider.jsx

import { memo, useState, useEffect, useRef, useMemo } from "react";
import styles from "./vertical-text-slider.module.css";

const VerticalTextSlider = (props) => {
  const {
    texts,
    direction = "up",
    visibleDuration = 2000,
    className,
    style,
    ...restProps
  } = props;

  const transitionDurationMs = 250;

  const [currentTextIndex, setCurrentTextIndex] = useState(0);

  const wordNodeRef = useRef();

  const wordDirectionClassName = useMemo(() => (
    `direction-${direction === "down" ? "down" : "up"}`
  ), [direction]);

  const totalAnimationDuration = useMemo(() => (
    Math.max(transitionDurationMs * 4, visibleDuration)
  ), [transitionDurationMs, visibleDuration]);

  useEffect(() => {
    let animationOutTimeoutId = null, animationEndTimeoutId = null;
    const animationInTimeoutId = setTimeout(() => {
      wordNodeRef.current.classList.add(styles["pause"]);
      animationOutTimeoutId = setTimeout(() => {
        wordNodeRef.current.classList.remove(styles["pause"]);
        wordNodeRef.current.style.animationName = styles[
          `text-slide-${direction === "down" ? "up" : "down"}-keyframes`
        ];
        animationEndTimeoutId = setTimeout(() => {
          setCurrentTextIndex(
            (currentTextIndex + 1) % texts.length
          );
        }, transitionDurationMs);
      }, totalAnimationDuration - (transitionDurationMs * 2));
    }, transitionDurationMs);
    return () => {
      clearTimeout(animationInTimeoutId);
      clearTimeout(animationOutTimeoutId);
      clearTimeout(animationEndTimeoutId);
    };
  }, [
    texts.length, 
    currentTextIndex, 
    direction, 
    totalAnimationDuration,
  ]);

  useEffect(() => {
    setCurrentTextIndex(0);
  }, [
    texts,
    visibleDuration,
    direction
  ]);

  return (
    <span
      {...restProps}
      className={[
        className,
        styles["vertical-text-slider"]
      ].join(" ")}
      style={{
        ...style,
        "--sliding-animation-duration": `${transitionDurationMs}ms`
      }}
    >
      <span
        ref={wordNodeRef}
        key={`word-${currentTextIndex}`}
        className={[
          styles["text"],
          styles[wordDirectionClassName]
        ].join(" ")}
      >
        {texts[currentTextIndex]}
      </span>
    </span>
  );
};

export default memo(
  VerticalTextSlider,
  (prevProps, nextProps) => {
    const { texts: prevTexts, ...restPrevProps } = prevProps;
    const { texts: nextTexts, ...restNextProps } = nextProps;
    const prevKeys = Object.keys(restPrevProps);
    const nextKeys = Object.keys(restNextProps);
    const areRestEqual = (
      prevKeys.length === nextKeys.length &&
      prevKeys.every(prop => (
        prevProps[prop] === nextProps[prop]
      ))
    );
    if(
      Array.isArray(prevTexts) && 
      Array.isArray(nextTexts)
    ) {
      return (
        areRestEqual && 
        prevTexts.every((text, index) => (
          text === nextTexts[index]
        ))
      );
    }
    return areRestEqual;
  },
);

2. Copy the CSS module file

In the same folder, create a file called vertical-text-slider.module.css and paste the following CSS.

vertical-text-slider.module.css

.vertical-text-slider {
  --sliding-animation-duration: 250ms;

  > .text {
    display: inline-block;
    word-break: keep-all;
    animation-duration: var(--sliding-animation-duration);
    animation-timing-function: ease-in-out;
    animation-fill-mode: forwards;

    &.direction-down {
      opacity: 0;
      transform: translateY(-50%);
      animation-name: text-slide-down-keyframes;
    }

    &.direction-up {
      opacity: 0;
      transform: translateY(50%);
      animation-name: text-slide-up-keyframes;
      animation-direction: reverse;
    }

    &.pause {
      opacity: 1 !important;
      transform: translateY(0%) !important;
      animation: unset;
    }
  }
}

@keyframes text-slide-up-keyframes {
  0% {
    opacity: 1;
    transform: translateY(0%);
  }

  100% {
    opacity: 0;
    transform: translateY(50%);
  }
}

@keyframes text-slide-down-keyframes {
  0% {
    opacity: 0;
    transform: translateY(-50%);
  }

  100% {
    opacity: 1;
    transform: translateY(0%);
  }
}

3. Use the component

Now you can import and use the component anywhere in your project.

vertical-text-slider-preview.jsx

import VerticalTextSlider from "@/components/text-effects/vertical-text-slider/vertical-text-slider";

const VerticalTextSliderPreview = () => {
  return (
    <h2>
      <VerticalTextSlider
        texts={[
          "Build faster",
          "Ship smarter",
          "Scale confidently",
          "Delight users"
        ]}
      />
    </h2>
  );
};

export default VerticalTextSliderPreview;

Down Direction

Build faster

vertical-text-slider

import VerticalTextSlider from "@/components/text-effects/vertical-text-slider/vertical-text-slider";

const VerticalTextSliderDownDirectionPreview = () => {
  return (
    <h2>
      <VerticalTextSlider
        texts={[
          "Build faster",
          "Ship smarter",
          "Scale confidently",
          "Delight users"
        ]}
        direction="down"
      />
    </h2>
  );
};

export default VerticalTextSliderDownDirectionPreview;

Custom Visible Duration

Build faster

vertical-text-slider

import VerticalTextSlider from "@/components/text-effects/vertical-text-slider/vertical-text-slider";

const VerticalTextSliderVisibleDurationPreview = () => {
  return (
    <h2>
      <VerticalTextSlider
        texts={[
          "Build faster",
          "Ship smarter",
          "Scale confidently",
          "Delight users"
        ]}
        visibleDuration={5000}
      />
    </h2>
  );
};

export default VerticalTextSliderVisibleDurationPreview;

Props

Configure the component with the following props:

PropTypeRequiredDefaultDescription
textsstring[]YesArray of text strings to display in the vertical slider.
direction"up" | "down"No"up"Slide direction. "up" slides text upward, "down" slides text downward.
visibleDurationnumberNo2000Time (in milliseconds) each text remains fully visible before sliding out. Minimum: 1000ms.
classNamestringNoOptional class name applied to the root container.
styleReact.CSSPropertiesNoInline styles applied to the root container.