DEV Community

Shelner
Shelner

Posted on

Create simple swiper component

// root/src/app/page.tsx
import SwipeFeed from "@/components/SwiperFeed";

export default function Home() {
  return (
    <main className="h-screen w-screen overflow-hidden bg-black">
      <SwipeFeed />
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode
"use client";
// root/src/components/SwiperFeed.tsx

import { useEffect, useRef, useState } from "react";
import { sampleContentData } from "@/lib/sample-data";
import SwipeCard from "./SwipeCard";

export default function SwipeFeed() {
    const [current, setCurrent] = useState(0);
    const [deltaY, setDeltaY] = useState(0);
    const [clientHeight, setClientHeight] = useState(0);
    const touchStartY = useRef<number | null>(null);
    const [dragging, setDragging] = useState(false);

    useEffect(() => {
        if (typeof window !== "undefined") {
            setClientHeight(window.innerHeight);
        }
    }, []);

    const handleSwipe = (direction: "up" | "down") => {
        setCurrent((prev) => {
            if (direction === "up" && prev < sampleContentData.length - 1) return prev + 1;
            if (direction === "down" && prev > 0) return prev - 1;
            return prev;
        });
    };

    const handleTouchStart = (e: React.TouchEvent) => {
        touchStartY.current = e.touches[0].clientY;
        setDragging(true);
    };

    const handleTouchMove = (e: React.TouchEvent) => {
        if (touchStartY.current === null) return;
        const currentY = e.touches[0].clientY;
        setDeltaY((currentY - touchStartY.current) * 5.5);
    };

    const handleTouchEnd = () => {
        setDragging(false);
        const swipeThreshold = 100;

        if (deltaY < -swipeThreshold) handleSwipe("up");
        else if (deltaY > swipeThreshold) handleSwipe("down");

        setDeltaY(0);
        touchStartY.current = null;
    };

    const renderCard = (index: number, offset: number) => {
        if (index < 0 || index >= sampleContentData.length || clientHeight === 0) return null;

        let translate = offset * clientHeight;
        if (dragging && offset === 0) translate += deltaY;
        if (dragging && offset !== 0) translate += deltaY;

        console.log({ translate });

        return (
            <SwipeCard
                key={index}
                data={sampleContentData[index]}
                isActive={true}
                index={index}
                style={{
                    transform: `translateY(${translate}px)`,
                    zIndex: 10 - Math.abs(offset),
                }}
            />
        );
    };

    return (
        <div
            className="h-full w-full relative overflow-hidden touch-none"
            onTouchStart={handleTouchStart}
            onTouchMove={handleTouchMove}
            onTouchEnd={handleTouchEnd}
        >
            {renderCard(current - 1, -1)}
            {renderCard(current, 0)}
            {renderCard(current + 1, 1)}
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode
// root/src/components/SwiperCard.tsx
interface Props {
    data: {
        title: string;
        description: string;
        image: string;
    };
    style?: React.CSSProperties;
    isActive: boolean;
    index: number;
}

export default function SwipeCard({ data, style }: Props) {
    return (
        <div
            className="absolute top-0 left-0 w-full h-full bg-cover bg-no-repeat bg-center text-white p-4 transition-transform duration-300 ease-in-out"
            style={{
                backgroundImage: `url(${data.image})`,
                ...style,
            }}
        >
            <div className="bg-black bg-opacity-40 p-4 rounded-xl mt-10 max-w-lg mx-auto">
                <h2 className="text-2xl font-bold">{data.title}</h2>
                <p className="mt-2 text-lg">{data.description}</p>
            </div>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode
// root/src/lib/sample-data.ts
export const sampleContentData = [
    {
        title: "Welcome to the App",
        description: "Swipe up to learn more.",
        image: "/images/slide1.png", // change the image path
    },
    {
        title: "Stay Informed",
        description: "Quick, fun, and useful content.",
        image: "/images/slide2.png", // change the image path
    },
    {
        title: "Keep Swiping",
        description: "Discover more as you go.",
        image: "/images/slide3.png", // change the image path
    },
];
Enter fullscreen mode Exit fullscreen mode

Place the image in public/images and change the image path in sample-data.ts.

Run: npm run dev

Top comments (0)