Build an animated testimonial card using HTML, CSS and JavaScript.
Tags: codenewbie, webdev, javascript, css
Published: 2019-11-28 18:45:08

Originally posted on [my blog](https://www.ibrahima-ndaw.com/blog/build-an-animated-testimonial-card/) <hr>
In this post, we are going to have some good time with CSS animations and DOM manipulation by building an animated testimonial card using HTML, CSS, and JavaScript.

You can check it live [here](https://codepen.io/ibrahima92/full/mdbLmVX)

- [HTML](#html)
- [CSS](#css)
- [JavaScript](#javascript)

## HTML

We start by wrapping our elements in the `main` tag.

```html
<main>
  <!--This is the current testimonial-->
  <div class="testimonial-container testimonial-active">
    <div class="testimonial-header"></div>
    <div class="testimonial-body">
      <img alt="Avatar" src="" class="testimonial-avatar" />
      <h1></h1>
      <p></p>
    </div>
    <div class="testimonial-footer">
      <div>
        <span><i class="fab fa-google"></i></span>
        <span><i class="fab fa-linkedin"></i></span>
        <span><i class="fab fa-twitter"></i></span>
      </div>
      <div>
        <button id="next">
          <i class="fa fa-3x fa-chevron-circle-right"></i>
        </button>
      </div>
    </div>
  </div>
```

We gonna have two main `div`, the first will be used for the actual testimonial card and the second in the code block below will help us to show the next testimonial card.<br>
Notice that the HTML content will be added through javaScript.

```html
      <!--This is the next testimonial-->
      <div class="testimonial-ghost-container">
        <div class="testimonial-ghost-header"></div>
        <div class="testimonial-ghost-body">
          <img alt="Avatar" src="" />
          <h1></h1>
          <p></p>
        </div>
        <div class="testimonial-ghost-footer">
          <div>
            <span><i class="fab fa-google"></i></span>
            <span><i class="fab fa-linkedin"></i></span>
            <span><i class="fab fa-twitter"></i></span>
          </div>
          <div>
            <button id="ghost-next">
              <i class="fa fa-3x fa-chevron-circle-right"></i>
            </button>
          </div>
        </div>
      </div>
    </main>
```

As I said earlier, this `div` will be hidden at the start. But when we switch to the next testimonial, it will be used to show the two testimonial cards at the same time. <br>

## CSS

As usual, we start the CSS part with some resets.

```css
@import url("https://fonts.googleapis.com/css?family=Roboto:400,400i,700&display=swap");

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  background: #f5f6f7;
  line-height: 1.6;
  font-family: "Roboto", sans-serif;
}

main {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  width: 100%;
  max-width: 100%;
  position: relative;
}
```

Then, change the font, set the background of the `body` to a light-grey color. Next, the `main` tag takes the full width and height, and we use `display: flex` to literally bring the testimonial card to the center of the viewport.

```css
.testimonial-container,
.testimonial-ghost-container {
  width: 22rem;
  height: 28rem;
  background: #fff;
  border-radius: 1.2rem;
  overflow: hidden;
  position: absolute;
}
.testimonial-active {
  z-index: 1;
  box-shadow: 0.5rem 0.5rem 1rem rgba(51, 51, 51, 0.2), 0.5rem 0.5rem 1rem rgba(51, 51, 51, 0.2);
}

.testimonial-header,
.testimonial-ghost-header {
  height: 10rem;
  background-image: linear-gradient(
      to right,
      rgba(239, 124, 0, 0.8),
      rgba(255, 181, 102, 0.8)
    ), url("https://shorturl.at/grwP6");
  background-size: cover;
  background-position: cover;
}
```

We continue by styling our two card elements. In the `.testimonial-container` and `.testimonial-ghost-container` classes, we use `position: absolute;` to sit these two elements one over the other following the `main` tag position. Then, the `.testimonial-active` class will help us bring the active testimonial card to the front. <br>
the next two classes are used to style the card header. It will have an image doubled by a gradient color as a background.

```css
.testimonial-avatar,
.testimonial-ghost-body img {
  border-radius: 100%;
  display: block;
  margin: auto;
  margin-top: -4rem;
  border: 0.5rem solid #fff;
  z-index: 100;
}

.testimonial-body,
.testimonial-ghost-body {
  padding: 0 1rem;
  text-align: center;
  margin-bottom: 1rem;
}
```

This part styles the avatar of our card. We use a negative value `-4rem` on the `margin-top` property to bring the avatar in the middle of the card header and the `z-index` property ensures that the element will be always at the top of the header.

```css
.testimonial-ghost-header {
  background-image: linear-gradient(
      to right,
      rgba(119, 119, 119, 0.8),
      rgba(119, 119, 119, 0.8)
    ), url("https://shorturl.at/grwP6");
}

.testimonial-ghost-body img {
  filter: blur(2px);
}

.testimonial-ghost-body h1,
.testimonial-ghost-body p i,
.testimonial-ghost-footer button i,
.testimonial-ghost-footer span i {
  color: #777;
}

.testimonial-footer,
.testimonial-ghost-footer {
  display: flex;
  justify-content: space-between;
  padding: 1rem;
}
```

When a change occurs, the previous testimonial card's style changes. the avatar will be blurred with `filter: blur(2px);`. The card header and elements color will be turned to dark, just for having a nice style.

```css
.testimonial-active-animated {
  animation: moveRight 1.5s ease-in-out;
}

.testimonial-inactive-animated {
  animation: moveLeft 1.5s ease-in-out;
}

@keyframes moveRight {
  0% {
    transform: translateX(0);
    box-shadow: none;
  }
  50% {
    transform: translateX(-10rem);
    box-shadow: none;
  }
  100% {
    transform: translateX(0);
  }
}

@keyframes moveLeft {
  0% {
    transform: translateX(0);
    opacity: 1;
    z-index: 2;
  }
  50% {
    transform: translateX(18rem) scale(0.96);
    opacity: 0.7;
  }
  100% {
    transform: translateX(0) scale(0.98);
    opacity: 0.2;
  }
}
```

This code block will be essential when it comes to switching to the next testimonial. We have two animations: the first `moveRight` will move the element from the left to the right with the `transform` property and the `box-shadow` will be hidden to just have a more natural effect.<br>
The second animation `moveLeft` will move from the left to the right and scale down a little bit with `transform: translateX(18rem) scale(0.96)`. It will also have a fade-in effect with the `opacity` property. And the `z-index` property will place the element at the top when the animation starts.<br>
The `.testimonial-active-animated` and `.testimonial-active-animated` will be attached to the appropriate testimonial cards.

## JavaScript

As you can see here, we start by selecting the two testimonial containers.

```javascript
const testimonialContainer = document.querySelector(".testimonial-container");
const testimonialGhost = document.querySelector(".testimonial-ghost-container");
const nextBtn = document.querySelector("#next");
const testimonials = [
  {
    name: "Sarah Drucker",
    text:
      "Working with John Doe was a real pleasure, he helps me extending my business online.",
    avatar: "https://shorturl.at/eqyGW"
  },
  {
    name: "Nicolas Jaylen",
    text:
      "My business was broken, then i start working with John Doe, and now everything works fine.",
    avatar: "https://shorturl.at/ptC58"
  },
  {
    name: "Awa Fall",
    text:
      "John Doe helps me a lot from designing my website to make it live in just 5 weeks.",
    avatar: "https://shorturl.at/lwBY1"
  }
];
let counter = 0;
```

Then, we have a button for listening to the click event and an array of testimonials that will be displayed dynamically following the `counter` variable.

```javascript
const handleFirstTestimonial = () => {
  // Author avatar selection
  testimonialContainer.children[1].children[0].src = testimonials[0].avatar;
  // Testimonial Author selection
  testimonialContainer.children[1].children[1].innerHTML = testimonials[0].name;
  // Testimonial text selection
  testimonialContainer.children[1].children[2].innerHTML = `
  <i class="fas fa-quote-left"></i>
  ${testimonials[0].text}
  <i class="fas fa-quote-right"></i>
  `;
};
```

The `handleFirstTestimonial()` function helps us showing the first testimonial of the array. Here, we traverse the DOM through the `testimonialContainer` element to select child elements. We set the avatar, the author of the testimonial, and the text with the first testimonial on the `testimonials` array.

```javascript
const activeTestimonial = () => {
  testimonialContainer.classList.add("testimonial-active-animated");
  // Author avatar selection
  testimonialContainer.children[1].children[0].src =
    testimonials[counter].avatar;
  // Testimonial Author selection
  testimonialContainer.children[1].children[1].innerHTML =
    testimonials[counter].name;
  // Testimonial text selection
  testimonialContainer.children[1].children[2].innerHTML = `<i class="fas fa-quote-left"></i>
  ${testimonials[counter].text}
  <i class="fas fa-quote-right"></i>`;

  setTimeout(() => {
    // Remove the active animated class
    testimonialContainer.classList.remove("testimonial-active-animated");
  }, 1400);
};
```

Then, when the user switches to the next testimonial, we call the `activeTestimonial()` function to handle it. And, use the `testimonialContainer` to traverse the DOM and set appropriate data to the card elements. And make the animation happen with `testimonialContainer.classList.add("testimonial-active-animated");`, and finally, remove the animation after 1.4 seconds to be able to animate it again.

```javascript
const inactiveTestimonial = () => {
  testimonialGhost.classList.add("testimonial-inactive-animated");
  let newCounter = counter;
  if (newCounter === 0) {
    newCounter = testimonials.length;
  }
  // image selection
  testimonialGhost.children[1].children[0].src =
    testimonials[newCounter - 1].avatar;
  // title selection
  testimonialGhost.children[1].children[1].innerHTML =
    testimonials[newCounter - 1].name;
  // text selection
  testimonialGhost.children[1].children[2].innerHTML = `<i class="fas fa-quote-left"></i>
  ${testimonials[newCounter - 1].text}
  <i class="fas fa-quote-right"></i>`;
  setTimeout(() => {
    // Remove the active animated class
    testimonialGhost.classList.remove("testimonial-inactive-animated");
  }, 1400);
};
```

Like the `activeTestimonial()`, the `inactiveTestimonial` function will handle the inactive testimonial card. We traverse the DOM with `testimonialGhost` to select elements and set the data to the previous testimonial card.<br>
Here, we use a `newCounter` to just handle the `testimonials` array if the `counter` is equal to `0`, we reassign the `newCounter` with the last testimonial card of the array.

```javascript
nextBtn.addEventListener("click", () => {
  if (counter === testimonials.length - 1) {
    counter = 0;
    inactiveTestimonial();
    activeTestimonial();
  } else {
    counter++;
    inactiveTestimonial();
    activeTestimonial();
  }
});

handleFirstTestimonial();
```

To make all the magic happen, we need to listen to the click event. And check if the counter is equal to the last element of the array. If it's the case reinitialize the `counter` to `0` and call the needed functions. Otherwise, increment the `counter` variable and call `inactiveTestimonial()` and `activeTestimonial()`.<br>
Then, to start everything when the page loads, we call the `handleFirstTestimonial()` function.<br>

That's all folks

You can check it live [here](https://codepen.io/ibrahima92/full/mdbLmVX) <br>
![fullscreen-slider](https://drive.google.com/uc?id=15u8qi_VlsgH7BMpbLKvAV-BcAPO4Nxk3)

| [TWITTER](https://twitter.com/ibrahima92_) | [BLOG](https://www.ibrahima-ndaw.com/) | [NEWSLETTER](https://ibrahima-ndaw.us5.list-manage.com/subscribe?u=8dedf5d07c7326802dd81a866&id=5d7bcd5b75) | [GITHUB](https://github.com/ibrahima92) | [LINKEDIN](https://www.linkedin.com/in/ibrahima-ndaw/) | [CODEPEN](https://codepen.io/ibrahima92) | [DEV](https://dev.to/ibrahima92) |
| -------------- | ------------------------------------------ | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------- | ------------------------------------------------------ | ---------------------------------------- | -------------------------------- |