blog
Lawrence Murray on 30 October 2022
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:
Here, we address the first motivation, showing how to automate image resizing for Jekyll sites, by using ImageMagick command-line tools.
While the second motivation makes sense for all image formats, the former only makes sense for raster formats such as PNG and JPEG. Vector formats such as SVG are already responsive, in that they scale up and down without loss in clarity or change in file size.
Responsive images are enabled by two attributes of the HTML img
element:
srcset
, providing a list of available image variants and their widths, andsizes
providing 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.jpg
of width 256 pixels,example-w512.jpg
of width 512 pixels, andexample.jpg
of width 1024 pixels.The sizes
attribute provides guidance as to the context in which the image appears:
575px
) then there is one column (100vw
i.e. 100% of viewport width),767px
) then there are two columns (50vw
i.e. 50% of viewport width),991px
) then there are three columns (34vw
—rounding up),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 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.jpg
Here, 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.jpg
The -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.
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.
Add the following to the site’s _config.yml
:
responsive:
widths: [400,500,700,900]
quality: 30
This 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.
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)
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.
To test that responsive images are working, try:
_site
directory, or via your browser by right-clicking and selecting “View source”. Verify that the srcset
, width
and height
attributes are set correctly.example-w256.jpg
, where 256
denotes the resized width.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.
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.
A how-to and round-up of cloud service providers.
Lawrence Murray
22 Nov 22
Zero-stride catch and a custom CUDA kernel.
Lawrence Murray
16 Mar 23
Working or failing gracefully across Apostrophe, Kramdown, and Jekyll. No plugins required.
Lawrence Murray
2 Nov 22
F. Ronquist, J. Kudlicka, V. Senderov, J. Borgström, N. Lartillot, D. Lundén, L.M. Murray, T.B. Schön, D. Broman