Building an Amazing Responsive Carousel in React

carousel

In this tutorial, we will explore the implementation of a responsive testimonial carousel in React. Testimonial carousels are commonly used to showcase user feedback, reviews, or other relevant content in a visually appealing way. We’ll break down the provided code and provide a detailed explanation for each section.

Overview:

  1. React Component Structure:
    • Understand the structure of the React component, including state management and rendering logic.
  2. CSS Styling:
    • Dive into the CSS styles responsible for the appearance and animations of the testimonial carousel.

React Component Structure:

// Import React and useEffect, useState hooks
import React, { useEffect, useState } from "react";
import "./App.css"; // Import CSS styles

// Testimonial Component
const Testimonial = ({ item, isActive, opacity }) => (
  <div
    className={`testimonial ${isActive ? "active" : "inactive"}`}
    style={{ opacity }}
  >
    {/* Testimonial Content */}
    <div className="img">
      <img src={item.imgSrc} alt="" />
    </div>
    <h2>{item.name}</h2>
    <p>{item.text}</p>
  </div>
);

// Main App Component
const App = () => {
  // State for managing the carousel
  const [state, setState] = useState({
    currentSlide: 0,
    currentActive: 0,
    inactiveTestimonialOpacity: 1,
  });

  // Destructure state values for easy access
  const { currentSlide, currentActive, inactiveTestimonialOpacity } = state;

  // Animation duration for transitions
  const animationDuration = 500;

  // useEffect for handling transitions when changing slides
  useEffect(() => {
    if (currentActive !== currentSlide) {
      setState((prevState) => ({
        ...prevState,
        inactiveTestimonialOpacity: 0,
      }));

      const timeoutId = setTimeout(() => {
        setState((prevState) => ({
          ...prevState,
          inactiveTestimonialOpacity: 1,
        }));
      }, animationDuration);

      return () => clearTimeout(timeoutId);
    }
  }, [currentActive, currentSlide]);

  // Function to handle slide changes
  const playSlide = (slide) => {
    // Update currentSlide and currentActive in the state
    setState((prevState) => ({
      ...prevState,
      currentSlide: slide,
      currentActive: slide,
    }));

    // Wrap around if reaching the last or first slide
    if (slide < 0) slide = currentSlide = testimContent.length - 1;
    if (slide > testimContent.length - 1) slide = currentSlide = 0;

    // Perform opacity transition for testimonial content
    if (currentActive !== currentSlide) {
      setState((prevState) => ({
        ...prevState,
        inactiveTestimonialOpacity: 0,
      }));

      setTimeout(() => {
        setState((prevState) => ({
          ...prevState,
          inactiveTestimonialOpacity: 1,
        }));
      }, animationDuration);
    }
  };

  // Function to handle arrow clicks
  const handleArrowClick = (direction) => {
    const newSlide =
      direction === "left" ? currentSlide - 1 : currentSlide + 1;
    playSlide(newSlide);
  };

  // Function to handle dot clicks
  const handleDotClick = (index) => {
    playSlide(index);
  };

  // Testimonial data
  const testimContent = [
    // Testimonial objects with image source, name, and text
    // ...
  ];

  return (
    // Testimonial Carousel Section
    <section id="testim" className="testim">
      <div className="wrap">
        {/* Left and Right arrows for navigation */}
        <span
          id="right-arrow"
          className="arrow right fa fa-chevron-right"
          onClick={() => handleArrowClick("right")}
        ></span>
        <span
          id="left-arrow"
          className="arrow left fa fa-chevron-left"
          onClick={() => handleArrowClick("left")}
        ></span>

        {/* Dots for navigation */}
        <ul id="testim-dots" className="dots">
          {testimContent.map((item, index) => (
            <li
              key={index}
              className={`dot ${index === currentSlide ? "active" : ""}`}
              onClick={() => handleDotClick(index)}
            ></li>
          ))}
        </ul>

        {/* Testimonial Content Container */}
        <div id="testim-content" className="cont">
          {testimContent.map((item, index) => (
            // Render Testimonial component for each testimonial object
            <Testimonial
              key={index}
              item={item}
              isActive={index === currentSlide}
              opacity={
                index === currentActive ? 1 : inactiveTestimonialOpacity
              }
            />
          ))}
        </div>
      </div>
    </section>
  );
};

export default App;
JSX

CSS Styling:

@import url('https://fonts.googleapis.com/css2?family=Poppins&family=Roboto&display=swap');
*,
*:after,
*:before {
  margin: 0;
  padding: 0;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  -ms-box-sizing: border-box;
  -o-box-sizing: border-box;
  box-sizing: border-box;
  -webkit-user-select: none;
  -ms-user-select: none;
  -o-user-select: none;
  -moz-user-select: none;
  user-select: none;
  cursor: default;
}
html {
  width: 100%;
  height: auto;
}
body {
  width: 100%;
  height: auto;
  font-size: 16px;
  font-family: 'Roboto', sans-serif;
  background: rgba(30, 30, 30);
}
.testim {
  width: 100%;
  position: absolute;
  top: 50%;
  -webkit-transform: translatey(-50%);
  -moz-transform: translatey(-50%);
  -ms-transform: translatey(-50%);
  -o-transform: translatey(-50%);
  transform: translatey(-50%);
}
.testim .wrap {
  position: relative;
  width: 100%;
  max-width: 1020px;
  padding: 40px 20px;
  margin: auto;
}
.testim .arrow {
  display: block;
  position: absolute;
  color: #eee;
  cursor: pointer;
  font-size: 2em;
  top: 50%;
  -webkit-transform: translateY(-50%);
  -ms-transform: translateY(-50%);
  -moz-transform: translateY(-50%);
  -o-transform: translateY(-50%);
  transform: translateY(-50%);
  -webkit-transition: all 0.3s ease-in-out;
  -ms-transition: all 0.3s ease-in-out;
  -moz-transition: all 0.3s ease-in-out;
  -o-transition: all 0.3s ease-in-out;
  transition: all 0.3s ease-in-out;
  padding: 5px;
  z-index: 22222222;
}
.testim .arrow:before {
  cursor: pointer;
}
.testim .arrow:hover {
  color: #ea830e;
}
.testim .arrow.left {
  left: 10px;
}
.testim .arrow.right {
  right: 10px;
}
.testim .dots {
  text-align: center;
  position: absolute;
  width: 100%;
  bottom: 60px;
  left: 0;
  display: block;
  z-index: 3333;
  height: 12px;
}
.testim .dots .dot {
  list-style-type: none;
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1px solid #eee;
  margin: 0 10px;
  cursor: pointer;
  -webkit-transition: all 0.5s ease-in-out;
  -ms-transition: all 0.5s ease-in-out;
  -moz-transition: all 0.5s ease-in-out;
  -o-transition: all 0.5s ease-in-out;
  transition: all 0.5s ease-in-out;
  position: relative;
}
.testim .dots .dot.active,
.testim .dots .dot:hover {
  background: #ea830e;
  border-color: #ea830e;
}
.testim .dots .dot.active {
  -webkit-animation: testim-scale 0.5s ease-in-out forwards;
  -moz-animation: testim-scale 0.5s ease-in-out forwards;
  -ms-animation: testim-scale 0.5s ease-in-out forwards;
  -o-animation: testim-scale 0.5s ease-in-out forwards;
  animation: testim-scale 0.5s ease-in-out forwards;
}
.testim .cont {
  position: relative;
  overflow: hidden;
}
.testim .cont > div {
  text-align: center;
  position: absolute;
  top: 0;
  left: 0;
  padding: 0 0 70px 0;
  opacity: 0;
}
.testim .cont > div.inactive {
  opacity: 1;
}
.testim .cont > div.active {
  position: relative;
  opacity: 1;
}
.testim .cont div .img img {
  display: block;
  width: 100px;
  height: 100px;
  margin: auto;
  border-radius: 50%;
}
.testim .cont div h2 {
  color: #ea830e;
  font-size: 1em;
  margin: 15px 0;
}
.testim .cont div p {
  font-size: 1.15em;
  color: #eee;
  width: 80%;
  margin: auto;
}
.testim .cont div.active .img img {
  -webkit-animation: testim-show 0.5s ease-in-out forwards;
  -moz-animation: testim-show 0.5s ease-in-out forwards;
  -ms-animation: testim-show 0.5s ease-in-out forwards;
  -o-animation: testim-show 0.5s ease-in-out forwards;
  animation: testim-show 0.5s ease-in-out forwards;
}
.testim .cont div.active h2 {
  -webkit-animation: testim-content-in 0.4s ease-in-out forwards;
  -moz-animation: testim-content-in 0.4s ease-in-out forwards;
  -ms-animation: testim-content-in 0.4s ease-in-out forwards;
  -o-animation: testim-content-in 0.4s ease-in-out forwards;
  animation: testim-content-in 0.4s ease-in-out forwards;
}
.testim .cont div.active p {
  -webkit-animation: testim-content-in 0.5s ease-in-out forwards;
  -moz-animation: testim-content-in 0.5s ease-in-out forwards;
  -ms-animation: testim-content-in 0.5s ease-in-out forwards;
  -o-animation: testim-content-in 0.5s ease-in-out forwards;
  animation: testim-content-in 0.5s ease-in-out forwards;
}
.testim .cont div.inactive .img img {
  -webkit-animation: testim-hide 0.5s ease-in-out forwards;
  -moz-animation: testim-hide 0.5s ease-in-out forwards;
  -ms-animation: testim-hide 0.5s ease-in-out forwards;
  -o-animation: testim-hide 0.5s ease-in-out forwards;
  animation: testim-hide 0.5s ease-in-out forwards;
}
.testim .cont div.inactive h2 {
  -webkit-animation: testim-content-out 0.4s ease-in-out forwards;
  -moz-animation: testim-content-out 0.4s ease-in-out forwards;
  -ms-animation: testim-content-out 0.4s ease-in-out forwards;
  -o-animation: testim-content-out 0.4s ease-in-out forwards;
  animation: testim-content-out 0.4s ease-in-out forwards;
}
.testim .cont div.inactive p {
  -webkit-animation: testim-content-out 0.5s ease-in-out forwards;
  -moz-animation: testim-content-out 0.5s ease-in-out forwards;
  -ms-animation: testim-content-out 0.5s ease-in-out forwards;
  -o-animation: testim-content-out 0.5s ease-in-out forwards;
  animation: testim-content-out 0.5s ease-in-out forwards;
}
@-webkit-keyframes testim-scale {
  0% {
    -webkit-box-shadow: 0px 0px 0px 0px #eee;
    box-shadow: 0px 0px 0px 0px #eee;
  }
  35% {
    -webkit-box-shadow: 0px 0px 10px 5px #eee;
    box-shadow: 0px 0px 10px 5px #eee;
  }
  70% {
    -webkit-box-shadow: 0px 0px 10px 5px #ea830e;
    box-shadow: 0px 0px 10px 5px #ea830e;
  }
  100% {
    -webkit-box-shadow: 0px 0px 0px 0px #ea830e;
    box-shadow: 0px 0px 0px 0px #ea830e;
  }
}
@-moz-keyframes testim-scale {
  0% {
    -moz-box-shadow: 0px 0px 0px 0px #eee;
    box-shadow: 0px 0px 0px 0px #eee;
  }
  35% {
    -moz-box-shadow: 0px 0px 10px 5px #eee;
    box-shadow: 0px 0px 10px 5px #eee;
  }
  70% {
    -moz-box-shadow: 0px 0px 10px 5px #ea830e;
    box-shadow: 0px 0px 10px 5px #ea830e;
  }
  100% {
    -moz-box-shadow: 0px 0px 0px 0px #ea830e;
    box-shadow: 0px 0px 0px 0px #ea830e;
  }
}
@-ms-keyframes testim-scale {
  0% {
    -ms-box-shadow: 0px 0px 0px 0px #eee;
    box-shadow: 0px 0px 0px 0px #eee;
  }
  35% {
    -ms-box-shadow: 0px 0px 10px 5px #eee;
    box-shadow: 0px 0px 10px 5px #eee;
  }
  70% {
    -ms-box-shadow: 0px 0px 10px 5px #ea830e;
    box-shadow: 0px 0px 10px 5px #ea830e;
  }
  100% {
    -ms-box-shadow: 0px 0px 0px 0px #ea830e;
    box-shadow: 0px 0px 0px 0px #ea830e;
  }
}
@-o-keyframes testim-scale {
  0% {
    -o-box-shadow: 0px 0px 0px 0px #eee;
    box-shadow: 0px 0px 0px 0px #eee;
  }
  35% {
    -o-box-shadow: 0px 0px 10px 5px #eee;
    box-shadow: 0px 0px 10px 5px #eee;
  }
  70% {
    -o-box-shadow: 0px 0px 10px 5px #ea830e;
    box-shadow: 0px 0px 10px 5px #ea830e;
  }
  100% {
    -o-box-shadow: 0px 0px 0px 0px #ea830e;
    box-shadow: 0px 0px 0px 0px #ea830e;
  }
}
@keyframes testim-scale {
  0% {
    box-shadow: 0px 0px 0px 0px #eee;
  }
  35% {
    box-shadow: 0px 0px 10px 5px #eee;
  }
  70% {
    box-shadow: 0px 0px 10px 5px #ea830e;
  }
  100% {
    box-shadow: 0px 0px 0px 0px #ea830e;
  }
}
@-webkit-keyframes testim-content-in {
  from {
    opacity: 0;
    -webkit-transform: translateY(100%);
    transform: translateY(100%);
  }
  to {
    opacity: 1;
    -webkit-transform: translateY(0);
    transform: translateY(0);
  }
}
@-moz-keyframes testim-content-in {
  from {
    opacity: 0;
    -moz-transform: translateY(100%);
    transform: translateY(100%);
  }
  to {
    opacity: 1;
    -moz-transform: translateY(0);
    transform: translateY(0);
  }
}
@-ms-keyframes testim-content-in {
  from {
    opacity: 0;
    -ms-transform: translateY(100%);
    transform: translateY(100%);
  }
  to {
    opacity: 1;
    -ms-transform: translateY(0);
    transform: translateY(0);
  }
}
@-o-keyframes testim-content-in {
  from {
    opacity: 0;
    -o-transform: translateY(100%);
    transform: translateY(100%);
  }
  to {
    opacity: 1;
    -o-transform: translateY(0);
    transform: translateY(0);
  }
}
@keyframes testim-content-in {
  from {
    opacity: 0;
    transform: translateY(100%);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
@-webkit-keyframes testim-content-out {
  from {
    opacity: 1;
    -webkit-transform: translateY(0);
    transform: translateY(0);
  }
  to {
    opacity: 0;
    -webkit-transform: translateY(-100%);
    transform: translateY(-100%);
  }
}
@-moz-keyframes testim-content-out {
  from {
    opacity: 1;
    -moz-transform: translateY(0);
    transform: translateY(0);
  }
  to {
    opacity: 0;
    -moz-transform: translateY(-100%);
    transform: translateY(-100%);
  }
}
@-ms-keyframes testim-content-out {
  from {
    opacity: 1;
    -ms-transform: translateY(0);
    transform: translateY(0);
  }
  to {
    opacity: 0;
    -ms-transform: translateY(-100%);
    transform: translateY(-100%);
  }
}
@-o-keyframes testim-content-out {
  from {
    opacity: 1;
    -o-transform: translateY(0);
    transform: translateY(0);
  }
  to {
    opacity: 0;
    transform: translateY(-100%);
    transform: translateY(-100%);
  }
}
@keyframes testim-content-out {
  from {
    opacity: 1;
    transform: translateY(0);
  }
  to {
    opacity: 0;
    transform: translateY(-100%);
  }
}
@-webkit-keyframes testim-show {
  from {
    opacity: 0;
    -webkit-transform: scale(0);
    transform: scale(0);
  }
  to {
    opacity: 1;
    -webkit-transform: scale(1);
    transform: scale(1);
  }
}
@-moz-keyframes testim-show {
  from {
    opacity: 0;
    -moz-transform: scale(0);
    transform: scale(0);
  }
  to {
    opacity: 1;
    -moz-transform: scale(1);
    transform: scale(1);
  }
}
@-ms-keyframes testim-show {
  from {
    opacity: 0;
    -ms-transform: scale(0);
    transform: scale(0);
  }
  to {
    opacity: 1;
    -ms-transform: scale(1);
    transform: scale(1);
  }
}
@-o-keyframes testim-show {
  from {
    opacity: 0;
    -o-transform: scale(0);
    transform: scale(0);
  }
  to {
    opacity: 1;
    -o-transform: scale(1);
    transform: scale(1);
  }
}
@keyframes testim-show {
  from {
    opacity: 0;
    transform: scale(0);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}
@-webkit-keyframes testim-hide {
  from {
    opacity: 1;
    -webkit-transform: scale(1);
    transform: scale(1);
  }
  to {
    opacity: 0;
    -webkit-transform: scale(0);
    transform: scale(0);
  }
}
@-moz-keyframes testim-hide {
  from {
    opacity: 1;
    -moz-transform: scale(1);
    transform: scale(1);
  }
  to {
    opacity: 0;
    -moz-transform: scale(0);
    transform: scale(0);
  }
}
@-ms-keyframes testim-hide {
  from {
    opacity: 1;
    -ms-transform: scale(1);
    transform: scale(1);
  }
  to {
    opacity: 0;
    -ms-transform: scale(0);
    transform: scale(0);
  }
}
@-o-keyframes testim-hide {
  from {
    opacity: 1;
    -o-transform: scale(1);
    transform: scale(1);
  }
  to {
    opacity: 0;
    -o-transform: scale(0);
    transform: scale(0);
  }
}
@keyframes testim-hide {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0;
    transform: scale(0);
  }
}
@media all and (max-width: 300px) {
  body {
    font-size: 14px;
  }
}
@media all and (max-width: 500px) {
  .testim .arrow {
    font-size: 1.5em;
  }
  .testim .cont div p {
    line-height: 25px;
  }
}
CSS
  • Explanation:
    • The CSS styles define the layout, animations, and responsiveness of the testimonial carousel.
    • The carousel is centered on the page with arrows and dots for navigation.
    • Testimonials are displayed one at a time with opacity transitions for smooth animations.
    • The styles include media queries for responsive adjustments.

Detailed Explanation:

React Component Structure:

  1. Testimonial Component:
    • Represents an individual testimonial item.
    • Receives item, isActive, and opacity as props.
    • Renders the testimonial content, including an image, name, and text.
  2. App Component:
    • Manages the state for the carousel, including the current slide, current active testimonial, and opacity.
    • Uses the useEffect hook to handle opacity transitions when changing slides.
    • Provides functions for handling arrow clicks, dot clicks, and playing slides.
    • Renders the main structure of the testimonial carousel, including arrows, dots, and testimonial content.
  3. Testimonial Data:
    • testimContent is an array of testimonial objects, each containing an image source, name, and text.

CSS Styling:

  1. Global Styles:
    • Sets up default styles for the body and HTML.
  2. Carousel Container Styles:
    • Centers the carousel on the page, adjusting its position and width.
  3. Arrow Styles:
    • Defines styles for left and right arrows, including color, size, and hover effects.
  4. Dot Styles:
    • Styles dots for navigation with transitions for hover effects.
  5. Testimonial Content Styles:
    • Manages the layout and animations of testimonial content.
    • Uses animations for showing and hiding testimonials.
  6. Media Queries:
    • Adjusts styles for smaller screens to ensure responsiveness.

Conclusion:

By combining React for the logic and CSS for styling, we’ve created a responsive testimonial carousel. This component can be easily integrated into websites to showcase user testimonials in an engaging manner.

Feel free to customize the code further, experiment with different animations, or modify the testimonial data to suit your project’s needs.

Happy coding!

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *