logo imgParther

Creating a Parallax Scroll Image Slider with Framer Motion and Tailwind CSS

June 28, 2025

In this blog post, I'll show you how I built a smooth parallax scroll image slider using Framer Motion, Next.js, and Tailwind CSS. This component creates a beautiful horizontal scroll effect that reacts to vertical scrolling — perfect for galleries, hero sections, or adding subtle depth to any landing page.

See It In Action

Understanding the Component

The component displays two rows of images that move horizontally in opposite directions as the user scrolls vertically. This creates a smooth, layered parallax effect that brings depth to your UI — perfect for galleries, hero sections, or showcasing portfolios.

Setting Up the Environment

We are using React, Framer Motion and Tailwind CSS for styling.

  • Tailwind CSS
  • React
  • Framer Motion

Setting Up the Image Rows

Before we add any motion or scrolling effects, let’s start with the basic layout — two horizontal rows of images stacked vertically:

<> app/index.tsx
"use client";
import Image from "next/image";
import React from "react";

const Index = () => {
  const leftImages = [
    "/ImgParallax/img/g.jpg",
    "/ImgParallax/img/gg.jpg",
    "/ImgParallax/img/ss.jpg",
    "/ImgParallax/img/ssss.jpg",
    "/ImgParallax/img/ttt.jpg"
  ];

  const rightImages = [
    "/ImgParallax/img/ss.jpg",
    "/ImgParallax/img/dd.jpg",
    "/ImgParallax/img/ttt.jpg",
    "/ImgParallax/img/sss.jpg",
    "/ImgParallax/img/g.jpg"
  ];

  return (
    <div className="overflow-hidden relative z-10 w-full bg-EerieBlack px-0 sm:px-3 lg:px-5 py-52 md:py-32">
      <div className="flex justify-center gap-3 md:gap-4 py-0.5 md:py-2 h-32 md:h-56">
        {leftImages.map((src, index) => (
          <Image
            key={index}
            alt={`img-${index}`}
            src={src}
            width={380}
            height={253.36}
            className="h-3/4 w-3/4 md:w-full md:h-full"
          />
        ))}
      </div>
      <div className="flex justify-center gap-3 md:gap-4 py-0.5 md:py-2 h-32 md:h-56">
        {rightImages.map((src, index) => (
          <Image
            key={index}
            alt={`img-${index}`}
            src={src}
            width={380}
            height={253.36}
            className="h-3/4 w-3/4 md:w-full md:h-full"
          />
        ))}
      </div>
    </div>
  );
};

export default Index;

We should have something like this:

Preview of static image rows

Adding Scroll-Based Parallax Motion

With the static image rows set up, it’s time to bring them to life with a smooth parallax effect. In this step, you’ll use Framer Motion’s useScroll and useTransform hooks.

🔍 What does this do?

  • Track how much you’ve scrolled through the image container.
  • Map that scroll progress to horizontal movement for both rows.
  • Wrap the rows in <motion.div> so they animate as you scroll.
<> app/index.tsx
"use client";
import { useScroll, useTransform, motion } from "framer-motion";
import Image from "next/image";
import React, { useRef } from "react";

const Index = () => {
  const container = useRef(null);

  const { scrollYProgress } = useScroll({
    target: container,
    offset: ["start end", "end start"]
  });

  const xLeft = useTransform(scrollYProgress, [0, 1], [0, -150]);
  const xRight = useTransform(scrollYProgress, [0, 1], [0, 100]);

  const leftImages = [
    "/ImgParallax/img/g.jpg",
    "/ImgParallax/img/gg.jpg",
    "/ImgParallax/img/ss.jpg",
    "/ImgParallax/img/ssss.jpg",
    "/ImgParallax/img/ttt.jpg"
  ];

  const rightImages = [
    "/ImgParallax/img/ss.jpg",
    "/ImgParallax/img/dd.jpg",
    "/ImgParallax/img/ttt.jpg",
    "/ImgParallax/img/sss.jpg",
    "/ImgParallax/img/g.jpg"
  ];

  return (
    <div
      ref={container}
      className="overflow-hidden relative z-10 w-full bg-EerieBlack px-0 sm:px-3 lg:px-5 py-52 md:py-32"
    >
      <motion.div
        className="flex justify-center gap-3 md:gap-4 py-0.5 md:py-2 h-32 md:h-56"
        style={{ x: xLeft }}
      >
        {leftImages.map((src, index) => (
          <Image
            key={index}
            alt={`img-${index}`}
            src={src}
            width={380}
            height={253.36}
            className="h-3/4 w-3/4 md:w-full md:h-full"
          />
        ))}
      </motion.div>
      <motion.div
        className="flex justify-center gap-3 md:gap-4 py-0.5 md:py-2 h-32 md:h-56"
        style={{ x: xRight }}
      >
        {rightImages.map((src, index) => (
          <Image
            key={index}
            alt={`img-${index}`}
            src={src}
            width={380}
            height={253.36}
            className="h-3/4 w-3/4 md:w-full md:h-full"
          />
        ))}
      </motion.div>
    </div>
  );
};

export default Index;

Attach a ref

Attach a ref to the container. This tells useScroll exactly which section to watch:

<> app/index.tsx
const container = useRef(null);

Track scroll progress

  • scrollYProgress goes from 0 → 1 as the container enters and leaves the viewport.
  • The offset controls when the animation starts and ends.
<> app/index.tsx
const { scrollYProgress } = useScroll({
  target: container,
  offset: ["start end", "end start"]
});

Map scroll progress to horizontal motion

  • The left row moves left up to -150px.
  • The right row moves right up to +100px.
<> app/index.tsx
const xLeft = useTransform(scrollYProgress, [0, 1], [0, -150]);
const xRight = useTransform(scrollYProgress, [0, 1], [0, 100]);

Wrap rows in <motion.div> and bind the x transform using the style prop — this makes the rows slide smoothly as you scroll.

See It In Action

🎉 Conclusion

You've successfully created a smooth Parallax Scroll Image Slider component! This demonstrates how simple scroll-based animations can add depth and interactivity to your Next.js projects with Framer Motion and Tailwind CSS.

Copyright 2025 © Parther