Zach’s ugly mug (his face)

Zach Leatherman

Don’t Shut Down Your Business! Instead Useeleven_ty’s AvatarEleventy Image

January 25, 2021 #9 Popular

This talk was first delivered for Jamstack Toronto.

Hello, Zach Leatherman here, Local SSG Owner and 👍🏻 Image 👍🏻 Enthusiast 👍🏻. Are your JavaScripts making too much noise all the time?

Well image optimization can’t solve that problem but I am here to tell you about Eleventy 🖼 Image—an exciting new utility to perform build-time image transformations for both vector and raster images. It’s easy to use and it does not require Eleventy. You can use it in any Node.js script.

Contents #

The Hello World #

First, npm install @11ty/eleventy-img into your project.

Then head over to Unsplash and find yourself a nice image. I like to look for a nice picture of a Nebula. Here’s a good Nebula. Download a copy of this image locally to our project directory and name it nebula.jpg.

Make another file in our project directory called demo.js and use the following contents:

const Image = require("@11ty/eleventy-img");

(async () => {
let stats = await Image("nebula.jpg");

console.log( stats );
})();

Run this script using node demo.js and you’ll see something like this:

~/Code/jamstack-toronto ᐅ node demo.js
{
webp: [
{
format: 'webp',
width: 7857,
height: 7462,
filename: '7fc15c7-7857.webp',
outputPath: 'img/7fc15c7-7857.webp',
url: '/img/7fc15c7-7857.webp',
sourceType: 'image/webp',
srcset: '/img/7fc15c7-7857.webp 7857w',
size: 3439498
}
],
jpeg: [
{
format: 'jpeg',
width: 7857,
height: 7462,
filename: '7fc15c7-7857.jpeg',
outputPath: 'img/7fc15c7-7857.jpeg',
url: '/img/7fc15c7-7857.jpeg',
sourceType: 'image/jpeg',
srcset: '/img/7fc15c7-7857.jpeg 7857w',
size: 6245602
}
]
}

By default, Eleventy Image creates a webp and jpeg version of the image you feed it, with the same dimensions. It looks like we fed it a very large image, 7857×7462 and a ~3MB webp plus a ~6MB jpeg.

If you want to create different formats, you can change this behavior using the formats option. It looks like this:

const Image = require("@11ty/eleventy-img");

(async () => {
let stats = await Image("nebula.jpg", {
// ["webp", "jpeg"] is the default
formats: ["jpeg"],
});
})();

You can use jpeg, png, webp, avif (new!), and svg (although SVG requires an SVG input file).

Remote Control #

If you return to our lovely Nebula image on Unsplash, we can modify our script to download this remote image for us.

This is especially useful when your content is being driven from a CMS or other external data source. We want to optimize those images too.

Right click on our Nebula and retrieve the big raw image URL. You may want to remove the w= and q= parameter to get as close to the raw original as possible.

Instead of nebula.jpg, let’s use this URL.

const Image = require("@11ty/eleventy-img");

(async () => {
let stats = await Image("https://images.unsplash.com/photo-1462331940025-496dfbfc7564?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop", {
formats: ["jpeg"]
});

console.log( stats );
})();

which outputs:

~/Code/jamstack-toronto ᐅ node demo.js
{
jpeg: [
{
format: 'jpeg',
width: 7857,
height: 7462,
filename: '2056cbb-7857.jpeg',
outputPath: 'img/2056cbb-7857.jpeg',
url: '/img/2056cbb-7857.jpeg',
sourceType: 'image/jpeg',
srcset: '/img/2056cbb-7857.jpeg 7857w',
size: 6245511
}
]
}

This is awesome because the request to the remote URL is cached (by default) for one day. No additional network requests are made. No source images are needed to be stored in your repo. You can work offline (even after the full day has passed) and it will use this cached copy. (Check out the additional Caching options)

I use this for all of the avatars on the 11ty.dev. The home page alone includes 522 separate images and still weighs only 852 KB.

Going back to our nebula image—at over 6MB—it’s obviously way too big.

Travel Size #

Let’s resize our Nebula to 1400px.

const Image = require("@11ty/eleventy-img");

(async () => {
let stats = await Image("https://images.unsplash.com/photo-1462331940025-496dfbfc7564?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop", {
formats: ["jpeg"],
widths: [1400], // by default this uses the original size
});

console.log( stats );
})();

which outputs:

~/Code/jamstack-toronto ᐅ node demo.js
{
jpeg: [
{
format: 'jpeg',
width: 1400,
height: 1329,
filename: '2056cbb-1400.jpeg',
outputPath: 'img/2056cbb-1400.jpeg',
url: '/img/2056cbb-1400.jpeg',
sourceType: 'image/jpeg',
srcset: '/img/2056cbb-1400.jpeg 1400w',
size: 170827
}
]
}

A much more reasonable 170KB.

Please don’t make me write HTML #

If you don’t want to write the HTML, Eleventy Image can do that for you too.

const Image = require("@11ty/eleventy-img");

(async () => {
let stats = await Image("https://images.unsplash.com/photo-1462331940025-496dfbfc7564?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop", {
formats: ["jpeg"],
widths: [1400],
});

console.log( Image.generateHTML(stats, {
alt: "A bomb ass nebula",
}) );
})();

Outputs:

~/Code/jamstack-toronto ᐅ node demo.js
<img src="/img/2056cbb-1400.jpeg" width="1400" height="1329" alt="A bomb ass nebula">

What’s awesome here is that we get the width and height attributes of our output image added for free. This allows the browser to set the aspect ratio of the image while it’s loading to avoid Content Layout Shifts.

The second argument to generateHTML is any HTML attributes you’d like to include on the <img>. Make sure you include alt or we’ll throw an error! alt="" works fine.

It gets better. Let’s feed it a more complicated stats object, with more formats and more widths:

const Image = require("@11ty/eleventy-img");

(async () => {
let stats = await Image("https://images.unsplash.com/photo-1462331940025-496dfbfc7564?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop", {
formats: ["avif", "webp", "jpeg"],
widths: [600, 1200, 1800],
});

console.log( Image.generateHTML(stats, {
alt: "A bomb ass nebula",
loading: "lazy",
decoding: "async",
}) );
})();

Outputs:

~/Code/jamstack-toronto ᐅ node demo.js
<picture>
<source type="image/avif" srcset="/img/2056cbb-600.avif 600w, /img/2056cbb-1200.avif 1200w, /img/2056cbb-1800.avif 1800w">
<source type="image/webp" srcset="/img/2056cbb-600.webp 600w, /img/2056cbb-1200.webp 1200w, /img/2056cbb-1800.webp 1800w">
<source type="image/jpeg" srcset="/img/2056cbb-600.jpeg 600w, /img/2056cbb-1200.jpeg 1200w, /img/2056cbb-1800.jpeg 1800w">
<img src="/img/2056cbb-600.jpeg" width="600" height="569" alt="A bomb ass nebula" loading="lazy" decoding="async">
</picture>

We just generated 9 different images (3 formats, 3 widths) and the generateHTML was smart and used <picture> instead of <img>. Hooray!

Next try passing a sizes attribute to your generateHTML call.

SVG Flies First Class #

It’s easy to do vector to raster conversion, too. This would likely work great for programmatically generating OpenGraph images, for example.

const Image = require("@11ty/eleventy-img");

(async () => {
let stats = await Image("https://upload.wikimedia.org/wikipedia/commons/f/fd/Ghostscript_Tiger.svg", {
formats: ["avif"],
widths: [1200],
});

console.log( stats );
})();

Outputs:

~/Code/jamstack-toronto ᐅ node demo-svg.js
{
avif: [
{
format: 'avif',
width: 1200,
height: 1200,
filename: '11efb293-1200.avif',
outputPath: 'img/11efb293-1200.avif',
url: '/img/11efb293-1200.avif',
sourceType: 'image/avif',
srcset: '/img/11efb293-1200.avif 1200w',
size: 53041
}
]
}

You can passthrough svg input to svg output too, which may seem strange. But when you’re using remote images, perhaps from a CMS, and you don’t know what image format the end user might be uploading, it’s useful to preserve the vector format if one is available! See also the svgShortCircuit option.

CSS Background Check #

Note that this is the first example so far that uses Eleventy to function.

Using Eleventy Image with a CSS background image is only slightly more tricky. Consider the following Eleventy template (perhaps named demo.11ty.js) that outputs a CSS file:

const Image = require("@11ty/eleventy-img");

module.exports.data = async function() {
return {
permalink: "/style.css"
};
};

module.exports.render = async function () {
let stats = await Image("nebula.jpg", {
formats: ["jpeg"],
widths: [600],
});

return `#hero-div {
background-image: url(
${stats.jpeg[0].url});
}
`
;
};

You might image any number of things that Eleventy Image could be used for: Favicons, Open Graph images, raster images embedded inside of SVG using <image/>.

In addition to local file names and remote URL strings, the first argument to Image() could be a Buffer too—maybe even from Puppeteer’s page.screenshot().

Go forth #

Image optimization won’t mitigate your JavaScript performance costs, but it can make your page load faster and lighter! Eleventy Image can help.

Zach’s ugly mug (his face)

Zach is a builder for the web with Netlify. He’s currently fixated on web fonts and static site generators. His public speaking résumé includes talks in eight different countries at events like Beyond Tellerrand, Smashing Conference, CSSConf, and The White House. He is an emeritus of Filament Group, NEJS CONF, and still helps out with NebraskaJS. Read more about Zach »

Previous
Barebones CSS for Fluid Images
Next
A little collection of theme-less Web Components

15 Retweets

JamstackTORONTO aka Jamstack U.N. 🇺🇳EleventyNathalieMohammad ArifThomas AllmerTristan GibbsRyan No Seriously Wear a Mask BooneRares PortanRaymond Camden 🥑Load LabzJean Pierre KolbNolan FranklinAndy BellMatt DeCampEleventy
127 Likes
MaëligRhian van Esch⚙️ Christian SharafBrett JankordNaomi See 🤖👩🏻‍🎤❤️🤦‍♀️🥸🦠Patrick HaugNixinovaMike AparicioDanny de VriesJasper Moelker 🇪🇺💜⚡CosCarles MuiñosRobert McCrearyPeter AntoniusAndrew AquinoMarco useCauseAndEffect()Paul ApplegateDave 👾 Working from homeDennisAlena Nikolaevaaaron hansDevessierBrett EvansEleventyRattleknife DesignChris TseStephanie EcklesTrevor TwiningOleksandr ShutLee FisherMaëligNathalieDanny de VriesDinisCarol 🌻okdiosDrew AmatoGuillaume DeblockAnvesh ⚛️🌐Michelle BarkerDavid Hund ✌Mia || MiriamWebchefz InfotechRobin RendleDanielTodd 🦞Amber WilsonJohn MeyerhoferKarim JordanVladislav ShkodinBrett EvansMathias Rando JuulJorge del CasarJosh CrainAlex@home 🏡 🌻✊StackroleAndrew ColdenThomas HollandThomas AllmerKevin Gimbel 🖖 (he/him)Iago BarreiroJan Kollarsdan leathermanᴅᴇʀᴇᴋStephan JägernlbEl Perro Negro 💙💛🇪🇦Jan SkovgaardMichael GehrmannZeno RochaAnders GrendstadbakkMatt BiilmannMatt Tunney ☕️Jonathan Yeong✨JoëlRyan No Seriously Wear a Mask BooneVictor CamnerinCallum GrantMarc Littlemore 🤖Hugh HaworthCAHO badMartin Berglundzack - building tools for NotionMichael HastrichSalmndrStefan FeserNolan FranklinThis Dot LabsCody Peterson #BLM🍄 Bobby 🍄Jean Pierre Kolbbudparr, enthusiastT Carter BaxterPaul ApplegateErik VorhesAlex ClappertonDevin ClarkFluxmod - Email MarketerAlejandro RodríguezTanner DolbyLoad LabzChristian | 👨🏼‍💻Uncle AniekanJack 🕺Jens GrochtdreisMaxime Richard_octoJack𝕕𝕘𝕣𝕒𝕞𝕞𝕒𝕥𝕚𝕜𝕠Matthias Ottaaron hansBri Camp GomezPatrick HaugArihant VermaJake KorthelvendrimEleventywesruv 💻🐕Florian Geierstangerhenry from online ✷konfuzedEmilio MartinezrickthehatAndy BellThamara Gerig ☁🌈🌞Dave RupertCarles MuiñosMatt DeCamp
15 Replies
  1. JamstackTORONTO aka Jamstack U.N. 🇺🇳

    JamstackTORONTO aka Jamstack U.N. 🇺🇳 @JAMstackTORONTO #

    you're always welcome Zach (#doubleEntendre). Apologies for the late deploy, but happy we finally got them out.

  2. Bruce Lawson

    Bruce Lawson @brucel #

    We're going to sign it to you every day until there's a CLI, and a nice GUI interface for Mac and Win

  3. Zach Leatherman

    Zach Leatherman @zachleat #

    wow this is going to be in my head all day—thanks max

  4. Carol 🌻

    Carol 🌻 @CarolSaysThings #

    Hey! This is great timing, I'm setting up eleventy-img on my site this week 🙌🏼 I do have a question, if that's ok, and feel free to point me to a more appropriate channel: If you set multiple widths for the images, generateHtml sets the width of the img tag to the smallest (+… Truncated

  5. Simon Pieters

    Simon Pieters @zcorpan #

    That looks pretty slick. Two reactions: * `sizes` is required when using "w" * Maybe it could have a generateCSS that serializes `image-set()`

  6. Zach Leatherman

    Zach Leatherman @zachleat #

    DAngErouslY clOsE to A SeA shanTY, HerE

  7. Bruce Lawson

    Bruce Lawson @brucel #

    "I was at the end of my tether, man.."

  8. Zach Leatherman

    Zach Leatherman @zachleat #

    dOn’t bUtTEr mE Up UnlEss mY head IS sTuCK IN The sTaIrCasE BANisTEr bRucE

  9. Zach Leatherman

    Zach Leatherman @zachleat #

    no plans for a CLI for now but maybe if a bunch of people ask 😅

  10. Tristan Gibbs

    Tristan Gibbs @gibbs_digital #

    Thanks for writing this. I’ve been wanting to learn more about Eleventy Img 🙂

  11. Bruce Lawson

    Bruce Lawson @brucel #

    i can deal with that code, and it looks top, thanks!

  12. Zach Leatherman

    Zach Leatherman @zachleat #

    Thanks!

  13. James Doc

    James Doc @jamesdoc #

    That is a nice touch

  14. Dave Rupert

    Dave Rupert @davatron5000 #

    Do you use eleventy-img in your build pipeline? If so, what’s the cost? Or do you use it as a “sidecar” kind of tool to process images while you’re building you static site?

  15. Bri Camp Gomez

    Bri Camp Gomez @BrianaCamp #

    "Are your JavaScripts making too much noise all the time?"

    5 Mentions
    1. 54.252.222.218 #

      Optimising images and serving them in modern formats can be a real headache when you’re making a website. With my new website powered by headless WordPress, I thought this would be difficult. I would need to: download images from the WordPress site optimise them with an ima… Truncated

    2. martinschneider.me #

      In THE ChANGElOg i’m DocUMENTIng MOdIfiCations TO ThiS siTe tHaT mIght be oF iNTERESt, BUT ARe NoT necEssArIly MY OWN iDeaS OR woRk.wHEn I iniTialLY buILD The HOMEpage oF tHIS WEbSiTE, i gENeRATED thE useD iMaGe sizEs bY HAnD and coNVerteD tHeM tO WebP with an onLInE imaGE cOnVer… Truncated

    3. Patrick Faramaz

      Patrick Faramaz @PatrickFaramaz #

      #eleventy image api zachleat.com/web/eleventy-i…

    4. Julien Brionne

      Julien Brionne @akashrine #

      Don’t Shut Down Your Business! Instead Use Eleventy Image zachleat.com/web/eleventy-i… #personalblog #feedly

    5. Matthias Ott

      Matthias Ott @m_ott #

      Eleventy Image let’s you generate jpeg, png, webp, avif, and svg – and even outputs the HTML code for a picture element! 🚀🙌