The Responsive Images plugin for the Grav CMS inserts responsive images into HTML pages. It makes the browser display images on different devices with optimal image resolution and page speed.
ℹ️ This plugin generates a
srcset
attribute for foreground images and media queries for background images. See this overview article and this in-depth explanation why other methods are not helpful.
Serving desktop-sized images to mobile devices can use 2–4x more data than needed, slowing down sites. Speed matters:
- Low-performing sites may lose users.
- Google uses speed as a ranking factor on mobile devices.
-
For each image on your website, create a pre-built set of properly sized image files, for example with ImageMagick.
ℹ️ You can use advanced encoding and compression techniques in this step. An example using ImageMagick and jpegoptim:
convert jeep.jpg -quality 100 -resize 800x534 jeep-0800.jpg jpegoptim --quiet --all-progressive --strip-all --max=85 jeep-0800.jpg
-
Embed the image into your page or template using the provided Twig functions.
-
Foreground image: The function
image_element()
generates an HTMLimg
tag withsrc
andsrcset
attributes referring to image sources and widths. -
Background image: The function
background_image_class()
generates a CSS class using media queries to specify image sources for different device widths.
-
Installing the Responsive Images plugin can be done in one of two ways. The GPM (Grav Package Manager) installation method enables you to quickly and easily install the plugin with a simple terminal command, while the manual method enables you to do so via a zip file.
The simplest way to install this plugin is via the Grav Package Manager (GPM) through your system's terminal (also called the command line). From the root of your Grav installation directory type:
bin/gpm install responsive-images
This will install the Responsive Images plugin into your user/plugins
directory within Grav. Its files can be found under user/plugins/responsive-images
.
To install this plugin, just download the zip version of this repository and unzip it under user/plugins
. Then, rename the folder to responsive-images
. You can find these files on GitHub or via GetGrav.org.
You should now have all the plugin files under
user/plugins/responsive-images
ℹ️ This plugin is a modular component for Grav which requires Grav and the Error and Problems plugins to operate.
ℹ️ Before configuring this plugin, you should copy the
user/plugins/responsive-images/responsive-images.yaml
touser/config/plugins/responsive-images.yaml
and only edit that copy.
The plugin can be enabled or disabled. The default configuration is:
enabled: true
alternativeFormats: []
Modern image formats such as WebP can drastically reduce image download sizes while preserving quality. The plugin supports a list of alternative image formats via alternativeFormats
. To provide WebP images in addition to base image files, set alternativeFormats: ["webp"]
.
For each alternative image format, the plugin expects an image file with the alternative's extension (e.g. .webp
) next to the corresponding base image file (typically JPEG or PNG). It will then prefer the alternative image if the browser supports it.
To use alternative background images, each page's <html>
element must provide classes identifying browser support for alternative formats (e.g. <html class="webp">
). You can use the provided Twig function html_image_support_classes()
to achieve this (e.g. <html class="{{ html_image_support_classes() }}">
) for webp
and avif
formats, or use your own detection mechanism.
ℹ️ JavaScript detection, such as the one provided by Modernizr, might not complete before images start to load. In this case, images can load twice, in different formats.
Additional information: Using WebP Images | CSS-Tricks.
More than one alternative image format can be specified. These will be offered in the configured format order, but always before the respective base image file.
If images.debug
is set to true
in system.yaml
, the plugin's generated CSS code will contain comments explaining the use of the sizes
parameter:
images:
debug: true
ℹ️ Remember to enable Twig processing on each page using the
image_element()
function.
To display a responsive foreground image in a Grav page or Twig template, firstly use:
{{ image_element("images/astronaut-*.jpg", sizes="(min-width: 1200px) 1200px, 100vw", title="Chasing Stars") }}
Secondly, provide a corresponding set of image files at different widths:
images/astronaut-0600.jpg
images/astronaut-0800.jpg
images/astronaut-1200.jpg
images/astronaut-2400.jpg
ℹ️ The numbers must reflect the image's intrinsic width in pixels. This is the image's real width, not CSS pixels.
ℹ️ MDN provides an introduction to responsive images in HTML. For an in-depth discussion about
srcset
andsizes
, see this excellent article on ericportis.com.
The image_element()
function
- generates an
<img>
tag, - discovers image widths from file names where the
*
wildcard was placed, - generates the appropriate
srcset
attribute, - generates a
src
attribute, choosing the second largest image as a fallback for browsers which ignoresrcset
, - passes on the
sizes
andtitle
attributes, - generates an appropriate
alt
attribute.
The above example will generate the following HTML code:
<img src="/images/astronaut-1200.jpg" srcset="/images/astronaut-2400.jpg 2400w, /images/astronaut-1200.jpg 1200w, /images/astronaut-0800.jpg 800w, /images/astronaut-0600.jpg 600w" sizes="(min-width: 1200px) 1200px, 100vw" title="Chasing Stars" alt="Chasing Stars">
{{ image_element(<path> [, baseWidth=<baseWidth>] [, attribute="value" [, ...]]) }}
generates an HTML img
tag with src
and srcset
attributes referring to image sources and widths, and optional extra attributes (such as sizes
, title
) as given.
Where
<path>
is the path to the image with a single*
placeholder for the image width, or no placeholder.<path>
may contain prefixes such astheme://
,<baseWidth>
is a string specifying the width of the base image if the browser ignoressrcset
(optional, defaults to the second largest image width as determined from image file names),ℹ️ If baseWidth is used, it must match the width as it appears in the file name, e.g. "0800" in the above example, not "800".
- further parameters specify additional image element attributes.
If a title
attribute is present but no alt
attribute, an alt
attribute with the title
attribute's value will be generated. Supply an empty alt
attribute to suppress it.
You should supply a sizes
attribute for each responsive image. It tells the browser how large the image will render in different layouts. Only then can the browser choose the appropriate image file for the intended size and given display density.
The sizes
value can be a single size, such as 100vw
for an image displaying at full-width on all displays. It can also be a combination of media conditions (e.g. screen widths) and image widths for responsive layouts with images appearing differently depending on the device's screen size.
Intended image width | sizes parameter |
---|---|
Full viewport width | 100vw |
50% of the viewport width | 50vw |
Fixed width of 630px | 630px |
1200px wide on viewport widths >= 1200px, full viewport width on all others | (min-width: 1200px) 1200px, 100vw |
100% of the viewport width on viewports up to 480px wide, 50% of the viewport width on screens 481–1024px wide, and 800px on screens wider than 1024px | (max-width: 480px) 100vw, (max-width: 1024px) 50vw, 800px |
ℹ️ Background images have no
srcset
support in CSS. This plugin works around this limitation by generating CSS media queries for different viewport sizes and display pixel densities. For an in-depth explanation why other methods are not helpful, see this article on ericportis.com.
The Twig function background_image_class()
returns the name of a generated CSS class, which specifies a responsive background image. The returned class name must appear as part of a class
list of the HTML element containing the background (such as <body>
or <div>
).
ℹ️ For responsive images to work on modular pages, Twig caching must be disabled due to Grav issue #1934. Please set
pages: { never_cache_twig: true }
in yoursystem.yaml
or setnever_cache_twig: true
in each modular page's frontmatter. Otherwise images will disappear on the second page load.
To display a responsive background image, firstly use a method from one of the following alternatives.
-
Completely specify the image in a Twig template:
<div class="{{ background_image_class('/images/stars-*.jpg', sizes='(min-width: 1200px) 1200px, 100vw', position='top', size='cover', attachment='fixed') }}"> [...] </div>
-
Use a parameterized specification in a Twig template:
<div {% if page.header.background_image %}class="{{ background_image_class(page.header.background_image) }}"{% endif %}> [...] </div>
joined by corresponding parameters in the page's frontmatter:
background_image: path: "/images/stars-*.jpg" sizes: "(min-width: 1200px) 1200px, 100vw" position: top size: cover attachment: fixed
-
Use a Twig function in a page's frontmatter:
ℹ️ To use Twig in a page's frontmatter, enable Twig processing and set
pages: { frontmatter: { process_twig: true } }
in yoursystem.yaml
.body_classes: title-center title-h1h2 {{ background_image_class('/images/stars-*.jpg', sizes='(min-width: 1200px) 1200px, 100vw', position='top', size='cover', attachment='fixed') }}
-
Use a background image in the page's content:
<div class="{{ background_image_class('/images/stars-*.jpg', sizes='(min-width: 1200px) 1200px, 100vw', position='top', size='cover', attachment='fixed') }}"> [...] </div>
ℹ️ Remember to enable Twig processing.
Secondly, provide a corresponding set of image files for different viewport widths:
/images/stars-0600.jpg
/images/stars-0800.jpg
/images/stars-1200.jpg
/images/stars-2400.jpg
ℹ️ The numbers must reflect the image's intrinsic width in pixels. This is the image's real width, not CSS pixels.
The background_image_class()
function
- generates a CSS class
ri-background-image-1
, - discovers image widths from file names where the
*
wildcard was placed, - generates appropriate CSS code using media queries,
- chooses the second largest image as a fallback for browsers which ignore media queries,
- passes on the additional parameters as CSS properties, each prefixed by
background-
, - returns the name of the generated background class (
ri-background-image-1
).
For each of the above examples, background_image_class()
will generate the following HTML code in the head section:
NOTE: If
images.debug
is set totrue
insystem-yaml
, the generated CSS code will contain comments explaining the use of thesizes
parameter, as shown here.
<style>
.ri-background-image-1 { background-image: url('/user/pages/images/stars-1200.jpg'); background-position: top; background-size: cover; background-attachment: fixed; }
/* sizes='(min-width: 1200px) 1200px, 100vw' */
@media
(min-width: 0px) /* fallback */ {
.ri-background-image-1 { background-image: url('/user/pages/images/stars-0600.jpg'); }
}
@media
(-webkit-min-device-pixel-ratio: 1) and (min-width: 601px), (min-resolution: 96dpi) and (min-width: 601px) /* 100vw */,
(-webkit-min-device-pixel-ratio: 1.5) and (min-width: 401px), (min-resolution: 144dpi) and (min-width: 401px) /* 100vw */,
(-webkit-min-device-pixel-ratio: 2) and (min-width: 301px), (min-resolution: 192dpi) and (min-width: 301px) /* 100vw */,
(-webkit-min-device-pixel-ratio: 3) and (min-width: 201px), (min-resolution: 288dpi) and (min-width: 201px) /* 100vw */,
(-webkit-min-device-pixel-ratio: 4) and (min-width: 151px), (min-resolution: 384dpi) and (min-width: 151px) /* 100vw */ {
.ri-background-image-1 { background-image: url('/user/pages/images/stars-0800.jpg'); }
}
@media
(-webkit-min-device-pixel-ratio: 1) and (min-width: 801px), (min-resolution: 96dpi) and (min-width: 801px) /* 100vw */,
(-webkit-min-device-pixel-ratio: 1.5) and (min-width: 534px), (min-resolution: 144dpi) and (min-width: 534px) /* 100vw */,
(-webkit-min-device-pixel-ratio: 2) and (min-width: 401px), (min-resolution: 192dpi) and (min-width: 401px) /* 100vw */,
(-webkit-min-device-pixel-ratio: 3) and (min-width: 267px), (min-resolution: 288dpi) and (min-width: 267px) /* 100vw */,
(-webkit-min-device-pixel-ratio: 4) and (min-width: 201px), (min-resolution: 384dpi) and (min-width: 201px) /* 100vw */ {
.ri-background-image-1 { background-image: url('/user/pages/images/stars-1200.jpg'); }
}
@media
(-webkit-min-device-pixel-ratio: 1.5) and (min-width: 801px), (min-resolution: 144dpi) and (min-width: 801px) /* 100vw */,
(-webkit-min-device-pixel-ratio: 2) and (min-width: 601px), (min-resolution: 192dpi) and (min-width: 601px) /* 100vw */,
(-webkit-min-device-pixel-ratio: 3) and (min-width: 401px), (min-resolution: 288dpi) and (min-width: 401px) /* 100vw */,
(-webkit-min-device-pixel-ratio: 4) and (min-width: 301px), (min-resolution: 384dpi) and (min-width: 301px) /* 100vw */,
(-webkit-min-device-pixel-ratio: 1) and (min-width: 1201px), (min-resolution: 96dpi) and (min-width: 1201px) /* (min-width: 1200px) 1200px, 100vw */ {
.ri-background-image-1 { background-image: url('/user/pages/images/stars-2400.jpg'); }
}
</style>
Variants 1 and 2 will insert the following HTML code at the place of invocation:
<div class="ri-background-image-1">
[...]
</div>
Variant 3, when used with Grav's default page, will insert ri-background-image-1
into the list of class names for the <body>
tag:
<body id="top" class="title-center title-h1h2 ri-background-image-1 header-fixed header-animated sticky-footer">
{{ background_image_class(<path> [, baseWidth=<baseWidth>] [, sizes=<sizes>] [, property='value' [, ...]]) }}
generates a CSS class using media queries to specify image sources for different device widths. The function returns the class name.
Parameters:
-
<path>
is the path to the image with a single*
placeholder for the image width, or no placeholder.<path>
may contain prefixes such astheme://
. -
<baseWidth>
is a string specifying the width of the base image if the browser ignores media queries (optional, defaults to the second largest image width as determined from image file names).ℹ️ If baseWidth is used, it must match the width as it appears in the file name, e.g. "0800" in the above example, not "800".
-
<sizes>
is asrcset/sizes
-like attribute with a set of media conditions and slot width hints. It indicates the intended image size for different viewport widths. For details, see the MDN documentation on srcset with sizes. The parameter is optional and defaults to100vw
.ℹ️ The
sizes
parameter is a restricted variant of the HTML<img>
tag'ssizes
attribute: It supports onlymin-width
media conditions with apx
unit, and only slot widths withpx
andvw
units.ℹ️ Complex expressions for
sizes
cannot always be expressed optimally, as one CSS media query per image file must cover all parts of asizes
expression. In certain cases, the browser will choose an image larger than necessary, prioritizing quality over image size.Example:
sizes='(min-width: 1200px) 1200px, 100vw'
would provide-
images suitable for a slot width of
1200px
on viewport widths>= 1200px
, and -
images suitable for 100% of the viewport width on viewport widths
< 1200px
.
-
-
further parameters will be passed as CSS properties, each prefixed by
background-
.