Building a Progressive Image Loader
What is Progressive Image Loading?
Progressive image loading is a technique where you initially display a lightweight placeholder (like a blur, low-resolution version, or simple graphic) while the full-resolution image loads asynchronously in the background. Once the high-quality image is ready, it smoothly transitions in, replacing the placeholder.
This approach provides several benefits:
- Faster perceived site load times - Users see content immediately
- Better user experience - No jarring layout shifts or blank spaces
- Bandwidth efficiency - Images only load when needed (lazy loading)
- Performance optimization - Reduces initial page load time
Architecture Overview
Our progressive image loader consists of three main components:
- Symfony Twig Component (ProgressiveImage.php) - Server-side logic and configuration
- Stimulus Controller (progressive-image_controller.js) - Client-side loading and animation logic
- Twig Template (ProgressiveImage.html.twig) - HTML structure and integration
- Sonata Media Bundle - Image processing and URL generation
The Symfony Twig Component
The ProgressiveImage component serves as the backbone of our system, handling configuration.
Key Features:
- Flexible sizing: Supports responsive breakpoints with customizable media queries
- Sonata Media integration: Leverages Sonata Media Bundle for image processing and URL generation
- Customizable styling: Separate CSS classes for container, placeholder, picture, and image elements
- Accessibility: Automatic alt text generation with fallbacks
- Lazy loading control: Option to disable lazy loading for above-the-fold images
The Stimulus Controller
The client-side Stimulus controller handles the complex loading logic and smooth animations:
Smart Loading Strategy
The controller implements an intelligent loading strategy:
- Cache Check: First checks if the image is already cached by the browser
- Immediate Loading: If cached, loads instantly without animations
- Lazy Loading: Uses Intersection Observer API for viewport-based loading
- Fallback Support: Gracefully degrades for browsers without Intersection Observer
Performance Optimizations
- Batched DOM Updates: Uses
requestAnimationFrameto batch style changes - Promise-based Loading: Prevents duplicate loading attempts
- Memory Management: Properly cleans up observers and promises
- Error Handling: Graceful degradation when images fail to load
Sonata Media Bundle Integration
The component seamlessly integrates with Sonata Media Bundle, leveraging its powerful image processing capabilities:
{% for format, mediaQuery in sizes %}
<source media="{{ mediaQuery }}"
data-srcset="{{ sonata_path(media, format) }}"
type="{{ media.contentType }}">
{% endfor %}
Benefits of Sonata Media Integration:
- Automatic Format Generation: Creates multiple image sizes and formats
- CDN Support: Easy integration with CDN providers
- Metadata Management: Handles alt text, titles, and descriptions
- Security: Validated and processed image uploads
- Performance: Optimized image delivery with proper caching headers
Usage Examples
Basic Usage
<twig:ProgressiveImage
:media="image"
:placeholder="sonata_path(heroImage, 'placeholder')"
:sizes="{
'small': '(max-width: 480px)',
'medium': '(max-width: 768px)',
'large': '(min-width: 769px)'
}"
/>
Advanced Configuration
<twig:ProgressiveImage
:media="heroImage"
:lazy="false"
containerClass="hero-image-container"
:placeholder="sonata_path(heroImage, 'placeholder')"
imageClass="hero-image"
alt="Hero image for our amazing product"
:sizes="{
'mobile': '(max-width: 768px)',
'tablet': '(max-width: 1024px)',
'desktop': '(min-width: 1025px)'
}"
/>
Remember to generate small and bad quality version of your image called placeholderusing Sonata Media Bundle.
CSS Integration
The component needs only little CSS:
.progressive-media-container {
position: relative;
overflow: hidden;
}
.progressive-placeholder {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transition: opacity 0.3s ease;
filter: blur(5px);
transform: scale(1.1);
}
.progressive-picture {
opacity: 0;
transition: opacity 0.3s ease;
}
.progressive-picture.loaded {
opacity: 1;
}
.progressive-placeholder.faded {
opacity: 0;
}
Browser Support and Fallbacks
The component is designed with progressive enhancement in mind:
- Intersection Observer: Falls back to immediate loading if not supported
- Modern JavaScript: Uses modern syntax but can be transpiled for older browsers
- Responsive Images: Uses
<picture>element with<source>fallbacks - Accessibility: Provides proper alt text and semantic HTML structure
Performance Considerations
Loading Strategy
- Images above the fold can be set to load immediately (
lazy="false") - Images below the fold use lazy loading with a 50px root margin for smooth experience
- Cached images bypass the progressive loading animation
Memory Management
- Observers are properly disconnected when components unmount
- Loading promises are cleaned up to prevent memory leaks
- Event listeners are removed during cleanup
Network Optimization
- Only loads images when they're needed
- Leverages browser caching effectively
- Supports modern image formats through Sonata Media Bundle
Conclusion
This progressive image loader provides a robust, performance-focused solution for modern web applications. By combining Symfony's component system, Stimulus's reactive controllers, and Sonata Media Bundle's powerful image processing, we've created a system that:
- Improves perceived performance
- Provides excellent user experience
- Maintains clean, maintainable code
- Integrates seamlessly with existing Symfony applications
- Supports modern web standards while providing fallbacks
The modular architecture makes it easy to extend and customize while the integration with Sonata Media Bundle provides enterprise-grade image management capabilities.
Whether you're building a blog, e-commerce site, or media-rich application, this progressive image loader will help you deliver a superior user experience while maintaining optimal performance.
Ready to implement progressive image loading in your Symfony application? The complete source code is available (above), and the component can be easily integrated into any Symfony project using Sonata Media Bundle.