skip to Main Content

I need to make a carousel which look like this the carousel I need

I have been able to achieve something like this with reactjs and cssthe carousel I have been able to make so far

But I have not been able to get the exact curve effect. Here is the code for the carousel I have been working on.

Carousel.js

import React, { useState } from "react";
import Slide from "../CarouselSlide/Slide";
import styles from "./Carousel.module.css";

const Carousel = ({ slides }) => {
  const [slideIndex, setSlideIndex] = useState(0);

  const handlePrevSlide = () => {
    setSlideIndex((prev) => (prev === 0 ? slides.length - 1 : prev - 1));
  };

  const handleNextSlide = () => {
    setSlideIndex((prev) => (prev + 1) % slides.length);
  };

  return (
    <section className={`${styles.slidesWrapper} w-full`}>
      <div className={styles.slides}>
        <button
          className={`${styles.prevSlideBtn} cursor-pointer`}
          onClick={handlePrevSlide}
        >
          <svg
            width="113"
            height="122"
            viewBox="0 0 113 122"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
          >
            <g filter="url(#filter0_d_429_1959)">
              <circle cx="51.8531" cy="60.8531" r="20.8531" fill="#F49B00" />
              <path
                d="M53.998 56.2952L49.4501 60.8531L53.998 65.411L52.5979 66.8111L46.6399 60.8531L52.5979 54.8951L53.998 56.2952Z"
                fill="white"
              />
            </g>
          </svg>
        </button>

        {[...slides, ...slides, ...slides].map((slide, i) => {
          let offset = slides.length + (slideIndex - i);

          if (typeof slide === "string") {
            return (
              <Slide
                image={slide.image}
                title={slide.title}
                author={slide.author}
                year={slide.year}
                offset={offset}
                key={i}
              />
            );
          } else {
            return (
              <Slide
                image={slide.image}
                title={slide.title}
                author={slide.author}
                year={slide.year}
                offset={offset}
                key={i}
              />
            );
          }
        })}
        <button
          className={`${styles.nextSlideBtn} cursor-pointer`}
          onClick={handleNextSlide}
        >
          <svg
            width="122"
            height="122"
            viewBox="0 0 122 122"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
          >
            <g filter="url(#filter0_d_429_1955)">
              <circle cx="61.147" cy="60.8531" r="20.8531" fill="#F49B00" />
              <path
                d="M57.0161 65.411L61.5641 60.8531L57.0161 56.2952L58.4162 54.8951L64.3743 60.8531L58.4162 66.8111L57.0161 65.411Z"
                fill="white"
              />
            </g>
          </svg>
        </button>
      </div>
    </section>
  );
};

export default Carousel;

Carousel.module.css

.slidesWrapper {
  @apply min-h-[630px] flex items-center justify-center overflow-hidden;
}

.slidesWrapper *,
.slidesWrapper *::before,
.slidesWrapper *::after {
  @apply relative;
}

.slides {
  @apply grid;
}

.prevSlideBtn,
.nextSlideBtn {
  @apply appearance-none absolute z-[5]  self-center;
}

.prevSlideBtn {
  @apply -left-[37rem];
}

.nextSlideBtn {
  @apply -right-[35rem];
}

Code for a Slide
Slide.js

import { useRef, useEffect } from "react";
import CardContent from "@mui/material/CardContent";
import Typography from "@mui/material/Typography";
import CardMedia from "@mui/material/CardMedia";
import Card from "@mui/material/Card";
import styles from "./Slide.module.css";

const Slide = ({ image, title, author, year, offset }) => {
  return (
    <div
      className={styles.slide}
      style={{
        "--offset": offset,
        "--dir": offset === 0 ? 0 : offset > 0 ? 0.8 : -0.8,
      }}
    >
      <div className={styles.slideContent}>
        <Card sx={{ boxShadow: 8, borderRadius: 3 }}>
          <CardMedia
            component="img"
            alt="Self-Portrait with Grey Felt Hat"
            image={image}
            sx={{
              height: 350,
              maxWidth: 350,
              objectFit: "fill",
            }}
          />
          <CardContent>
            <Typography
              gutterBottom
              variant="subtitle2"
              fontWeight="bold"
              component="div"
            >
              {title || "Self-Portrait with Grey Felt Hat"}
            </Typography>
            <div className="flex">
              <Typography variant="body2" color="text.secondary">
                {author || "Vincent van Gogh"}
              </Typography>
              <Typography
                component="div"
                variant="body2"
                color="text.secondary"
                className="ml-auto"
              >
                {year || "1887"}
              </Typography>
            </div>
          </CardContent>
        </Card>
      </div>
    </div>
  );
};

export default Slide;

Slide.module.css

.slideContent {
  @apply transition-transform duration-[0.5s] ease-[ease-in-out] grid content-center rounded-[15px];
  transform-style: preserve-3d;
  transform: perspective(3000px) translateX(calc(100% * var(--offset)))
    rotateY(calc(-45deg * var(--dir)));
}

.slide {
  grid-area: 1 / -1;
}

I tried adjusting the height of specific slides using The :nth-child() CSS pseudo-class. But it doesn’t seem to be working.

3

Answers


  1. I will not give you a ready code, but will try to explain you the problem.
    This image shows what you want to achieve enter image description here

    The pink lines are your Slides, the green dotted circle is a Carousel. For each Slide you need to calculate:

    • delta z, which is the distance between center of each slide casted on Z axis
    • delta x, which is the distance between center of each slide casted on X axis
    • alpha angle, you can calculate this as tan alpha = delta z / delta x (this is not exact value, but for bigger circle radiuses, the error will be very small)

    When you have this, the css implementation will be easy, just transform X by delta X, transform Z by delta Z and rotate Y by alpha angle

    Login or Signup to reply.
  2. Your problem is not about react, it is just about CSS transforms.

    Take a look at the below implementation.

    You can play with the perspective value to adjust the final look.

    .container {
        height: 200px;
        transform-style: preserve-3d;
        perspective-origin: 600px center;
        perspective: 800px;
        margin-top: 50px;
    }
    
    .card {
        border: solid 1px blue;
        background-color: lightblue;
        border-radius: 10px;
        height: 200px;
        width: 200px;
        position: absolute;
        transform-style: preserve-3d;
        transform: rotateY(var(--angle));
        transform-origin: center 0 1200px; 
        left: 500px;
    }
    
    #c1 {
        --angle: 20deg;
    }
    #c2 {
        --angle: 10deg;
    }
    #c3 {
        --angle: 0deg;
    }
    #c4 {
        --angle: -10deg;
    }
    #c5 {
        --angle: -20deg;
    }
    <div class="container">
        <div class="card" id="c1">CARD 1</div>
        <div class="card" id="c2">CARD 2</div>
        <div class="card" id="c3">CARD 3</div>
        <div class="card" id="c4">CARD 4</div>
        <div class="card" id="c5">CARD 5</div>
    </div>
    Login or Signup to reply.
  3. The Swiper Slider has a similar effect. You can change the angle or depth in coverflowEffect option. I pasted and slightly modified the basic example:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="utf-8" />
      <title>Swiper demo</title>
      <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1" />
      <!-- Link Swiper's CSS -->
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.min.css" />
    
      <!-- Demo styles -->
      <style>
        html,
        body {
          position: relative;
          height: 100%;
        }
    
        body {
          background: #eee;
          font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
          font-size: 14px;
          color: #000;
          margin: 0;
          padding: 0;
        }
    
        .swiper {
          width: 100%;
          padding-top: 50px;
          padding-bottom: 50px;
        }
    
        .swiper-slide {
          background-position: center;
          background-size: cover;
          width: 100px;
          height: 100px;
        }
    
        .swiper-slide img {
          display: block;
          width: 100%;
          height: 100%;
          object-fit: cover;
        }
      </style>
    </head>
    
    <body>
      <!-- Swiper -->
      <div class="swiper mySwiper">
        <div class="swiper-wrapper">
          <div class="swiper-slide">
            <img src="https://swiperjs.com/demos/images/nature-1.jpg" />
          </div>
          <div class="swiper-slide">
            <img src="https://swiperjs.com/demos/images/nature-2.jpg" />
          </div>
          <div class="swiper-slide">
            <img src="https://swiperjs.com/demos/images/nature-3.jpg" />
          </div>
          <div class="swiper-slide">
            <img src="https://swiperjs.com/demos/images/nature-4.jpg" />
          </div>
          <div class="swiper-slide">
            <img src="https://swiperjs.com/demos/images/nature-5.jpg" />
          </div>
          <div class="swiper-slide">
            <img src="https://swiperjs.com/demos/images/nature-6.jpg" />
          </div>
          <div class="swiper-slide">
            <img src="https://swiperjs.com/demos/images/nature-7.jpg" />
          </div>
          <div class="swiper-slide">
            <img src="https://swiperjs.com/demos/images/nature-8.jpg" />
          </div>
          <div class="swiper-slide">
            <img src="https://swiperjs.com/demos/images/nature-9.jpg" />
          </div>
        </div>
        <div class="swiper-pagination"></div>
      </div>
    
      <!-- Swiper JS -->
      <script src="https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.min.js"></script>
    
      <!-- Initialize Swiper -->
      <script>
        var swiper = new Swiper(".mySwiper", {
          effect: "coverflow",
          grabCursor: true,
          centeredSlides: true,
          slidesPerView: "auto",
          spaceBetween: 10,
          loop: true,
          loopedSlides: 8,
          coverflowEffect: {
            rotate: 80,
            depth: -100,
            modifier: .2,
            scale: 1.2,
          },
          pagination: {
            el: ".swiper-pagination",
            clickable: true,
          },
        });
      </script>
    </body>
    
    </html>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search