Docs
Text Slider

Text Slider

A text animation bringing text from either sider up or down.

Hello world !
Slide like this.

Install the following dependencies:

pnpm add gsap @gsap/react

Make a file for cn and mergerRef functions and match the import afterwards

import clsx, { ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export const cn = (...classes: ClassValue[]) => twMerge(clsx(...classes))
 
export function mergeRefs<T>(...refs: (React.Ref<T> | undefined)[]) {
  return (node: T | null) => {
    refs.forEach((ref) => {
      if (typeof ref === "function") {
        ref(node)
      } else if (ref && "current" in ref) {
        ;(ref as React.MutableRefObject<T | null>).current = node
      }
    })
  }
}

Make a file and copy paste this code in a file with name text-slider.tsx

"use client"
 
import React, { useRef } from "react"
import { cn } from "@/lib/utils"
import { useGSAP } from "@gsap/react"
import gsap from "gsap"
import { ScrollTrigger } from "gsap/ScrollTrigger"
 
interface TextSliderProps {
  children?: string | React.ReactNode
  className?: string
  delay?: number
  start?: string
  end?: string
  translateX?: number
  popFrom?: "up" | "down"
  translateDuration?: number
  duration?: number
}
 
gsap.registerPlugin(ScrollTrigger)
 
export function TextSlider({
  children,
  className,
  delay = 0,
  translateX,
  popFrom = "down",
  translateDuration = 0.4,
  duration = 0.3,
  start = "top 80%",
  end = "top 65%",
}: TextSliderProps) {
  const containerRef = useRef<HTMLElement>(null)
  const lineRef = useRef<HTMLDivElement>(null)
  const tl = useRef<gsap.core.Timeline | null>(null)
 
  useGSAP(
    () => {
      if (containerRef.current && lineRef.current) {
        const lineHeight = lineRef.current.offsetHeight || 0
        const animationDistance = popFrom === "up" ? -lineHeight : lineHeight
 
        tl.current = gsap.timeline({
          delay,
          scrollTrigger: {
            trigger: containerRef.current,
            start,
            end,
            toggleActions: "play none none reverse",
          },
        })
 
        // Animate line using the calculated height
        tl.current.from(lineRef.current, {
          y: animationDistance,
          duration: duration,
          ease: "linear",
        })
 
        // Add translateX animation if specified
        if (translateX) {
          tl.current.to(
            containerRef.current,
            {
              x: translateX.toString().concat("%"),
              duration: translateDuration,
              ease: "linear",
            },
            "+=0.1"
          )
        }
      }
 
      return () => {
        if (tl.current) {
          tl.current.kill()
        }
      }
    },
    {
      dependencies: [popFrom],
      revertOnUpdate: true,
      scope: containerRef,
    }
  )
 
  return (
    <section
      ref={containerRef}
      className={cn("overflow-hidden block", className)}
    >
      <div className="inline-block opacity-1" ref={lineRef}>
        {children}
      </div>
    </section>
  )
}

Props

NameTypeDescription
childrenstring | React.ReactNodeContent to be animated.
classNamestringCustom CSS classes to style the container.
delaynumberDelay before the animation starts, in seconds.
startstringScrollTrigger start point, refers to gsap docs
endstringScrollTrigger end point, refers to gsap docs
translateXnumberHorizontal translation percentage to move the container.
popFrom"up" | "down"Direction from which the content should slide in.
translateDurationnumberDuration for the horizontal (X-axis) animation in seconds.
durationnumberDuration for the vertical pop-in animation in seconds.