Get image pixel colours in Eleventy / Node

How to get colour information from images at build time when using Eleventy (or Node more generally).

6 minute read

If you're pre-building pages with Eleventy (static site generator) you're probably taking advantage of the Eleventy Image plugin for transforming your images. In this post I'm going to walk through doing a little extra work to get colour information from those images at the same time.

TL;DR #

js
sharp(src)
.raw()
.toBuffer()
.then(data => {
return `rgb(${data[0]}, ${data[1]}, ${data[2]})`;
});

How is this useful? #

There are many ways you could make use of the following technique, but one example that you'll probably recognise is an audio player that uses colours from album artwork in its interface.

Four screenshots of a music player app, with different background gradients

In a Web context your first thought might be to do this in the browser, but if we're already transforming our images on a server / during a build, that feels like a much better time to be running this operation.

A simple example #

For the sake of this post I'm going to use the simplest use-case I can think of. I've got some images isolated on coloured backgrounds, and I want their containers to have the same background colour. I'm going to use photos of some pots I've been making recently.

Screenshot of a photo of teapot, on a blue page showing either side

What this screenshot shows is a full height photo on a web page, with the page background visible either side. I've made the background blue for demonstration purposes. Our aim here is for those blue areas to be the same colour as the photo background.

There are many JavaScript libraries for getting colours from images, a popular example is Color Thief. These libraries tend to produce palettes based on averaged colours. This is might be what you want for something like the music player example. In this case though I just want one, predictable colour, so I'm going to make use of a tool that Eleventy Image is already using under the hood - Sharp.

Eleventy Image setup #

As a starting point, in my .eleventy.js file I have this fairly standard short-code configured:

js
eleventyConfig.addShortcode("image", async function(src, alt, name) {
let metadata = await Image(src, {
widths: [512, 1024],
formats: ["webp", "jpeg"],
});
let imageAttributes = {
class: `${name}_img`,
alt,
sizes: "100vw",
loading: "lazy",
decoding: "async",
};
return Image.generateHTML(metadata, imageAttributes);
});

Inside this I'm going to add two things:

  1. A function to get the colour of the first (top-left) pixel
  2. A style attribute containing a custom property on the picture element

Get pixel colour with Sharp #

You'll need to add const sharp = require("sharp") to the top of .eleventy.js, then in your addShortcode function add:

js
async function bgColor() {
return sharp(src)
.raw()
.toBuffer()
.then(data => {
return `rgb(${data[0]}, ${data[1]}, ${data[2]})`;
});
}

We are passing our source image (src) to Sharp, and as described here, getting data about the pixels back. We are returning an RGB colour value from the red, green, and blue values of the first pixel. With that we are nearly there, we just need to get that rgb() value to CSS so we can make use of it.

Add a CSS custom property #

We can get our colour to CSS via a custom property in a style attribute. I need that custom property higher in the DOM than the img element though, because I want to colour the wrapper of the img not the img itself. We could hand write the output of or short-code and add a wrapper there, but as I know that a picture element is going to be created here I make use of pictureAttributes (added in v4 of Eleventy Image). This is passed via options (again inside addShortcode) like so:

js
let options = {
pictureAttributes: {
class: `${name}_picture`,
style: `--background: ${await bgColor()}`
},
};

Then modify the return statement to include options:

js
return Image.generateHTML(metadata, imageAttributes, options);

As well as passing the colour to the style attribute, I also added a class here. So in my CSS I can finally add that background colour:

css
.pot-list_picture {
background-color: var(--background);
}

You can see the result here.

Start a conversation. @ me on Mastodon.