The Carousel That Never Sleeps
TL;DR: A carousel that runs infinite scroll listeners and DOM queries like it's training for a marathon.
The Code
1let hero_slide = document.querySelector('#hero-slide')
2
3let hero_slide_items = hero_slide.querySelectorAll('.slide')
4
5let hero_slide_index = 0
6
7let hero_slide_play = true
8
9let hero_slide_control_items = hero_slide.querySelectorAll('.slide-control-item')
10
11let slide_next = hero_slide.querySelector('.slide-next')
12
13let slide_prev = hero_slide.querySelector('.slide-prev')
14
15let header = document.querySelector('header')
16
17showSlide = (index) => {
18 hero_slide.querySelector('.slide.active').classList.remove('active')
19 hero_slide.querySelector('.slide-control-item.active').classList.remove('active')
20 hero_slide_control_items[index].classList.add('active')
21 hero_slide_items[index].classList.add('active')
22}
23
24nextSlide = () => {
25 hero_slide_index = hero_slide_index + 1 === hero_slide_items.length ? 0 : hero_slide_index + 1
26 showSlide(hero_slide_index)
27}
28
29prevSlide = () => {
30 hero_slide_index = hero_slide_index - 1 < 0 ? hero_slide_items.length - 1 : hero_slide_index - 1
31 showSlide(hero_slide_index)
32}
33
34slide_next.addEventListener('click', () => nextSlide())
35
36slide_prev.addEventListener('click', () => prevSlide())
37
38// add event to slide select
39hero_slide_control_items.forEach((item, index) => {
40 item.addEventListener('click', () => showSlide(index))
41})
42
43// pause slide when mouse come in slider
44hero_slide.addEventListener('mouseover', () => hero_slide_play = false)
45
46// resume slide when mouse leave out slider
47hero_slide.addEventListener('mouseleave', () => hero_slide_play = true)
48
49setTimeout(() => hero_slide_items[0].classList.add('active'), 200);
50
51// auto slide
52// setInterval(() => {
53// if (!hero_slide_play) return
54// nextSlide()
55// }, 5000);
56
57// change header style when scroll
58window.addEventListener('scroll', () => {
59 if (document.body.scrollTop > 80 || document.documentElement.scrollTop > 80) {
60 header.classList.add('shrink')
61 } else {
62 header.classList.remove('shrink')
63 }
64})
65
66// element show on scroll
67
68let scroll = window.requestAnimationFrame || function(callback) {window.setTimeout(callback, 1000/60)}
69
70let el_to_show = document.querySelectorAll('.show-on-scroll')
71
72isElInViewPort = (el) => {
73 let rect = el.getBoundingClientRect()
74
75 let distance = 200
76
77 return (rect.top <= (window.innerHeight - distance || document.documentElement.clientHeight - distance))
78}
79
80loop = () => {
81 el_to_show.forEach(el => {
82 if (isElInViewPort(el)) el.classList.add('show')
83 })
84
85 scroll(loop)
86}
87
88loop()
89The Prayer 🤞
🤞 Fingers crossed that users won't notice their laptop fans spinning up like jet engines when they visit our homepage! Maybe if we just ignore those performance warnings in DevTools, they'll go away on their own. I'm sure running querySelectorAll on every single scroll event is totally fine - computers are fast these days, right?
The Reality Check
This carousel is a performance nightmare masquerading as a simple slider. The requestAnimationFrame loop runs continuously, checking viewport positions on every frame even when nothing is happening. Combined with the scroll event listener that queries the DOM on every pixel scrolled, you've created a perfect storm of unnecessary computation that will drain mobile batteries faster than a teenager drains data.
The global variables scattered throughout create a maintenance headache, while the lack of error handling means any missing DOM element will crash the entire script. Users on slower devices will experience janky animations and delayed interactions, while the continuous RAF loop prevents the browser from properly optimizing performance during idle periods.
Search engines and accessibility tools will struggle with this implementation since there's no semantic structure or ARIA labels. The auto-advance feature is commented out, but even the manual controls lack proper keyboard navigation, making this carousel unusable for anyone not using a mouse.
The Fix
First, debounce that scroll event and cache your DOM queries. Create a proper class-based structure to encapsulate the carousel logic:
1class HeroCarousel {
2 constructor(selector) {
3 this.carousel = document.querySelector(selector);
4 if (!this.carousel) return;
5
6 this.slides = this.carousel.querySelectorAll('.slide');
7 this.controls = this.carousel.querySelectorAll('.slide-control-item');
8 this.nextBtn = this.carousel.querySelector('.slide-next');
9 this.prevBtn = this.carousel.querySelector('.slide-prev');
10 this.currentIndex = 0;
11 this.isPlaying = true;
12
13 this.init();
14 }
15
16 init() {
17 this.bindEvents();
18 this.showSlide(0);
19 }
20
21 showSlide(index) {
22 // Remove active classes
23 this.carousel.querySelector('.slide.active')?.classList.remove('active');
24 this.carousel.querySelector('.slide-control-item.active')?.classList.remove('active');
25
26 // Add active classes
27 this.slides[index]?.classList.add('active');
28 this.controls[index]?.classList.add('active');
29
30 this.currentIndex = index;
31 }
32}
33For the scroll animations, use Intersection Observer instead of that resource-hungry RAF loop:
1class ScrollAnimations {
2 constructor() {
3 this.observer = new IntersectionObserver(
4 this.handleIntersection.bind(this),
5 { rootMargin: '-200px 0px' }
6 );
7
8 document.querySelectorAll('.show-on-scroll')
9 .forEach(el => this.observer.observe(el));
10 }
11
12 handleIntersection(entries) {
13 entries.forEach(entry => {
14 if (entry.isIntersecting) {
15 entry.target.classList.add('show');
16 this.observer.unobserve(entry.target); // Stop observing once shown
17 }
18 });
19 }
20}
21Debounce the header scroll effect to prevent excessive DOM manipulation:
1const debounce = (func, wait) => {
2 let timeout;
3 return function executedFunction(...args) {
4 const later = () => {
5 clearTimeout(timeout);
6 func(...args);
7 };
8 clearTimeout(timeout);
9 timeout = setTimeout(later, wait);
10 };
11};
12
13const handleHeaderScroll = debounce(() => {
14 const header = document.querySelector('header');
15 const scrolled = window.pageYOffset > 80;
16 header?.classList.toggle('shrink', scrolled);
17}, 16); // ~60fps
18
19window.addEventListener('scroll', handleHeaderScroll, { passive: true });
20Lesson Learned
Performance optimization isn't about making code work, it's about making it work efficiently for everyone.