Building Testimonial Slider in LWC

by Rijwan Mohmmed
2 comments
building-testimonial-slider-in-lwc

Hello folks, today we will discuss Building Testimonial Slider in LWC. Testimonials are a powerful way to showcase the positive experiences of your customers or users. Implementing a testimonial slider in Lightning Web Components (LWC) can elevate the user experience and add credibility to your website or application.

building-testimonial-slider-in-lwc

Also, check this: Efficient Server-Side Pagination in LWC: No Offset Approach

Key Highlights :

  1. Pure HTML CSS and Javascript slider. not use any third-party library.
  2. The collection of images can be automatically changed using a timer or manually when the user clicks the displayed buttons.
  3. Create Nav buttons to slide the testimonial.

Code :

LwcTestimonialSlider.HTML:

<template>
	<div class="body">
		<div class="wrapper">
			<i id="left" class="fa-solid" data-id="arrow">
                <lightning-icon icon-name="utility:back"></lightning-icon>
            </i>
			<ul class="carousel" lwc:dom="manual"></ul>
			<i id="right" class="fa-solid"><lightning-icon icon-name="utility:forward"></lightning-icon></i>
		</div>
	</div>
</template>

LwcTestimonialSlider.JS:

import { LightningElement } from 'lwc';
export default class LwcTestimonialSlider extends LightningElement {

    data = [
        { Name: 'Blanche Pearson', img: 'https://t4.ftcdn.net/jpg/03/50/40/93/240_F_350409330_2bqhjowfBmrqEia5U8lBsGrvD7h8EIo6.jpg', 'profession': 'Web Developer', review: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' },
        { Name: 'Joenas Brauers', img: 'https://t3.ftcdn.net/jpg/06/15/91/36/240_F_615913669_1GvdMMT0H44Z4owh9SCYsml6mCcy8g3G.jpg', 'profession': 'Sales Manager', review: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' },
        { Name: 'Lariach French', img: 'https://t4.ftcdn.net/jpg/06/81/01/43/240_F_681014359_dyMjwn4JYLtY985umiBOeytmLmVxEjC0.jpg', 'profession': 'App Designer', review: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' },
        { Name: 'James Khosravi', img: 'https://t4.ftcdn.net/jpg/03/50/40/93/240_F_350409330_2bqhjowfBmrqEia5U8lBsGrvD7h8EIo6.jpg', 'profession': 'Freelancer', review: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' },
        { Name: 'Rijwan', img: 'https://t3.ftcdn.net/jpg/03/99/91/62/240_F_399916297_1JwXdmC6ViCG4YhZuhLVz7xfuZhfHCY9.jpg', 'profession': 'Salesforce Expert', review: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' },
        { Name: 'Sachin', img: 'https://t3.ftcdn.net/jpg/02/60/67/80/240_F_260678009_gV1PBTotxDIwTngnmn3nYqWshbznYf24.jpg', 'profession': 'Salesforce Consultant', review: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' },
    ]

    renderedCallback() {
        this.data.forEach(ele => {
            this.template.querySelector('.carousel').innerHTML += '<li class="card"> <div class="img"> <img src="' + ele.img + '" alt="img" draggable="false"></div> <h2>' + ele.Name + '</h2> <span>' + ele.profession + '</span> <p class="testim"> ' + ele.review + ' </p></li>';
        })

        const wrapper = this.template.querySelector(".wrapper");
        const carousel = this.template.querySelector(".carousel");
        const firstCardWidth = carousel.querySelector(".card").offsetWidth;
        const arrowBtns = this.template.querySelectorAll(".wrapper i");
        const carouselChildrens = [...carousel.children];

        let isDragging = false;
        let isAutoPlay = true;
        let startX;
        let startScrollLeft;
        let timeoutId;

        // Get the number of cards that can fit in the carousel at once
        let cardPerView = Math.round(carousel.offsetWidth / firstCardWidth);

        // Insert copies of the last few cards to beginning of carousel for infinite scrolling
        carouselChildrens
            .slice(-cardPerView)
            .reverse()
            .forEach((card) => {
                carousel.insertAdjacentHTML("afterbegin", card.outerHTML);
            });


        carouselChildrens.slice(0, cardPerView).forEach((card) => {
            carousel.insertAdjacentHTML("beforeend", card.outerHTML);
        });

        // Scroll the carousel at appropriate postition to hide first few duplicate cards on Firefox
        carousel.classList.add("no-transition");
        carousel.scrollLeft = carousel.offsetWidth;
        carousel.classList.remove("no-transition");

        // Add event listeners for the arrow buttons to scroll the carousel left and right
        arrowBtns.forEach((btn) => {
            console.log(btn);
            console.log(btn.id);
            btn.addEventListener("click", () => {
                carousel.scrollLeft += btn.id.includes("left") ? -firstCardWidth : firstCardWidth;
            });
        });

        const dragStart = (e) => {
            isDragging = true;
            carousel.classList.add("dragging");
            // Records the initial cursor and scroll position of the carousel
            startX = e.pageX;
            startScrollLeft = carousel.scrollLeft;
        };

        const dragging = (e) => {
            if (!isDragging) return; // if isDragging is false return from here
            // Updates the scroll position of the carousel based on the cursor movement
            carousel.scrollLeft = startScrollLeft - (e.pageX - startX);
        };

        const dragStop = () => {
            isDragging = false;
            carousel.classList.remove("dragging");
        };

        const infiniteScroll = () => {
            // If the carousel is at the beginning, scroll to the end
            if (carousel.scrollLeft === 0) {
                carousel.classList.add("no-transition");
                carousel.scrollLeft = carousel.scrollWidth - 2 * carousel.offsetWidth;
                carousel.classList.remove("no-transition");
            }
            // If the carousel is at the end, scroll to the beginning
            else if (
                Math.ceil(carousel.scrollLeft) ===
                carousel.scrollWidth - carousel.offsetWidth
            ) {
                carousel.classList.add("no-transition");
                carousel.scrollLeft = carousel.offsetWidth;
                carousel.classList.remove("no-transition");
            }

            // Clear existing timeout & start autoplay if mouse is not hovering over carousel
            clearTimeout(timeoutId);
            if (!wrapper.matches(":hover")) autoPlay();
        };

        const autoPlay = () => {
            if (window.innerWidth < 800 || !isAutoPlay) return; // Return if window is smaller than 800 or isAutoPlay is false
            // Autoplay the carousel after every 2500 ms
            timeoutId = setTimeout(() => (carousel.scrollLeft += firstCardWidth), 2500);
        };
        autoPlay();

        carousel.addEventListener("mousedown", dragStart);
        carousel.addEventListener("mousemove", dragging);
        document.addEventListener("mouseup", dragStop);
        carousel.addEventListener("scroll", infiniteScroll);
        wrapper.addEventListener("mouseenter", () => clearTimeout(timeoutId));
        wrapper.addEventListener("mouseleave", autoPlay);
    }
}

LwcTestimonialSlider.CSS :

.body {
	display: flex;
	padding: 0 35px;
	align-items: center;
	justify-content: center;
	min-height: 100vh;
	background: linear-gradient(to left top, #031a9a, #8b53ff);
}
.wrapper {
	max-width: 1100px;
	width: 100%;
	position: relative;
}
.wrapper i {
	top: 50%;
	height: 50px;
	width: 50px;
	cursor: pointer;
	font-size: 1.25rem;
	position: absolute;
	text-align: center;
	line-height: 50px;
	background: #fff;
	border-radius: 50%;
	box-shadow: 0 3px 6px rgba(0, 0, 0, 0.23);
	transform: translateY(-50%);
	transition: transform 0.1s linear;
}
.wrapper i:active {
	transform: translateY(-50%) scale(0.85);
}
.wrapper i:first-child {
	left: -22px;
}
.wrapper i:last-child {
	right: -22px;
}
.wrapper .carousel {
	display: grid;
	grid-auto-flow: column;
	grid-auto-columns: calc((100% / 3) - 12px);
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	gap: 16px;
	border-radius: 8px;
	scroll-behavior: smooth;
	scrollbar-width: none;
}
.carousel::-webkit-scrollbar {
	display: none;
}
.carousel.no-transition {
	scroll-behavior: auto;
}
.carousel.dragging {
	scroll-snap-type: none;
	scroll-behavior: auto;
}
.carousel.dragging .card {
	cursor: grab;
	user-select: none;
}
.carousel :where(.card, .img) {
	display: flex;
	justify-content: center;
	align-items: center;
}
.carousel .card {
	scroll-snap-align: start;
	height: 370px;
	list-style: none;
	background: #fff;
	cursor: pointer;
	padding-bottom: 15px;
	flex-direction: column;
	border-radius: 8px;
	padding-top: 15px;
}
.carousel .card .img {
	background: #8b53ff;
	height: 148px;
	width: 148px;
	border-radius: 50%;
}
.card .img img {
	width: 140px;
	height: 140px;
	border-radius: 50%;
	object-fit: cover;
	border: 4px solid #fff;
}
.carousel .card h2 {
	font-weight: 500;
	font-size: 1.4rem;
	margin: 30px 0 5px;
}
.carousel .card span {
	color: #6a6d78;
	font-size: 1rem;
}

@media screen and (max-width: 900px) {
	.wrapper .carousel {
		grid-auto-columns: calc((100% / 2) - 9px);
	}
}

@media screen and (max-width: 600px) {
	.wrapper .carousel {
		grid-auto-columns: 100%;
	}
}


.testim {
    font-size: 1.15em;
    color: #333;
    padding:4%;
    margin: auto;
	text-align: center;
}

LwcTestimonialSlider.JS-meta.xml:

<?xml version="1.0"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
	<apiVersion>57.0</apiVersion>
	<isExposed>true</isExposed>
	<targets>
		<target>lightning__HomePage</target>
	</targets>
</LightningComponentBundle>

Output :

Reference :

  1. LWC
What’s your Reaction?
+1
4
+1
1
+1
0
+1
0
+1
1
+1
1

You may also like

2 comments

ishu March 13, 2024 - 9:26 am

its not working

Reply
Rijwan Mohmmed March 13, 2024 - 4:43 pm

Can you please tell me error

Reply

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.