├── .gitignore ├── customCarousel.css ├── README.md └── customCarousel.js /.gitignore: -------------------------------------------------------------------------------- 1 | index.html -------------------------------------------------------------------------------- /customCarousel.css: -------------------------------------------------------------------------------- 1 | .slider-logo_list-wrapper { 2 | overflow: scroll; 3 | padding-bottom: 0.5em; 4 | } 5 | 6 | .slider-logo_list-wrapper::-webkit-scrollbar { 7 | height: 0.25em; 8 | width: 100%; 9 | } 10 | 11 | .slider-logo_list-wrapper::-webkit-scrollbar-track { 12 | background: transparent; 13 | } 14 | 15 | .slider-logo_list-wrapper::-webkit-scrollbar-thumb { 16 | background: var(--cream); 17 | border-radius: 2em; 18 | } 19 | 20 | .logo-testimonials_list { 21 | transition: transform 0.5s ease-in-out; 22 | } 23 | 24 | .slider-logo_list { 25 | display: flex; 26 | align-items: center; 27 | justify-content: space-between; 28 | } 29 | 30 | .logo-testimonials_item { 31 | transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out; 32 | width: 100%; 33 | opacity: 0; 34 | } 35 | 36 | .logo-testimonials_item.active { 37 | opacity: 1; 38 | } 39 | 40 | .slider-logo_item { 41 | transition: background 0.2s ease-in-out; 42 | } 43 | 44 | .slider-logo_item:hover { 45 | background: rgba(255, 255, 255, 0.2); 46 | } 47 | 48 | .slider-logo_item.clickedActive { 49 | background: rgba(255, 255, 255, 0.2); 50 | } 51 | 52 | .slider-logo_progress-bar { 53 | transition: opacity 0.5s ease-in-out; 54 | opacity: 0; 55 | width: 100%; 56 | } 57 | 58 | .active .slider-logo_progress-bar { 59 | opacity: 1; 60 | animation: logoProgressBar 5s ease-in; 61 | width: 100%; 62 | } 63 | 64 | .clickedActive .slider-logo_progress-bar { 65 | opacity: 1; 66 | width: 100%; 67 | } 68 | 69 | @keyframes logoProgressBar { 70 | 0% { 71 | width: 0%; 72 | } 73 | 74 | 100% { 75 | width: 100%; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asset Client Logo Carousel 2 | 3 | The Asset Client Logo Carousel is a custom carousel designed to showcase client logos and testimonials on a website. It provides an interactive and visually appealing way to display client logos and testimonials, allowing users to navigate through them easily. 4 | 5 | ## Features 6 | 7 | - Display client logos and testimonials in a carousel format. 8 | - Smooth transition between logos and testimonials. 9 | - Automatic scrolling to showcase different logos and testimonials. 10 | - Clickable navigation to manually switch between logos and testimonials. 11 | - Responsive design for optimal viewing on different devices. 12 | 13 | ## Installation 14 | 15 | To use the Asset Client Logo Carousel in your project, follow these steps: 16 | 17 | 1. Download the `customCarousel.js` file from this repository. 18 | 2. Include the `customCarousel.js` file in your project's HTML file. 19 | 3. Add the necessary HTML markup and CSS styles to create the carousel container and customize its appearance. 20 | 4. Initialize the carousel by calling the appropriate functions or event listeners. 21 | 22 | ## Usage 23 | 24 | To use the Asset Client Logo Carousel, follow these guidelines: 25 | 26 | 1. Create a container element in your HTML file to hold the carousel. 27 | 1. Add the necessary HTML markup to define the structure of the carousel, including the client logos and testimonials. 28 | 1. Customize the carousel's appearance using CSS styles. 29 | 1. use jsdelivr or S3 to add `customCarousel.js` & `customCarousel.css` to your page. 30 | 1. Test and preview the carousel in your web browser to ensure it functions as expected. 31 | 32 | ## Examples 33 | 34 | example of html structure in webflow page: 35 | 36 | ```html 37 |
38 |
39 | 40 |
41 | Client Logo 1 42 |
43 |
44 | Client Logo 2 45 |
46 | 47 |
48 | 49 |
50 | 51 |
52 |

Testimonial 1

53 |
54 |
55 |

Testimonial 2

56 |
57 | 58 |
59 |
60 | ``` 61 | -------------------------------------------------------------------------------- /customCarousel.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function () { 2 | const testimonialWrapper = document.querySelector('.logo-testimonials_wrapper'); 3 | const testimonialList = document.querySelector('.logo-testimonials_list'); 4 | const testimonialLogosList = document.querySelector('.slider-logo_list-wrapper'); 5 | 6 | const testimonials = Array.from(testimonialWrapper.querySelectorAll('.logo-testimonials_item')); 7 | const testimonialLogos = Array.from(testimonialWrapper.querySelectorAll('.slider-logo_item')); 8 | 9 | let currentIndex = 0; 10 | 11 | const setActiveItem = (index) => { 12 | const currentTestimonial = testimonials[currentIndex]; 13 | const currentLogo = testimonialLogos[currentIndex]; 14 | 15 | // remove active class from current testimonial 16 | currentTestimonial.classList.remove('active'); 17 | currentLogo.classList.remove('active'); 18 | currentLogo.classList.remove('clickedActive'); 19 | 20 | // add active class to new testimonial 21 | testimonials[index].classList.add('active'); 22 | testimonialLogos[index].classList.add('active'); 23 | 24 | // translate testimonial list 25 | testimonialList.style.transform = `translateX(-${index * 100}%)`; 26 | 27 | // scroll testimonial logos list 28 | const scrollWidth = testimonialLogos 29 | .slice(0, index) 30 | .reduce((acc, logo) => acc + logo.offsetWidth, 0); 31 | 32 | testimonialLogosList.scroll({ 33 | left: scrollWidth, 34 | behavior: 'smooth', 35 | }); 36 | 37 | currentIndex = index; 38 | }; 39 | 40 | const handleNextTestimonial = (callback = () => {}) => { 41 | if (currentIndex === testimonials.length - 1) { 42 | setActiveItem(0); 43 | } else { 44 | setActiveItem(currentIndex + 1); 45 | } 46 | 47 | callback(); 48 | }; 49 | 50 | const handlePreviousTestimonial = (callback = () => {}) => { 51 | if (currentIndex === 0) { 52 | setActiveItem(testimonials.length - 1); 53 | } else { 54 | setActiveItem(currentIndex - 1); 55 | } 56 | callback(); 57 | }; 58 | 59 | const handleTestimonialClick = (index, callback = () => {}) => { 60 | setActiveItem(index); 61 | callback(); 62 | }; 63 | 64 | let hasIntersected = false; 65 | let carouselInterval = null; 66 | 67 | // set initial active item and interval when wrapper is on the screen 68 | const observer = new IntersectionObserver( 69 | (entries) => { 70 | entries.forEach((entry) => { 71 | if (entry.isIntersecting) { 72 | setActiveItem(0); 73 | carouselInterval = setInterval(handleNextTestimonial, 6000); 74 | 75 | if (!hasIntersected) { 76 | hasIntersected = true; 77 | testimonialLogos.forEach((logo, index) => { 78 | logo.addEventListener('click', () => { 79 | handleTestimonialClick(index, () => { 80 | clearInterval(carouselInterval); 81 | carouselInterval = setInterval(handleNextTestimonial, 6000); 82 | }); 83 | }); 84 | }); 85 | } 86 | } else { 87 | clearInterval(carouselInterval); 88 | } 89 | }); 90 | }, 91 | { threshold: 0.5 } 92 | ); 93 | 94 | observer.observe(testimonialWrapper); 95 | 96 | let touchstartX = 0; 97 | let touchendX = 0; 98 | 99 | testimonialList.addEventListener( 100 | 'touchstart', 101 | (event) => { 102 | touchstartX = event.changedTouches[0].screenX; 103 | }, 104 | false 105 | ); 106 | 107 | testimonialList.addEventListener( 108 | 'touchend', 109 | (event) => { 110 | touchendX = event.changedTouches[0].screenX; 111 | handleGesture(); 112 | }, 113 | false 114 | ); 115 | 116 | function handleGesture() { 117 | // add deadzone to touch so that when scrolling on mobile it doesn't trigger the carousel 118 | if (Math.abs(touchendX - touchstartX) < 100) return; 119 | if (touchendX <= touchstartX) { 120 | handleNextTestimonial(() => { 121 | clearInterval(carouselInterval); 122 | carouselInterval = setInterval(handleNextTestimonial, 6000); 123 | }); 124 | } 125 | 126 | if (touchendX >= touchstartX) { 127 | handlePreviousTestimonial(() => { 128 | clearInterval(carouselInterval); 129 | carouselInterval = setInterval(handleNextTestimonial, 6000); 130 | }); 131 | } 132 | } 133 | }); 134 | --------------------------------------------------------------------------------