Building Custom Image Marquees: A Deep Dive into Smooth Animations
Introduction
While the classic HTML <marquee> element is long deprecated, the need for smooth scrolling content remains popular in modern web design. In this guide, we'll explore how to build a performant, customizable image marquee from scratch while avoiding common pitfalls that can lead to janky animations or poor performance.
The Basic Structure
First, let's establish the HTML structure for our marquee:
<div class="marquee-container">
<div class="marquee-content">
<img src="image1.jpg" alt="Image 1">
<img src="image2.jpg" alt="Image 2">
<img src="image3.jpg" alt="Image 3">
<!-- Duplicate images for seamless loop -->
<img src="image1.jpg" alt="Image 1">
<img src="image2.jpg" alt="Image 2">
<img src="image3.jpg" alt="Image 3">
</div>
</div>
The CSS Foundation
Here's the essential CSS to get us started:
.marquee-container {
width: 100%;
overflow: hidden;
position: relative;
}
.marquee-content {
display: flex;
gap: 20px; /* Space between images
position: relative;
white-space: nowrap;
will-change: transform;
}
.marquee-content img {
height: 200px; /* Adjust as needed
width: auto;
object-fit: cover;
flex-shrink: 0;
}
Common Pitfalls and Solutions
Pitfall #1: Using Left/Right Properties for Animation
Many developers initially try to animate using left
or right
properties, which can lead to poor performance as these trigger layout recalculations.
Solution: Use transform: translateX()
instead:
@keyframes scroll {
from {
transform: translateX(0);
}
to {
transform: translateX(-50%);
}
}
.marquee-content {
animation: scroll 20s linear infinite;
}
Pitfall #2: Jerky Loop Transitions
When the animation loops, you might notice a sudden jump.
Solution: Duplicate the content and ensure the total width is calculated correctly:
const marqueeContent = document.querySelector('.marquee-content');
const images = marqueeContent.querySelectorAll('img');
const totalWidth = Array.from(images).slice(0, images.length / 2)
.reduce((width, img) => width + img.offsetWidth + 20, 0); // 20 is gap
// Set animation duration based on content width
const duration = totalWidth / 50; // pixels per second
marqueeContent.style.animationDuration = `${duration}s`;
Pitfall #3: Performance Issues on Mobile
Mobile devices can struggle with continuous animations.
Solution: Use will-change
and throttle animations when not in viewport:
// Use Intersection Observer to pause animation when not visible
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
const marquee = entry.target;
if (entry.isIntersecting) {
marquee.style.animationPlayState = 'running';
} else {
marquee.style.animationPlayState = 'paused';
}
});
}, { threshold: 0.1 });
observer.observe(document.querySelector('.marquee-content'));
Pitfall #4: Accessibility Concerns
Moving content can be problematic for users with motion sensitivity.
Solution: Respect user preferences and provide controls:
@media (prefers-reduced-motion: reduce) {
.marquee-content {
animation: none;
}
}
// Add pause on hover functionality
marqueeContent.addEventListener('mouseenter', () => {
marqueeContent.style.animationPlayState = 'paused';
});
marqueeContent.addEventListener('mouseleave', () => {
marqueeContent.style.animationPlayState = 'running';
});
Advanced Features
Dynamic Speed Adjustment
Let's add the ability to control the marquee speed:
class ImageMarquee {
constructor(container, options = {}) {
this.container = container;
this.speed = options.speed || 1;
this.direction = options.direction || 'left';
this.setupMarquee();
}
setSpeed(newSpeed) {
this.speed = newSpeed;
const currentDuration = parseFloat(
getComputedStyle(this.content).animationDuration
);
this.content.style.animationDuration = `${currentDuration / newSpeed}s`;
}
setupMarquee() {
// Implementation details...
}
}
Responsive Considerations
Ensure your marquee works well across different screen sizes:
.marquee-container {
--marquee-height: clamp(200px, 30vw, 400px);
}
.marquee-content img {
height: var(--marquee-height);
width: auto;
}
@media (max-width: 768px) {
.marquee-content {
gap: 10px;
}
}
Conclusion
Building a custom image marquee requires careful consideration of performance, accessibility, and user experience. By avoiding these common pitfalls and implementing the solutions provided, you can create smooth, performant marquees that enhance your web projects without compromising the user experience.
Remember to:
- Use transform instead of position properties
- Implement proper looping techniques
- Consider performance optimizations
- Respect user preferences and accessibility
- Provide controls for user interaction
Happy coding!