You can do this using image transforms.
There are a couple things you’re going to need to consider though:
- Not every image will necessarily be high-res (e.g. a computer screenshot taken on a non-retina screen)
- You probably don’t want to output images that are larger than their original size
For #1, we need some way of identifying images that are 1X vs. 2X. You could do this a couple different ways:
- Give your assets a Lightswitch field that identifies if it’s a 2X image
- Use a consistent naming convention for 2X image filenames, e.g. “filename@2x.ext” (to borrow Retina.js’s convention)
I personally prefer the second. (That’s what we do on buildwithcraft.com, except we use “.2x” instead of “@2x”.)
We can tackle #2 with some logic in the template that only uses a transform when the resulting size is going to be smaller than the original image.
It’s the combination of these two problems where things get interesting: You need to figure out the image’s CSS pixel size (based on whether it’s 1X or 2X) before you start determining whether it needs a transform, and how big to make the transform, etc.
Here’s what we came up with for Craft’s documentation (such as the image on the asset transforms page):
- Create a macro called
srcset()
which will be responsible for outputting a <source>
or <img>
tag’s srcset=
attribute and value, and optionally the width=
and height=
attributes as well. It should accept the following arguments:
image
- an AssetFileModel
maxCssWidth
- the maximum “CSS pixel” width that we want to output the image at
includeDimensions
- whether the macro should also output the width=
and height=
attributes.
- Create a
<picture>
element with nested <source>
and <img>
elements per the Picture spec, using our new srcset()
macro to write out the srcset=
attributes.
Here’s what the macro looks like:
{% macro srcset(image, maxCssWidth, includeDimensions) %}
{%- spaceless %}
{# Is this a 2X image? #}
{% set is2x = image.filename matches '/@2x\\.\\w+$/' %}
{# Determine the CSS width for the image, if it were output at 100% #}
{% set cssWidth = (is2x ? round(image.width / 2) : image.width) %}
{# Determine the image size that 1X screens should get #}
{% set width = (cssWidth > maxCssWidth ? maxCssWidth : cssWidth) %}
{# Determine the image size that 2X screens should get #}
{% set width2x = (width * 2) > image.width ? image.width : (width * 2) %}
{# Output the srcset= attribute value #}
srcset="{{ image.getUrl(image.width != width ? { width: width }) }}, {{ image.getUrl(image.width != width2x ? { width: width2x }) }} 2x"
{#- Output the width= and height= attributes if needed #}
{%- if includeDimensions %} width="{{ width }}" height="{{ image.getHeight({ width: width }) }}"{% endif %}
{% endspaceless -%}
{% endmacro %}
Adjust that {% set is2x = ...
line based on however you’re identifying 2X images. For example if you’re going the Lightswitch route instead, it could be as simple as:
{% set is2x = image.is2x %}
Note that each of the image.getUrl()
calls on the {{ output }} line are making sure that the width is different than the actual image width before passing the { width: X }
object into getUrl()
- that way we’re not having Craft go through the trouble of generating a transform unless we actually need to.
With that macro in place, now we just need to import it in our template, and start using it.
{% from _self import srcset %}
{# ... #}
{% for image in entry.myAssetsField %}
<picture>
<!--[if IE 9]><video style="display: none;"><![endif]-->
<source media="(min-width: 1000px)" {{ srcset(image, 1000) }}>
<source media="(min-width: 800px)" {{ srcset(image, 800) }}>
<source media="(max-width: 320px)" {{ srcset(image, 320) }}>
<!--[if IE 9]></video><![endif]-->
<img {{ srcset(image, 600, true) }} alt="{{ image.title }}">
</picture>
{% endfor %}
If you placed the macro in a different template, replace _self
with the path to that template, e.g. "_macros"
.
2Quick note: I think a picture element is overkill in this use case.
srcset
andsizes
attributes on animg
tag would be enough. – Jérôme Coupé – 2014-08-25T08:20:37.9831 – jnowland – 2014-08-28T00:45:19.333
Using a
<picture>
tag would sort of indicate art direction or a ratio change. Which is in plugin territory. See http://graphics8.nytimes.com/images/2014/05/22/blogs/imagecrop480.gif for a good demo taken from http://open.blogs.nytimes.com/2014/06/17/scoop-a-glimpse-into-the-nytimes-cms/?_php=true&_type=blogs&_r=0With responsive design becoming the norm it wouldn't be the worst thing to find out of the box.