11ty : how to use the image plugin to generate responsive images for CSS

10 min read
automation
Cover illustration : superdupont – A developer writing code on a laptop in a cozy office with plants and warm lighting, digital illustration style

I wanted to use the Eleventy plugin to process an image and set it as a div background.

My initial approach involved using the following Eleventy shortcode and CSS:

{% image "bg.png", "photo", [300, 600], "(min-width: 30em) 50vw, 100vw" %}
.background-container {
    background-image: url('/public/bg.png');
}

I managed to make it work by placing the image and CSS in a public addPassthroughCopy directory, but this approach did not allow for automatic conversion of my images. I was seeking a more streamlined solution.

Automatic Eleventy image plugin processing

"Is there a way to have this image processed by the Eleventy plugin and set as a div background? The only (complicated) way I managed to do it is by putting the image and the CSS in a public addPassthroughCopy directory."

<style>
.background-container {
    background-image: url('/public/bg.png');
}
</style>

<div class="background-container">...</div>

Aankhen & Darth_Mall from the discord help forum pushed me in the right direction.

Illustration

Through our conversation, we explored how to use Eleventy's eleventy-img plugin to dynamically generate responsive background images. I was provided with a detailed approach to achieve this, involving:

  1. Configuring Eleventy: Setting up Eleventy to handle image processing and include CSS directly in the templates.
  2. Creating Nunjucks Templates: Defining templates to generate and include responsive images.
  3. Handling Errors: Debugging and ensuring paths and configurations were correctly set.

The initial attempts included several adjustments and corrections to ensure the configuration was working correctly.

After several iterations with chatGPT, the final solution involved:

  1. Setting Up eleventy.config.js: Including configurations for eleventy-img and custom Nunjucks filters.
  2. Creating Background Template (background.njk): A Nunjucks template to generate responsive CSS.
  3. Creating HTML Page (post.njk): An HTML template to use the generated CSS for responsive background images.

The final working solution was documented thoroughly and shared in Markdown format to ensure it could be easily shared and understood.

Documentation Content

This documentation explains how to configure Eleventy to use responsive background images with the eleventy-img plugin. You will learn how to define Nunjucks templates to generate images in different sizes and formats, and then include them in your pages with the appropriate styles.

Configure eleventy.config.js (or eleventy.config.images.js)

Create or update your eleventy.config.js file to include the following configuration:

My code (ahum co-authored w/ chatGPT!):

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

function relativeToInputPath(inputPath, relativeFilePath) {
  let split = inputPath.split("/");
  split.pop();
  return path.resolve(split.join(path.sep), relativeFilePath);
}

function isFullUrl(url) {
  try {
    new URL(url);
    return true;
  } catch(e) {
    return false;
  }
}

module.exports = function(eleventyConfig) {
  // Eleventy Image shortcode
  // https://www.11ty.dev/docs/plugins/image/
  eleventyConfig.addAsyncShortcode("image", async function imageShortcode(src, alt, widths, sizes) {
    // Full list of formats here: https://www.11ty.dev/docs/plugins/image/#output-formats
    // Warning: Avif can be resource-intensive so take care!
    let formats = ["avif", "webp", "auto"];
    let input;
    if(isFullUrl(src)) {
      input = src;
    } else {
      input = relativeToInputPath(this.page.inputPath, src);
    }

    let metadata = await eleventyImage(input, {
      widths: widths || ["auto"],
      formats,
      outputDir: path.join(eleventyConfig.dir.output, "img"),
    });

    // TODO loading=eager and fetchpriority=high
    let imageAttributes = {
      alt,
      sizes,
      loading: "lazy",
      decoding: "async",
    };

    return eleventyImage.generateHTML(metadata, imageAttributes);
  });

  // Custom filter to generate CSS with media queries
  eleventyConfig.addNunjucksAsyncFilter("imageCSS", async function(src, sizes, formats, callback) {
    let input;
    if(isFullUrl(src)) {
      input = src;
    } else {
      if (!this.page || !this.page.inputPath) {
        callback(new Error("Invalid page inputPath"), null);
        return;
      }
      input = relativeToInputPath(this.page.inputPath, src);
    }

    let metadata = await eleventyImage(input, {
      widths: sizes,
      formats: formats,
      urlPath: "/img/",
      outputDir: path.join(eleventyConfig.dir.output, "img"),
    });

    let css = sizes.map((size, index) => {
      if (metadata[formats[0]][index]) {
        return `@media (min-width: ${size}px) {
          .background-container {
            background-image: url('${metadata[formats[0]][index].url}');
          }
        }`;
      } else {
        console.error(`Error: No metadata found for format ${formats[0]} and size index ${index}`);
        return "";
      }
    }).join("\n");

    callback(null, css);
  });
};

Code improved by Aankhen 👇

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

function relativeToInputPath(inputPath, relativeFilePath) {
  let split = inputPath.split("/");
  split.pop();
  return path.resolve(split.join(path.sep), relativeFilePath);
}

function isFullUrl(url) {
  try {
    new URL(url);
    return true;
  } catch(e) {
    return false;
  }
}

module.exports = function(eleventyConfig) {
  const formats = ["avif", "webp", "auto"];

  // Eleventy Image shortcode
  eleventyConfig.addAsyncShortcode("image", async function imageShortcode(src, alt, widths, sizes) {
    const input = (isFullUrl(src)) ? src : relativeToInputPath(this.page.inputPath, src);

    const metadata = await eleventyImage(input, {
      widths: widths || ["auto"],
      formats,
      urlPath: "/img/",
      outputDir: path.join(eleventyConfig.dir.output, "img"),
    });

    const imageAttributes = {
      alt,
      sizes,
      loading: "lazy",
      decoding: "async",
    };

    return eleventyImage.generateHTML(metadata, imageAttributes);
  });

  // Custom filter to generate CSS with media queries
  eleventyConfig.addNunjucksAsyncFilter("imageCSS", async function(src, sizes, formats) {
    const input = (isFullUrl(src)) ? src : relativeToInputPath(this.page.inputPath, src);

    const metadata = await eleventyImage(input, {
      widths: sizes,
      formats,
      urlPath: "/img/",
      outputDir: path.join(eleventyConfig.dir.output, "img"),
    });

    console.log("Metadata generated:", metadata);

    let css = sizes.map((size, index) => {
      if (metadata[formats[0]][index]) {
        return `@media (min-width: ${size}px) {
          .background-container {
            background-image: url("${metadata[formats[0]][index].url}");
          }
        }`;
      } else {
        console.error(`Error: No metadata found for format ${formats[0]} and size index ${index}`);
        return "";
      }
    }).join("\n");

    return css;
  });

  // Pass through the image directory
  eleventyConfig.addPassthroughCopy({ "./public/img": "img" });
};

Creating Templates

1. Background Template (background.njk)

Create a file _includes/layouts/background.njk with the following content:

{% set sizes = [300, 600, 1200] %}
{% set formats = ["jpeg"] %}
{% set imagePath = imagebg %}

{{ imagePath | imageCSS(sizes, formats) | safe }}

2. HTML Page (post.njk)

Create a file post.njk with the following content:

---
title: CSS background
description: CSS bg
layout: layouts/post.njk
tags: posts
backgroundImage: "my-bg.png"
---

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ title }}</title>
    <style>
        .background-container {
            background-size: cover;
            background-position: center;
            width: 100%;
            height: 100vh;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            color: white;
            font-size: 2em;
        }

        .background-container p,
        .background-container input,
        .background-container button {
            margin: 10px;
        }

        .background-container input {
            padding: 10px;
            font-size: 1em;
        }

        .background-container button {
            padding: 10px 20px;
            font-size: 1em;
            cursor: pointer;
        }

        {% set imagebg = backgroundImage %}
        {% include "layouts/background.njk" %}
    </style>
</head>
<body>
    <div class="background-container">
        <p>YES</p>
        <input type="text" placeholder="Ask your question here" />
        <button>Submit</button>
    </div>
</body>
</html>

Conclusion

By following these steps, you will have configured Eleventy to use responsive background images.

The configuration allows you to generate different sizes and formats of images and include them in the CSS with media queries for optimal display on various devices.

Illustration

Verification

  1. Check Paths and Permissions:

    • Ensure the public/img folder exists and permissions are set correctly.
    • Verify that the images are actually copied into the output directory _site/img.
  2. Debugging and Logs:

    • Use the logs generated by eleventy.config.js to verify that images are generated correctly and paths are accurate.
    • In case of issues, use error messages and logs to identify and fix configuration or path access errors.

What I Learned from This Experience

From this experience, I learned the importance of leveraging Eleventy's flexibility and plugins to create more efficient and streamlined solutions. Initially, I faced the challenge of processing and converting images automatically while integrating them as responsive background images in my project.

The journey involved understanding and implementing the eleventy-img plugin, configuring custom Nunjucks filters, and writing templates that dynamically generate the necessary CSS for different viewport sizes.

The key takeaways from this process include:

  • Configuring Eleventy Plugins: Gaining a deeper understanding of how to set up and configure plugins like eleventy-img for automatic image processing.
  • Template Creation: Learning how to create Nunjucks templates that can dynamically generate CSS and HTML content.
  • Responsive Design: Implementing responsive design techniques using media queries and dynamically generated CSS for different image sizes.
  • Debugging and Error Handling: Handling configuration and syntax errors effectively to ensure the smooth functioning of the project.

This experience also reinforced the value of community support (ty Aankhen & Darth_Mall 💪) and documentation. Engaging with the Eleventy community and referring to various examples and documentation provided crucial insights and guidance throughout the process.

A Word on Eleventy

Eleventy is a highly flexible and powerful static site generator that provides a great balance between simplicity and functionality. Its ability to work seamlessly with various templating engines like Nunjucks, and its extensive plugin ecosystem, make it an excellent choice for developers looking to create fast, efficient, and highly customizable websites.

Eleventy's minimalist approach allows developers to build projects with minimal configuration while still offering the power to handle complex requirements. Whether you're a seasoned developer or just starting out, Eleventy's intuitive and straightforward workflow makes it a joy to work with.

Overall, this experience has been both educational and rewarding, showcasing the true potential of Eleventy in creating dynamic and responsive web solutions.

PS: Without ChatGPT, I would never have managed. Thanks to this experience, even a non-developer like me can now achieve complex tasks and improve my skills.