The Jekyll plugin described here is now available, if you just want to get it installed.
A responsive design adapts to display size to improve the user experience. An example is a grid layout that increases the number of columns with the width of the screen, from one on a narrow mobile device, to several on a wide screen display. A responsive design is made up of responsive design elements, among them responsive images, which help to:
- deliver appropriately sized and compressed variants of images to minimize transfer time and optimize performance,
- deliver appropriate content at those various sizes, e.g. using simpler artwork for smaller icons, rather than merely shrinking larger artwork, to preserve clarity.
We address the first motivation, showing how to automate image resizing for Jekyll sites by using ImageMagick command-line tools.
HTML essentials
Responsive images are enabled by two attributes of the HTML img
element:
srcset, providing a list of available image variants and their widths, andsizesproviding a list of context-specific hints.
An img element should also provide the common
src, alt, width and
height attributes. Putting them all together, it should
look something like this:
<img
src="example.jpg"
alt="An example image"
srcset="example-w256.jpg 256w, example-w512.jpg 512w, example.jpg 1024w"
sizes="(max-width: 575px) 100vw, (max-width: 767px) 50vw, (max-width: 991px) 34vw, (min-width: 992px) 25vw"
width="1024"
height="768"
>Here, the srcset attribute provides three image
variants:
example-w256.jpgof width 256 pixels,example-w512.jpgof width 512 pixels, and- the original
example.jpgof width 1024 pixels.
The sizes attribute provides guidance as to the
context in which the image appears:
- if the viewport width is up to 575 pixels (
575px) then there is one column (100vwi.e. 100% of viewport width), - if the viewport width is up to 767 pixels (
767px) then there are two columns (50vwi.e. 50% of viewport width), - if the viewport width is up to 991 pixels (
991px) then there are three columns (34vw—rounding up), - otherwise there are four columns (
25vw).
This suggests that the image will appear as part of a one to four column layout, depending on display width.
Automating srcset is achievable; we do so here.
Automating sizes is much more difficult as its value
depends on the context in which the image appears; we do not attempt
to do so here.
The particular breakpoints chosen above are derived from those in Bootstrap.
ImageMagick essentials
ImageMagick is a
command-line tool that can query and modify images in a wide variety
of formats. We use its convert tool for resizing
images:
convert example.jpg -strip -quality 30 -resize 256 example-256w.jpgHere, we convert example.jpg to
example-256w.jpg, stripping meta data
(-strip) setting the quality (JPEG compression in this
case) to 30, and resizing to a width of 256 pixels (the height will
be scaled proportionally).
We also use ImageMagick’s identify tool to obtain
the widths and heights of images:
identify -ping -format '%w,%h' example.jpgThe -ping option avoids consuming the entire file to
obtain this information. The -format option gives the
output format, in this case the width and height separated by a
comma ('%w,%h'). The output will be
e.g. 1024,768.
ImageMagick has numerous libraries available for various programming languages. However, we will use the command-line interface directly to minimize dependencies, given that our needs are simple.
Jekyll plugin
We now develop a Jekyll plugin that automates image resizing for
a given set of widths. For convenience, it also provides filters to
set srcset, width and height
attributes.
Configuration
Add the following to the site’s _config.yml:
responsive:
widths: [400,500,700,900]
quality: 30This configures the widths of resized images and their quality.
The choice of widths should cover the typical sizes of
images as they appear on the site. The choice of quality (between 0
and 100) is a trade-off between file size (lower at lower quality)
and clarity (higher at higher quality). It could be tuned by eye.
Low quality can be very satisfactory with the prevalence of high
definition displays.
For indii.org, where most images
are photos, I find noticeable degradation in thumbnail quality at
20, but little at 30. That thumbnails link to original
high-resolution images also permits the quality to be reduced. For
widths values I iterated with PageSpeed Insights to assess
performance, and settled on the above configuration.
Installation
The plugin requires ImageMagick. It is standard in Linux distributions and available through Homebrew on macOS. If not already, it can be installed with e.g.:
| OS | Command |
|---|---|
| Ubuntu | apt-get install imagemagick |
| Fedora | dnf install ImageMagick |
| openSUSE | zypper install ImageMagick |
| macOS | brew install imagemagick |
If you happen to be hosting with CloudFlare Pages, it is already installed.
Copy the Ruby code below to
_plugins/jekyll-responsive-magick.rb:
require 'fileutils'
module Jekyll
module ResponsiveFilter
@@sizes = {}
def identify(input)
site = @context.registers[:site]
if site.config['responsive']['verbose']
verbose = site.config['responsive']['verbose']
else
verbose = false
end
cmd = "identify -ping -format '%w,%h' .#{input.shellescape}"
if verbose
print("#{cmd}\n")
end
@@sizes[input] = `#{cmd}`.split(',', 2).map!(&:to_i)
end
def srcset(input)
site = @context.registers[:site]
if not input.is_a? String || input.length == 0 || input.chr != '/'
throw "srcset: input must be absolute path"
end
dirname = File.dirname(input)
basename = File.basename(input, '.*')
extname = File.extname(input)
src = ".#{dirname}/#{basename}#{extname}"
srcwidth = width(input)
srcset = ["#{input} #{srcwidth}w"]
if File.exist?(src) and ['.jpg', '.jpeg', '.png', '.gif'].include?(extname)
dest = site.dest
if site.config['responsive']['widths']
widths = site.config['responsive']['widths']
else
# as default, use breakpoints of Bootstrap 5
widths = [576,768,992,1200,1400]
end
if site.config['responsive']['quality']
quality = site.config['responsive']['quality']
else
quality = 80
end
if site.config['responsive']['verbose']
verbose = site.config['responsive']['verbose']
else
verbose = false
end
widths.map do |width|
if srcwidth > width
file = "#{basename}-#{width}w#{extname}"
dst = "_responsive#{dirname}/#{file}"
if not site.static_files.find{|file| file.path == dst}
site.static_files << StaticFile.new(site, "_responsive", dirname, file)
if not File.exist?(dst) or File.mtime(src) > File.mtime(dst)
FileUtils.mkdir_p(File.dirname(dst))
cmd = "convert #{src.shellescape} -strip -quality #{quality} -resize #{width} #{dst.shellescape}"
if verbose
print("#{cmd}\n")
end
system(cmd)
end
end
srcset.push("#{dirname}/#{file} #{width}w")
end
end
end
return srcset.join(', ')
end
def width(input)
if not input.is_a? String || input.length == 0 || input.chr != '/'
throw "width: input must be absolute path"
end
if not @@sizes[input]
identify(input)
end
return @@sizes[input][0]
end
def height(input)
if not input.is_a? String || input.length == 0 || input.chr != '/'
throw "height: input must be absolute path"
end
if not @@sizes[input]
identify(input)
end
return @@sizes[input][1]
end
end
end
Liquid::Template.register_filter(Jekyll::ResponsiveFilter)Usage
The plugin provides three filters: srcset,
width and height. Each consumes an
absolute path to an image (as it would appear in src)
and generates a value for the corresponding attribute. The intended
usage is:
<img
src="{{ src }}"
srcset="{{ src | srcset }}"
width="{{ src | width }}"
height="{{ src | height }}"
>src must be an absolute path,
i.e. beginning with /, such as
/assets/example.jpg. This is necessary for the plugin
to find the right file in your project.
With the plugin installed and one or more uses of the
srcset, width or height
filters, you can build your site as normal, now with responsive
images.
Testing
To test that responsive images are working, try:
- Inspecting the rendered HTML of pages in the
_sitedirectory, or via your browser by right-clicking and selecting “View source”. Verify that thesrcset,widthandheightattributes are set correctly. - In your browser, right clicking an image and selecting “Open
image in new tab” (works with Firefox, Chromium and Chrome). Verify
that the expected image variant has been used by inspecting the URL
of the new tab. It should have a name such as
example-w256.jpg, where256denotes the resized width. - Checking your page with PageSpeed Insights to verify that appropriate image variants are being selected for different devices.
Further details
The srcset, width and
height filters call ImageMagick’s convert
and identity on demand, rather than for all image
assets in a project. Caching is used to call identity
at most once per image per build, and convert once per
image per width, storing resized images in a subdirectory
_responsive/ for reuse in subsequent builds. Resized
images in _responsive/ are updated only if their
original source image changes (detected using last modified times on
files).
If you experience any issues with outdated images, or simply wish
to clean up, remove the whole _responsive/ directory
and rebuild. You may also wish to do this if you change the
widths option in _config.yml.
When using width and height, expect
additional build time of about one second per 100 images due to the
overhead of launching identity processes.
When using srcset for the first time, expect
additional build time on the order of minutes as resized images are
generated with convert. Performance will improve
drastically on subsequent builds because of the
_responsive/ subdirectory.
Summary
With a combination of a plugin and ImageMagick, we can automate
the addition of the srcset attribute to
img elements for Jekyll sites, creating responsive
images to minimize transfer time and optimize website
performance.