A Symfony bundle that provides two components for optimized images:
<twig:img>
- For simple responsive images with automatic WebP conversion<twig:picture>
- For art direction with different crops per breakpointresponsive_image_preloads()
- For preloading images
Key Features:
- 🖼️ Automatic responsive image generation
- 🎯 Smart cropping with focal points
- 🔄 WebP format conversion
- 🚀 Performance optimization
- ⚡ Image preloading support
Benefits:
- 📱 Better user experience across all devices and screen sizes
- ⚡ Faster page loads with optimized image delivery
- 🎨 Maintain image quality while reducing file sizes
- 📊 Improved Core Web Vitals scores
- đź’» Less developer time spent on image optimization
- Requirements
- Installation
- Components
- Configuration
- Common Use Cases
- Using Presets
- Settings
- Providers
- Error Handling
- Security
- Development
- License
- Credits
- PHP 8.1 or higher
- Symfony 6.0 or higher
- GD extension or Imagick extension
composer require razbakov/symfony-responsive-image
Register the bundle in config/bundles.php
:
return [
// ...
Razbakov\ResponsiveImageBundle\ResponsiveImageBundle::class => ['all' => true],
];
Choose the approach that best fits your needs:
- Use
<twig:img>
when you need different sizes of the same image - Use
<twig:picture>
when you need different crops/ratios per breakpoint
Basic usage:
{# Simple responsive image #}
<twig:img
src="/images/hero.jpg"
alt="Hero image"
width="100vw sm:50vw"
/>
{# With aspect ratio and focal point #}
<twig:img
src="/images/hero.jpg"
alt="Hero image"
width="100vw sm:50vw"
ratio="16:9"
focal="center"
/>
{# Optimized hero image #}
<twig:img
src="/images/hero.jpg"
alt="Hero image"
width="100vw"
preload="true"
fetchpriority="high"
/>
The <twig:img>
component supports the following attributes:
<twig:img
# Required attributes
src="/images/hero.jpg" # Image source path
alt="Hero image" # Alt text for accessibility
# Common attributes
width="100vw sm:50vw md:400px" # Responsive widths
densities="x1 x2" # Generate different densities
ratio="16:9" # Aspect ratio
preset="hero" # Use preset configuration
# Optimization attributes
preload="true" # Add preload link
fetchpriority="high" # Set high priority for LCP
loading="lazy" # Enable lazy loading
# Image processing attributes
format="webp" # Output format (default: webp)
quality="80" # Image quality 0-100 (default: 80)
focal="center" # Focus point for cropping
fit="cover" # How image should fit dimensions
fallback="lg" # Fallback breakpoint (default: lg)
fallback-format="png" # Fallback format (default: auto)
background="#ffffff" # Background color for 'contain' fit
# Standard HTML attributes
class="hero-image" # CSS classes
data-controller="zoom" # Data attributes
aria-label="Hero section" # ARIA attributes
# Coming soon
height="600" # Override height
placeholder="blur" # Enable blurred placeholder
/>
Basic usage:
{# Different crops per breakpoint #}
<twig:picture
src="/images/hero.jpg"
alt="Hero image"
width="100vw md:80vw"
ratio="sm:1:1 md:16:9"
/>
<twig:picture
# Required attributes
src="/images/hero.jpg" # Image source path
alt="Hero image" # Alt text for accessibility
# Common attributes
width="100vw md:80vw" # Responsive sizes per breakpoint
class="hero-picture" # Any HTML attribute
# Image processing attributes
format="webp" # Output format (default: webp)
quality="80" # Image quality 0-100 (default: 80)
fallback="lg" # Fallback breakpoint (default: lg)
fallback-format="png" # Fallback format (default: auto)
# Coming soon
ratio="sm:1:1 md:16:9" # Different ratios per breakpoint
focal="sm:center md:0.5,0.3" # Focus points per breakpoint
fit="sm:contain md:cover" # Fit behavior per breakpoint
/>
Add this to your base template to enable preloading of critical images:
<!DOCTYPE html>
<html>
<head>
{{ responsive_image_preloads() }}
{# Will output preload links for images marked with preload="true" #}
</head>
<body>
{# Your content #}
</body>
</html>
The responsive_image_preloads()
function generates appropriate <link rel="preload">
tags for any images that have preload="true"
set. This is especially useful for LCP optimization.
<twig:img
src="/images/hero.jpg"
alt="Hero image"
width="100vw sm:50vw md:400px lg:800" # Default size for mobile, then breakpoint:size pairs
/>
This will automatically:
- Generate appropriate image widths based on your design system's breakpoints
- Create the correct srcset and sizes attributes
- Optimize image delivery for each viewport size
The sizes syntax follows this pattern:
- Start with default size (applies to smallest screens)
- Add breakpoint:size pairs for larger screens
- Each size applies from that breakpoint up
- Example:
"100vw sm:50vw md:400px lg:800"
100vw
- full width (640px) on mobile (<640px)sm:50vw
- half width (384px) from sm breakpoint (≥640px)md:400px
- fixed 400px from md breakpoint (≥768px)lg:800
- fixed 800px from lg breakpoint (≥1024px)
Default breakpoints:
- default: <640px - Mobile portrait
- sm: >= 640px - Mobile landscape
- md: >= 768px - Tablet portrait
- lg: >= 1024px - Tablet landscape
- xl: >= 1280px - Desktop
- 2xl: >= 1536px - Large desktop
Transformation rules:
- If default width is not set, it will be taken from the smallest breakpoint, i.e
sm:50vw md:400px
is translated to50vw md:400px
. - Dynamic width
vw
will generate all sizes from smallest breakpoint to image size but not larger than largest breakpoint. - Fixed width wll be used until there is a breakpoint with
vw
width set, from which point it will use dynamic rule.
Width string | Image versions |
---|---|
100 | 100px |
1000 | 1000px |
sm:50 md:100 lg:200 | 50px, 100px, 200px |
100vw | 640px, 768px, 1024px, 1280px, 1536px |
50vw lg:400px | 320px, 384px, 400px |
100 lg:100vw | 100px, 1024px, 1280px, 1536px |
100vw md:100 | 640px, 768px, 100px |
1000 lg:100vw | 1000px, 1024px, 1280px, 1536px |
Width and height are automatically calculated from:
- Original image dimensions when no ratio specified
- When ratio specified:
- Original width and calculated height if no width/height set
- Width and calculated height if width set (width="800" ratio="16:9")
- Calculated width and height if height set (height="600" ratio="16:9")
- Override both with width/height if needed (width="800" height="600")
The bundle uses your design system's breakpoints (configurable in responsive_image.yaml
).
To generate special versions of images for high-DPI displays (like Retina), use the densities
attribute:
<twig:img
src="/images/logo.png"
width="100"
densities="x1 x2" # Generate 1x and 2x versions
alt="Logo"
/>
This will generate:
<img
src="/images/logo-100.jpg"
srcset="/images/logo-100.jpg 100w, /images/logo-200.jpg 200w"
width="100"
alt="Logo"
/>
You can combine densities with responsive sizes:
<twig:img
src="/images/hero.jpg"
width="100vw sm:50vw md:400px"
densities="x1 x2"
alt="Hero image"
/>
The component will:
- Generate 1x and 2x versions for each size
- Include both width (w) descriptors in srcset
- Automatically calculate the correct dimensions for each density
The fit
property specifies how the image should be resized to fit the target dimensions. There are five standard values:
cover
(default) - Preserving aspect ratio, ensures the image covers both provided dimensions by cropping/clipping to fitcontain
- Preserving aspect ratio, contains image within both provided dimensions using "letterboxing" where necessaryfill
- Ignores the aspect ratio of the input and stretches to both provided dimensionsinside
- Preserving aspect ratio, resizes the image to be as large as possible while ensuring its dimensions are less than or equal to both those specifiedoutside
- Preserving aspect ratio, resizes the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specifiednone
- Uses original image dimensions
The fallback-format
property controls format selection for older browsers:
auto
(default): Chooses based on original image- PNG fallback if original has transparency (PNG, WebP, GIF)
- JPEG fallback for all other formats
jpg
: Force JPEG as fallback formatpng
: Force PNG as fallback formatempty
: Return empty GIF image
The fallback
property controls which breakpoint is used for the fallback image.
By default it is set to lg
, assuming older browsers that don't support srcset
are more likely to be desktop and have a resolution of 1024px.
The placeholder
property controls image loading placeholders:
<twig:img
src="/images/hero.jpg"
alt="Hero image"
placeholder="blur" # Enable blurred placeholder
placeholder="[200]" # Square placeholder of 200px
placeholder="[200,150]" # Placeholder with specific dimensions
placeholder="[200,150,70,3]" # With quality=70 and blur=3
/>
Placeholder options:
none
(default) - No placeholderblur
- Blurred version of the imagedominant
- Dominant color of the image- Array syntax for custom dimensions:
[size]
- Square placeholder (e.g.[200]
)[width,height]
- Custom dimensions (e.g.[200,150]
)[width,height,quality,blur]
- Full control (e.g.[200,150,70,3]
)
The placeholder image is automatically:
- Converted to a lightweight Base64 data URI
- Shown while the main image loads
- Faded out when the main image loads
- Optimized for performance
Example with blur placeholder:
<twig:img
src="/images/hero.jpg"
alt="Hero image"
placeholder="blur"
placeholder-class="my-placeholder" # Optional custom class
/>
Use <twig:picture>
when you need different versions of the image:
<twig:picture
src="/images/hero.jpg"
alt="Hero image"
width="100vw md:80vw" # Full width on mobile, 80% on desktop
ratio="sm:1:1 md:16:9" # Square for mobile, widescreen for desktop
fit="sm:cover md:cover" # Cover fitting for both breakpoints
focal="sm:center md:0.5,0.3" # Center on mobile, custom focus on desktop
/>
<twig:img
src="/images/product.jpg"
alt="Product"
width="800"
height="800"
fit="contain"
background="#ffffff"
loading="lazy"
/>
<twig:img
src="/images/portrait.jpg"
alt="Portrait"
ratio="4:3"
fit="cover"
focal="0.5,0.3"
width="100vw"
/>
<twig:img
src="{{ content.image.uri }}"
alt="{{ content.image.alt }}"
width="{{ content.image.width }}"
height="{{ content.image.height }}"
fit="cover"
focal="{{ content.image.focal }}"
/>
Presets allow you to reuse common configurations:
# config/packages/responsive_image.yaml
responsive_image:
presets:
thumbnail:
width: 200
height: 200
fit: cover
quality: 90
hero:
ratio: "16:9"
sizes: "100vw sm:50vw md:400px"
fetchpriority: high
preload: true
avatar:
width: 48
height: 48
fit: cover
placeholder: blur
product:
ratio: "1:1"
fit: contain
background: "#ffffff"
placeholder: dominant
Using presets in templates:
{# Using a preset #}
<twig:img
src="/images/photo.jpg"
alt="Photo"
preset="hero"
/>
{# Override preset values #}
<twig:img
src="/images/photo.jpg"
alt="Photo"
preset="hero"
fetchpriority="low" {# Override specific preset value #}
/>
You can define your own presets in the configuration. Preset values can be overridden by directly setting properties on the component.
Default settings in config/packages/responsive_image.yaml
:
responsive_image:
provider: "liip_imagine" # see below for providers
missing_image_placeholder: "/path/to/404-placeholder.jpg"
defaults:
breakpoints:
xs: 320
sm: 640
md: 768
lg: 1024
xl: 1280
2xl: 1536
format: "webp"
quality: 80
loading: lazy
fetchpriority: low
fit: "cover"
focal: "center"
placeholder: "none"
placeholder-class: "lazy-placeholder"
The bundle supports multiple providers for image transformation and optimization. Each provider is responsible for generating optimized image URLs and handling transformations. See Providers for more information.
Currently supported providers:
placeholder
- Uses placeholder service for testingcloudinary
fastly
liip_imagine
Coming soon:
aliyun
aws_amplify
bunny
caisy
cloudflare
cloudimage
contentful
directus
edgio
glide
gumlet
hygraph
imageengine
imagekit
imgix
ipx
netlify
prepr
prismic
sanity
sirv
storyblok
strapi
twicpics
unsplash
uploadcare
vercel
weserv
The bundle provides several error handling mechanisms:
- Missing images return a 404 placeholder
- Invalid configurations throw
InvalidConfigurationException
- Processing errors are logged to Symfony's error log
- Allowed image types: jpg, jpeg, png, gif, webp
- Maximum upload size: Configured through PHP's upload_max_filesize
- Path validation prevents directory traversal attacks
- Image validation ensures file integrity
# Clone repository
git clone https://github.com/razbakov/symfony-responsive-image
cd symfony-responsive-image
# Install dependencies
composer install
# Run tests
composer test
This bundle is available under the MIT license.
Inspired by NuxtImg.