Listen to one of my mixes while you read?

Building a Progressive Image Loader

Estimated reading time 5 min
Created: 5.7.2025, 15:44 - Updated: 16.12.2025, 15:41
15.6.2025, 15:39
progressive.jpg progressive.jpg
(picture that uses this loader) Progressive image loading is a crucial technique for modern web applications that prioritizes user experience by displaying content quickly while high-quality images load in the background. Today, I'll walk you through a robust progressive image loader implementation which I made that uses Symfony's Twig Components, Stimulus controllers, and Sonata Media Bundle.

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:

  1. Symfony Twig Component (ProgressiveImage.php) - Server-side logic and configuration
  2. Stimulus Controller (progressive-image_controller.js) - Client-side loading and animation logic
  3. Twig Template (ProgressiveImage.html.twig) - HTML structure and integration
  4. 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:

  1. Cache Check: First checks if the image is already cached by the browser
  2. Immediate Loading: If cached, loads instantly without animations
  3. Lazy Loading: Uses Intersection Observer API for viewport-based loading
  4. Fallback Support: Gracefully degrades for browsers without Intersection Observer

Performance Optimizations#

  • Batched DOM Updates: Uses requestAnimationFrame to 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.

You may also like

Katarsis logo Katarsis logo
Music

Chiphead - Katarsis