├── .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 |

42 |
43 |
44 |

45 |
46 |
47 |
48 |
49 |
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 |
--------------------------------------------------------------------------------